def default_authenticator(auth, req, ses, storage):
    # This is assemblyline authentication procedure
    # It will try to authenticate the user in the following order until a method is successful
    #    apikey
    #    username/password
    #    PKI DN
    #
    # During the authentication procedure the user/pass and DN methods will be subject to OTP challenge
    # if OTP is allowed on the server and has been turned on by the user
    #
    # Apikey authentication procedure is not subject to OTP challenge but has limited functionality

    apikey = auth.get('apikey', None)
    otp = auth.get('otp', 0)
    webauthn_auth_resp = auth.get('webauthn_auth_resp', None)
    state = ses.pop('state', None)
    password = auth.get('password', None)
    uname = auth.get('username', None)
    oauth_token = auth.get('oauth_token', None)

    if not uname:
        raise AuthenticationException('No user specified for authentication')

    # Bruteforce protection
    auth_fail_queue = NamedQueue("ui-failed-%s" % uname, **nonpersistent_config)
    if auth_fail_queue.length() >= config.auth.internal.max_failures:
        # Failed 'max_failures' times, stop trying... This will timeout in 'failure_ttl' seconds
        raise AuthenticationException("Maximum password retry of {retry} was reached. "
                                      "This account is locked for the next {ttl} "
                                      "seconds...".format(retry=config.auth.internal.max_failures,
                                                          ttl=config.auth.internal.failure_ttl))

    try:
        validated_user, priv = validate_apikey(uname, apikey, storage)
        if validated_user:
            return validated_user, priv

        validated_user, priv = validate_oauth(uname, oauth_token)
        if not validated_user:
            validated_user, priv = validate_ldapuser(uname, password, storage)
        if not validated_user:
            validated_user, priv = validate_userpass(uname, password, storage)

        if validated_user:
            validate_2fa(validated_user, otp, state, webauthn_auth_resp, storage)
            return validated_user, priv

    except AuthenticationException:
        # Failure appended, push failure parameters
        auth_fail_queue.push({
            'remote_addr': req.remote_addr,
            'host': req.host,
            'full_path': req.full_path
        })

        raise

    raise AuthenticationException("None of the authentication methods succeeded")
Esempio n. 2
0
def validate_ldapuser(username, password, storage):
    if config.auth.ldap.enabled and username and password:
        ldap_obj = BasicLDAPWrapper(config.auth.ldap)
        ldap_info = ldap_obj.login(username, password)
        if ldap_info:
            cur_user = storage.user.get(username, as_obj=False) or {}

            # Make sure the user exists in AL and is in sync
            if (not cur_user and config.auth.ldap.auto_create) or (
                    cur_user and config.auth.ldap.auto_sync):
                u_classification = ldap_info['classification']

                # Normalize email address
                email = get_attribute(ldap_info, config.auth.ldap.email_field)
                if email is not None:
                    email = email.lower()
                    u_classification = get_dynamic_classification(
                        u_classification, email)

                # Generate user data from ldap
                data = dict(
                    classification=u_classification,
                    uname=username,
                    name=get_attribute(ldap_info, config.auth.ldap.name_field)
                    or username,
                    email=email,
                    password="******",
                    type=ldap_info['type'],
                    dn=ldap_info['dn'])

                # Save the user avatar avatar from ldap
                img_data = get_attribute(ldap_info,
                                         config.auth.ldap.image_field,
                                         safe=False)
                if img_data:
                    b64_img = base64.b64encode(img_data).decode()
                    avatar = f'data:image/{config.auth.ldap.image_format};base64,{b64_img}'
                    storage.user_avatar.save(username, avatar)

                # Save the updated user
                cur_user.update(data)
                storage.user.save(username, cur_user)

            if cur_user:
                return username, ["R", "W", "E"]
            else:
                raise AuthenticationException("User auto-creation is disabled")

        elif config.auth.internal.enabled:
            # Fallback to internal auth
            pass
        else:
            raise AuthenticationException("Wrong username or password")

    return None, None
Esempio n. 3
0
def login(uname, path=None):
    user = STORAGE.user.get(uname, as_obj=False)
    if not user:
        raise AuthenticationException("User %s does not exists" % uname)

    if not user['is_active']:
        raise AccessDeniedException("User %s is disabled" % uname)

    add_access_control(user)

    user['2fa_enabled'] = user.pop('otp_sk', None) is not None
    user['allow_2fa'] = config.auth.allow_2fa
    user['allow_apikeys'] = config.auth.allow_apikeys
    user['allow_security_tokens'] = config.auth.allow_security_tokens
    user['apikeys'] = list(user.get('apikeys', {}).keys())
    user['c12n_enforcing'] = Classification.enforce
    user['has_password'] = user.pop('password', "") != ""
    user['has_tos'] = config.ui.tos is not None and config.ui.tos != ""
    user['tos_auto_notify'] = config.ui.tos_lockout_notify is not None and config.ui.tos_lockout_notify != []
    user['internal_auth_enabled'] = config.auth.internal.enabled
    security_tokens = user.get('security_tokens', {})
    user['security_tokens'] = list(security_tokens.keys())
    user['security_token_enabled'] = len(security_tokens) != 0
    user['read_only'] = config.ui.read_only
    user['authenticated'] = True

    return user
