Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error during SAML login: No hostname defined #417

Open
sorenwacker opened this issue Aug 29, 2024 · 0 comments
Open

Error during SAML login: No hostname defined #417

sorenwacker opened this issue Aug 29, 2024 · 0 comments

Comments

@sorenwacker
Copy link

Hi, I am not sure if this is the correct repository for my issue. Please let me know if this is the wrong place.

I have a question regarding the python saml2 package: from onelogin.saml2

I am getting this error:

Error during SAML login: No hostname defined

at login and logout, and I am not sure where to add the hostname exactly.

I am serving a plotly-dash app and try to add a SAML login wrapper like this:

import os
import logging
from flask import Flask, request, redirect, url_for
from onelogin.saml2.auth import OneLogin_Saml2_Auth as Auth
from onelogin.saml2.settings import OneLogin_Saml2_Settings as Settings
from onelogin.saml2.metadata import OneLogin_Saml2_Metadata
from slurmo.app.app import create_dash_app
from dotenv import load_dotenv
from time import time, strftime, gmtime


# Load environment variables from a .env file if present
load_dotenv()

# Configure logging
logging.basicConfig(
    level=logging.DEBUG,  # Set to DEBUG to see detailed messages
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# Helper function to read file contents safely
def read_file(path):
    try:
        with open(path, 'r') as file:
            return file.read()
    except Exception as e:
        logger.error(f"Error reading file at {path}: {e}")
        raise

# SAML configuration settings
saml_settings = {
    'sp': {
        'entityId': os.getenv('SAML_ENTITY_ID', 'https://yourapp.com/saml'),
        'assertionConsumerService': {
            'url': os.getenv('SAML_ACS_URL', 'https://yourapp.com/saml/acs'),
        },
        'singleLogoutService': {
            'url': os.getenv('SAML_SLO_URL', 'https://yourapp.com/saml/logout'),
        },
        'x509cert': read_file(os.getenv('SAML_SP_CERT_PATH', '/path/to/default/sp-cert.pem')),
        'privateKey': read_file(os.getenv('SAML_SP_PRIVATE_KEY_PATH', '/path/to/default/sp-private-key.pem')),
        'hostname': 'https://example.com',
    },
    'idp': {
        'entityId': os.getenv('SAML_IDP_ENTITY_ID', 'https://idp.example.com/metadata.xml'),
        'singleSignOnService': {
            'url': os.getenv('SAML_IDP_SSO_URL', 'https://idp.example.com/sso'),
        },
        'singleLogoutService': {
            'url': os.getenv('SAML_IDP_SLO_URL', 'https://idp.example.com/slo'),
        },
        'x509cert': read_file(os.getenv('SAML_IDP_CERT_PATH', '/path/to/default/idp-cert.pem')),
        'hostname': 'https://example.com',

    },
}

def create_app():
    """
    Create and configure the Flask app with Dash integration and SAML2 authentication.
    """
    # Retrieve data path from environment variables or set a default path
    data_path = os.getenv('SLURMO_DATA_PATH', 'slurmo')

    # Create a simple args object to pass to create_dash_app
    class Args:
        pass
    
    args = Args()
    args.data_path = data_path
    
    app = Flask(__name__)
    app.secret_key = os.getenv('FLASK_SECRET_KEY', 'your-secret-key')

    # Enable Flask debugging
    app.debug = True

    # Initialize SAML Auth
    def get_saml_auth():
        request_data = {
            'get_data': request.args,
            'post_data': request.form,
        }
        settings = Settings(saml_settings)
        logger.debug(f"SAML Settings: {saml_settings}")
        return Auth(request_data, settings)

    @app.route('/saml/login')
    def saml_login():
        logger.debug('Attempting SAML login')
        try:
            auth = get_saml_auth()
            login_url = auth.login()
            logger.debug(f'Login URL: {login_url}')
            return redirect(login_url)
        except Exception as e:
            logger.error(f"Error during SAML login: {e}")
            return "Login failed", 500

    @app.route('/saml/acs', methods=['POST'])
    def saml_acs():
        logger.debug('Processing SAML ACS')
        try:
            auth = get_saml_auth()
            auth.process_response()
            attributes = auth.get_attributes()
            logger.debug(f'SAML Attributes: {attributes}')
            if not auth.is_authenticated():
                logger.warning("Authentication failed")
                return redirect(url_for('saml_login'))
            return redirect(url_for('protected_resource'))
        except Exception as e:
            logger.error(f"Error processing SAML response: {e}")
            return "ACS processing failed", 500

    @app.route('/saml/logout')
    def saml_logout():
        logger.debug('Processing SAML logout')
        try:
            auth = get_saml_auth()
            logout_url = auth.logout()
            logger.debug(f'Logout URL: {logout_url}')
            return redirect(logout_url)
        except Exception as e:
            logger.error(f"Error during SAML logout: {e}")
            return "Logout failed", 500

    @app.route('/saml/metadata')
    def saml_metadata():
        try:
            auth = get_saml_auth()
            settings = auth.get_settings()

            # Set valid_until and cache_duration with appropriate values
            valid_until = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time() + OneLogin_Saml2_Metadata.TIME_VALID))
            cache_duration = OneLogin_Saml2_Metadata.TIME_CACHED  # 1 week

            metadata = OneLogin_Saml2_Metadata.builder(
                sp=settings.get_sp_data(),
                authnsign=settings.get_security_data().get('authnRequestsSigned', False),
                wsign=settings.get_security_data().get('wantAssertionsSigned', False),
                valid_until=valid_until,
                cache_duration=cache_duration,
                contacts=settings.get_contacts(),
                organization=settings.get_organization()
            )
            return metadata, 200, {'Content-Type': 'text/xml'}
        except Exception as e:
            logger.error(f"Error generating SAML metadata: {e}")
            return "Metadata generation failed", 500

    @app.route('/protected')
    def protected_resource():
        logger.debug('Accessing protected resource')
        auth = get_saml_auth()
        if not auth.is_authenticated():
            logger.warning("Unauthorized access attempt")
            return redirect(url_for('saml_login'))
        return "Protected Resource"

    # Redirect root URL to /dashboard/
    @app.route('/')
    def redirect_to_dashboard():
        return redirect('/dashboard/')

    # Create the Dash app
    dash_app = create_dash_app(args, server=app, url_base_pathname='/dashboard/')

    # Protect the Dash routes
    @app.before_request
    def before_request():
        if request.path.startswith('/dashboard/'):
            auth = get_saml_auth()
            if not auth.is_authenticated():
                logger.warning("Unauthorized access attempt to /dashboard/")
                return redirect(url_for('saml_login'))

    return app

# This is the application object used by WSGI servers
app = create_app()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant