Example #1
0
    def _respond(_uid, _description):
        token = crypt.generate_auth_token(_uid)

        if target == 'dcos:authenticationresponse:html':
            # Upon entering the SSO flow, the user-agent stored this target to
            # indicate that it desires to retrieve a special, human-readable
            # HTML authentication response.
            generate_authtoken_html_response(token, resp)

        else:
            generate_authtoken_json_response(token,
                                             req,
                                             resp,
                                             _uid,
                                             _description,
                                             is_remote=True)

            # Perform the redirect.
            if target is None:
                # `target` was not defined by user-agent while entering the
                # SSO flow. Redirect to root.
                raise falcon.HTTPSeeOther('/')

            else:
                # `target` was defined by user-agent upon entering the flow.
                # Redirect to it w/o further validation (although it may not
                # even be a valid URL).
                raise falcon.HTTPSeeOther(target)
Example #2
0
    def _login_service(self, req, resp, user, login_params):

        self.log.debug('Service login token: `%s`',
                       login_params.service_login_token)

        service_pubkey = user.pubkey
        self.log.info("Service login: validate service login JWT using "
                      "the service's public key")
        try:
            payload = jwt.decode(jwt=login_params.service_login_token,
                                 key=service_pubkey,
                                 algorithms='RS256')
        except (jwt.InvalidTokenError, ValueError) as e:
            self.log.info('Service login for `%s`: invalid token `%s`: %s',
                          login_params.uid, login_params.service_login_token,
                          e)
            self._raise_local_nonauth_error()

        if 'uid' not in payload:
            self.log.info('Service login: token misses `uid` claim')
            self._raise_local_nonauth_error()

        if payload['uid'] != login_params.uid:
            self.log.info('Service login: `uid` claim mismatch')
            self._raise_local_nonauth_error()

        # Emit warning log messages about insecurely constructed login tokens.
        if 'exp' not in payload:
            self.log.warning(
                'long-lived service login token (no exp claim) for uid `%s`',
                login_params.uid)

        elif payload['exp'] > time.time() + 600:
            self.log.warning(
                'long-lived service login token (> 10 minutes) for uid `%s`',
                login_params.uid)

        # In the special case of 'service user accounts', interpret `exp`
        # parameter (token expiration time) from request, if given (this is a
        # private interface, must not be used by relying parties. See crypt.py
        # for more commentary.
        authtoken = crypt.generate_auth_token(login_params.uid,
                                              login_params.exp)

        generate_authtoken_json_response(authtoken, req, resp,
                                         login_params.uid, user.description)
Example #3
0
    def _login_local_regular_user(self, req, resp, user, login_params):
        pw_hashed = user.passwordhash
        self.log.debug('User login: Validate password.')

        if not crypt.verify_password(login_params.pw, pw_hashed):
            self.log.info(
                str(
                    SecurityEventAuditLogEntry(
                        req, {
                            'action': 'password-login',
                            'result': 'deny',
                            'reason': 'invalid password provided',
                            'uid': login_params.uid,
                        })))
            self._raise_local_nonauth_error()

        authtoken = crypt.generate_auth_token(login_params.uid)
        generate_authtoken_json_response(authtoken, req, resp,
                                         login_params.uid, user.description)
Example #4
0
    def _oidc_id_token_login(self, req, resp, oidc_id_token):

        # Hand off the ID Token validation business logic to the
        # `oidcidtokenlogin` module.
        issuer, email = oidcidtokenlogin.verify_id_token_or_terminate(
            req, resp, oidc_id_token)

        uid = sanitize_remote_uid(email)

        regular_user_count = dbsession.query(User).filter_by(
            utype=UserType.regular).count()

        if regular_user_count == 0:

            log.info('There is no regular user account yet. Create one.')

            # Add user to database. Rely on that we have just checked that no
            # user is there, i.e. a conflict is unexpected. Technically, there
            # is race condition and if a separate party was faster adding the
            # same user, `import_remote_user()` below could raise
            # `bouncer.app.exceptions.EntityExists`. In practice, that requires
            # the same user to log in multiple times via the external login
            # method on a sub-second timescale through different Bouncer
            # instances. Leave this unhandled (one request will succeed, the
            # others will see a 500 Internal Server Error response). Store
            # issuer as provider_id so that we keep record of which identity
            # provider precisely emitted the data.
            descr = f'User added through OIDC ID Token login. Issuer: {issuer}'
            user = import_remote_user(uid=uid,
                                      description=descr,
                                      provider_type=ProviderType.oidc,
                                      provider_id=issuer)

        else:
            try:
                user = User.get(uid)
            except bouncer.app.exceptions.EntityNotFound:
                log.info(
                    "I know %s user(s), but `%s` ain't one of them. Emit 401.",
                    regular_user_count, uid)
                # Note(JP): 403 is more appropriate because this is effectively
                # our coarse-grained authorization mechanism hitting in, but 401
                # I think should be maintained for legacy reasons.
                raise falcon.HTTPUnauthorized(
                    description='ID Token login failed: user unknown', )

            # Make sure that provider ID and type are matching. That is if a
            # user is known in the database with the same uid as presented by
            # the current ID Token but stemming from a different provider type
            # or from a different issuer than recorded in the database then
            # reject the login request.
            if user.provider_type != ProviderType.oidc:
                raise falcon.HTTPUnauthorized(
                    description='ID Token login failed: provider type mismatch',
                )
            if user.provider_id != issuer:
                raise falcon.HTTPUnauthorized(
                    description='ID Token login failed: provider ID mismatch',
                )

        generate_authtoken_json_response(crypt.generate_auth_token(user.uid),
                                         req, resp, user.uid, user.description)