Esempio n. 4
0
def validate_oauth(username, oauth_token):
    # This function identifies the user via the internal API key functionality
    #   NOTE: It is not recommended to overload this function but you can still do it
    if config.auth.oauth.enabled and oauth_token:
        if get_token_store(username).exist(oauth_token):
            return username, ["R", "W", "E"]

        raise AuthenticationException("Invalid token")

    return None, None
def validate_userpass(username, password, storage):
    # This function uses the internal authenticator to identify the user
    # You can overload this to pass username/password to an LDAP server for exemple
    if config.auth.internal.enabled and username and password:
        user = storage.user.get(username)
        if user:
            if verify_password(password, user.password):
                return username, ["R", "W", "E"]

        raise AuthenticationException("Wrong username or password")

    return None, None
Esempio n. 6
0
def validate_apikey(username, apikey, storage):
    # This function identifies the user via the internal API key functionality
    #   NOTE: It is not recommended to overload this function but you can still do it
    if config.auth.allow_apikeys and apikey:
        user_data = storage.user.get(username)
        if user_data:
            name, apikey_password = apikey.split(":", 1)
            key = user_data.apikeys.get(name, None)
            if key is not None:
                if verify_password(apikey_password, key.password):
                    return username, key.acl

        raise AuthenticationException("Invalid apikey")

    return None, None
def validate_2fa(username, otp_token, state, webauthn_auth_resp, storage):
    security_token_enabled = False
    otp_enabled = False
    security_token_error = False
    otp_error = False
    report_errors = False

    # Get user
    user_data = storage.user.get(username)

    # Test Security Tokens
    if config.auth.allow_security_tokens:
        security_tokens = user_data.security_tokens

        credentials = [AttestedCredentialData(websafe_decode(x)) for x in security_tokens.values()]
        if credentials:
            # Security tokens are enabled for user
            security_token_enabled = True
            report_errors = True
            if state and webauthn_auth_resp:
                data = cbor.decode(bytes(webauthn_auth_resp))
                credential_id = data['credentialId']
                client_data = ClientData(data['clientDataJSON'])
                auth_data = AuthenticatorData(data['authenticatorData'])
                signature = data['signature']

                try:
                    server.authenticate_complete(state, credentials, credential_id, client_data, auth_data, signature)
                    return
                except Exception:
                    security_token_error = True

    # Test OTP
    if config.auth.allow_2fa:
        otp_sk = user_data.otp_sk
        if otp_sk:
            # OTP is enabled for user
            otp_enabled = True
            report_errors = True
            if otp_token:
                if get_totp_token(otp_sk) != otp_token:
                    otp_error = True
                else:
                    return

    if report_errors:
        if security_token_error:
            # Wrong response to challenge
            raise AuthenticationException("Wrong Security Token")
        elif otp_error:
            # Wrong token provided
            raise AuthenticationException("Wrong OTP token")
        elif security_token_enabled:
            # No challenge/response provided and security tokens are enabled
            raise AuthenticationException("Wrong Security Token")
        elif otp_enabled:
            # No OTP Token provided and OTP is enabled
            raise AuthenticationException("Wrong OTP token")

        # This should never hit
        raise AuthenticationException("Unknown 2FA Authentication error")
Esempio n. 8
0
def login(**_):
    """
    Login the user onto the system
    
    Variables:
    None
    
    Arguments: 
    None
    
    Data Block:
    {
     "user": <UID>,
     "password": <ENCRYPTED_PASSWORD>,
     "otp": <OTP_TOKEN>,
     "apikey": <ENCRYPTED_APIKEY>,
     "webauthn_auth_resp": <RESPONSE_TO_CHALLENGE_FROM_WEBAUTHN>
    }

    Result example:
    {
     "username": <Logged in user>, # Username for the logged in user
     "privileges": ["R", "W"],     # Different privileges that the user will get for this session
     "session_duration": 60        # Time after which this session becomes invalid
                                   #   Note: The timer reset after each call
    }
    """
    data = request.json
    if not data:
        data = request.values

    user = data.get('user', None)
    password = data.get('password', None)
    apikey = data.get('apikey', None)
    webauthn_auth_resp = data.get('webauthn_auth_resp', None)
    oauth_provider = data.get('oauth_provider', None)
    oauth_token = data.get('oauth_token', None)

    if config.auth.oauth.enabled and oauth_provider:
        oauth = current_app.extensions.get('authlib.integrations.flask_client')
        provider = oauth.create_client(oauth_provider)

        if provider:
            redirect_uri = f'https://{request.host}/login.html?provider={oauth_provider}'
            return provider.authorize_redirect(redirect_uri=redirect_uri)

    try:
        otp = int(data.get('otp', 0) or 0)
    except Exception:
        raise AuthenticationException('Invalid OTP token')

    if (user and password) or (user and apikey) or (user and oauth_token):
        auth = {
            'username': user,
            'password': password,
            'otp': otp,
            'webauthn_auth_resp': webauthn_auth_resp,
            'apikey': apikey,
            'oauth_token': oauth_token
        }

        logged_in_uname = None
        ip = request.headers.get("X-Forwarded-For", request.remote_addr)
        try:
            logged_in_uname, priv = default_authenticator(
                auth, request, flsk_session, STORAGE)
            session_duration = config.ui.session_duration
            cur_time = now()
            xsrf_token = generate_random_secret()
            current_session = {
                'duration': session_duration,
                'ip': ip,
                'privileges': priv,
                'time': int(cur_time) - (int(cur_time) % session_duration),
                'user_agent': request.headers.get("User-Agent", None),
                'username': logged_in_uname,
                'xsrf_token': xsrf_token
            }
            session_id = hashlib.sha512(
                str(current_session).encode("UTF-8")).hexdigest()
            current_session['expire_at'] = cur_time + session_duration
            flsk_session['session_id'] = session_id
            KV_SESSION.add(session_id, current_session)
            return make_api_response(
                {
                    "username": logged_in_uname,
                    "privileges": priv,
                    "session_duration": session_duration
                },
                cookies={'XSRF-TOKEN': xsrf_token})
        except AuthenticationException as wpe:
            uname = auth.get('username', '(None)')
            LOGGER.warning(
                f"Authentication failure. (U:{uname} - IP:{ip}) [{wpe}]")
            return make_api_response("", err=str(wpe), status_code=401)
        finally:
            if logged_in_uname:
                LOGGER.info(
                    f"Login successful. (U:{logged_in_uname} - IP:{ip})")

    return make_api_response(
        "", "Not enough information to proceed with authentication", 401)
Esempio n. 9
0
def login(uname, path=None):
    user = STORAGE.user.get(uname, as_obj=False)
    if not user:
        raise AuthenticationException("User %s does not exists" % uname)

    if not user['is_active']:
        raise AccessDeniedException("User %s is disabled" % uname)

    add_access_control(user)

    if path:
        user["submenu"] = [{
            "icon": "glyphicon-user",
            "active": path.startswith("/account.html"),
            "link": "/account.html",
            "title": "Account"
        }]

        if not config.ui.read_only:
            user["submenu"].extend([{
                "icon":
                "glyphicon-tasks",
                "active":
                path.startswith("/dashboard.html"),
                "link":
                "/dashboard.html",
                "title":
                "Dashboard"
            }])

        user["submenu"].extend([{
            "icon": "glyphicon-cog",
            "active": path.startswith("/settings.html"),
            "link": "/settings.html",
            "title": "Settings"
        }, {
            "icon": "glyphicon-log-out",
            "active": path.startswith("/logout.html"),
            "link": "/logout.html",
            "title": "Sign out"
        }])

        if 'admin' in user['type']:
            user['menu_active'] = (path.startswith("/settings.html")
                                   or path.startswith("/account.html")
                                   or path.startswith("/admin/")
                                   or path.startswith("/dashboard.html")
                                   or path.startswith("/kibana-dash.html"))
            user["admin_menu"] = [{
                "icon":
                None,
                "active":
                path.startswith("/admin/errors.html"),
                "link":
                "/admin/errors.html",
                "title":
                "Errors viewer"
            }]
            if not config.ui.read_only:
                user["admin_menu"].extend([{
                    "icon":
                    None,
                    "active":
                    path.startswith("/admin/services.html"),
                    "link":
                    "/admin/services.html",
                    "title":
                    "Services"
                }])
            user["admin_menu"].extend([{
                "icon":
                None,
                "active":
                path.startswith("/admin/site_map.html"),
                "link":
                "/admin/site_map.html",
                "title":
                "Site Map"
            }, {
                "icon":
                None,
                "active":
                path.startswith("/admin/users.html"),
                "link":
                "/admin/users.html",
                "title":
                "Users"
            }])
        else:
            user['menu_active'] = (path.startswith("/settings.html")
                                   or path.startswith("/account.html")
                                   or path.startswith("/dashboard.html"))
            user["kibana_dashboards"] = []
            user["admin_menu"] = []

    user['2fa_enabled'] = user.pop('otp_sk', None) is not None
    user['allow_2fa'] = config.auth.allow_2fa
    user['allow_apikeys'] = config.auth.allow_apikeys
    user['allow_security_tokens'] = config.auth.allow_security_tokens
    user['apikeys'] = list(user.get('apikeys', {}).keys())
    user['c12n_enforcing'] = CLASSIFICATION.enforce
    user['has_password'] = user.pop('password', "") != ""
    user['has_tos'] = config.ui.tos is not None and config.ui.tos != ""
    user['tos_auto_notify'] = config.ui.tos_lockout_notify is not None and config.ui.tos_lockout_notify != []
    user['internal_auth_enabled'] = config.auth.internal.enabled
    security_tokens = user.get('security_tokens', {})
    user['security_tokens'] = list(security_tokens.keys())
    user['security_token_enabled'] = len(security_tokens) != 0
    user['read_only'] = config.ui.read_only
    user['authenticated'] = True

    return user