Ejemplo n.º 1
0
class Authentication(object):
    def __init__(self, portal_cookie, token_name, workflow):
        self.token_name = token_name
        self.redis_base = REDISBase()
        self.redis_portal_session = REDISPortalSession(self.redis_base,
                                                       portal_cookie)
        self.workflow = workflow

        self.backend_id = self.authenticated_on_backend()
        self.redis_oauth2_session = REDISOauth2Session(
            self.redis_base,
            self.redis_portal_session.get_oauth2_token(self.backend_id))

        if not self.workflow.authentication:
            raise RedirectionNeededError(
                "Workflow '{}' does not need authentication".format(
                    self.workflow.name), self.get_redirect_url())
        self.credentials = ["", ""]

    def is_authenticated(self):
        if self.redis_portal_session.exists(
        ) and self.redis_portal_session.authenticated_app(self.workflow.id):
            # If user authenticated, retrieve its login
            self.credentials[0] = self.redis_portal_session.get_login(
                str(self.backend_id))
            return True
        return False

    def double_authentication_required(self):
        return self.workflow.authentication.otp_repository and \
            not self.redis_portal_session.is_double_authenticated(self.workflow.authentication.otp_repository.id)

    def authenticated_on_backend(self):
        backend_list = list(
            self.workflow.authentication.repositories_fallback.all())
        backend_list.append(self.workflow.authentication.repository)
        for backend in backend_list:
            if self.redis_portal_session.authenticated_backend(
                    backend.id) == '1':
                return str(backend.id)
        return ""

    def authenticate_sso_acls(self):
        backend_list = list(
            self.workflow.authentication.repositories_fallback.all())
        backend_list.append(self.workflow.authentication.repository)
        e, login = None, ""
        for backend in backend_list:
            if self.redis_portal_session.authenticated_backend(
                    backend.id) == '1':
                # The user is authenticated on backend, but he's not necessarily authorized on the app
                # The ACL is only supported by LDAP
                if backend.subtype == "LDAP":
                    # Retrieve needed infos in redis
                    login = self.redis_portal_session.keys['login_' +
                                                           str(backend.id)]
                    app_id = self.redis_portal_session.keys['app_id_' +
                                                            str(backend.id)]
                    password = self.redis_portal_session.getAutologonPassword(
                        app_id, str(backend.id), login)
                    # And try to re-authenticate user to verify credentials and ACLs
                    try:
                        backend.authenticate(
                            login,
                            password,  # acls=self.workflow.access_control_list,  # FIXME : Implement LDAP ACL in AccessControl model
                            logger=logger)
                        logger.info(
                            "User '{}' successfully re-authenticated on {} for SSO needs"
                            .format(login, backend.repo_name))
                    except Exception as e:
                        logger.error(
                            "Error while trying to re-authenticate user '{}' on '{}' for SSO needs : {}"
                            .format(login, backend.repo_name, e))
                        continue
                return str(backend.id)
        if login and e:
            raise e
        return ""

    def set_authentication_params(self, repo, authentication_results):
        if authentication_results:
            result = {}
            self.backend_id = str(repo.id)

            if isinstance(authentication_results, User):
                result = {
                    'data': {
                        'password_expired': False,
                        'account_locked':
                        (not authentication_results.is_active),
                        'user_email': authentication_results.email
                    },
                    'backend': repo
                }
                """ OAuth2 enabled in any case """
                result['data']['oauth2'] = {
                    'scope': '{}',
                    'token_return_type': 'both',
                    'token_ttl': self.workflow.authentication.auth_timeout
                }
            logger.debug(
                "AUTH::set_authentication_params: Authentication results : {}".
                format(authentication_results))
            return result
        else:
            raise AuthenticationError(
                "AUTH::set_authentication_params: Authentication results is empty : '{}' "
                "for username '{}'".format(authentication_results,
                                           self.credentials[0]))

    def authenticate_on_backend(self, backend):
        if backend.subtype == "internal":
            return self.set_authentication_params(
                backend,
                backend.authenticate(self.credentials[0], self.credentials[1]))
        else:
            return backend.authenticate(
                self.credentials[0],
                self.credentials[1],
                # FIXME : ACLs
                # acls=self.workflow.access_control_list,
                logger=logger)

    def authenticate(self, request):
        e = None
        backend = self.workflow.authentication.repository
        try:
            authentication_results = self.authenticate_on_backend(backend)
            logger.info(
                "AUTH::authenticate: User '{}' successfully authenticated on backend '{}'"
                .format(self.credentials[0], backend))
            self.backend_id = str(backend.id)
            return authentication_results

        except (AuthenticationError, ACLError, DBAPIError, PyMongoError,
                LDAPError) as e:
            logger.error(
                "AUTH::authenticate: Authentication failure for username '{}' on primary backend '{}' : '{}'"
                .format(self.credentials[0], str(backend), str(e)))
            logger.exception(e)
            for fallback_backend in self.workflow.authentication.repositories_fallback.all(
            ):
                try:
                    authentication_results = self.authenticate_on_backend(
                        backend)
                    self.backend_id = str(fallback_backend.id)
                    logger.info(
                        "AUTH::authenticate: User '{}' successfully authenticated on fallback backend '{}'"
                        .format(self.credentials[0], fallback_backend))
                    return authentication_results

                except (AuthenticationError, ACLError, DBAPIError,
                        PyMongoError, LDAPError) as e:
                    logger.error(
                        "AUTH::authenticate: Authentication failure for username '{}' on fallback backend '{}'"
                        " : '{}'".format(self.credentials[0],
                                         str(fallback_backend), str(e)))
                    logger.exception(e)
                    continue
            raise e or AuthenticationError

    def register_user(self, authentication_results):
        oauth2_token = None
        # No OAuth2 disabled
        # if self.application.enable_oauth2:
        timeout = self.workflow.authentication.auth_timeout
        oauth2_token = Uuid4().generate()
        self.redis_oauth2_session = REDISOauth2Session(
            self.redis_base, "oauth2_" + oauth2_token)
        if authentication_results['data'].get('oauth2', None):
            self.redis_oauth2_session.register_authentication(
                authentication_results['data']['oauth2'],
                authentication_results['data']['oauth2']['token_ttl'])
        else:
            self.redis_oauth2_session.register_authentication(
                {
                    'scope': '{}',
                    'token_return_type': 'both',
                    'token_ttl': timeout
                }, timeout)
        logger.debug(
            "AUTH::register_user: Redis oauth2 session successfully written in Redis"
        )

        portal_cookie = self.redis_portal_session.register_authentication(
            str(self.workflow.id), str(self.workflow.name),
            str(self.backend_id), self.workflow.get_redirect_uri(),
            self.workflow.authentication.otp_repository, self.credentials[0],
            self.credentials[1], oauth2_token, authentication_results['data'],
            timeout)
        logger.debug(
            "AUTH::register_user: Authentication results successfully written in Redis portal session"
        )

        return portal_cookie, oauth2_token

    def register_sso(self, backend_id):
        username = self.redis_portal_session.keys['login_' + backend_id]
        oauth2_token = self.redis_portal_session.keys['oauth2_' + backend_id]
        self.redis_oauth2_session = REDISOauth2Session(
            self.redis_portal_session.handler, "oauth2_" + oauth2_token)
        logger.debug(
            "AUTH::register_sso: Redis oauth2 session successfully retrieven")
        password = self.redis_portal_session.getAutologonPassword(
            self.workflow.id, backend_id, username)
        logger.debug(
            "AUTH::register_sso: Password successfully retrieven from Redis portal session"
        )
        portal_cookie = self.redis_portal_session.register_sso(
            self.workflow.authentication.auth_timeout, backend_id,
            str(self.workflow.id), str(self.workflow.get_redirect_uri()),
            username, oauth2_token)
        logger.debug(
            "AUTH::register_sso: SSO informations successfully written in Redis for user {}"
            .format(username))
        self.credentials = [username, password]
        if self.double_authentication_required():
            self.redis_portal_session.register_doubleauthentication(
                self.workflow.id, self.workflow.authentication.otp_repository)
            logger.debug(
                "AUTH::register_sso: DoubleAuthentication required : "
                "successfully written in Redis for user '{}'".format(username))
        return portal_cookie, oauth2_token

    def get_redirect_url(self):
        try:
            return self.workflow.get_redirect_uri() or \
                self.redis_portal_session.keys.get('url_{}'.format(self.workflow.id), None)
        except:
            return self.redis_portal_session.keys.get('url_{}'.format(self.workflow.id), None) or \
                self.workflow.get_redirect_uri()

    def get_url_portal(self):
        try:
            # FIXME : auth_portal attribute ?
            return self.workflow.auth_portal or self.workflow.get_redirect_uri(
            )
        except:
            return self.workflow.get_redirect_uri()

    def get_redirect_url_domain(self):
        return split_domain(self.get_redirect_url())

    def get_credentials(self, request):
        if not self.credentials[0]:
            try:
                self.retrieve_credentials(request)
            except:
                self.credentials[0] = self.redis_portal_session.get_login(
                    self.backend_id)
        logger.debug(
            "AUTH::get_credentials: User's login successfully retrieven from Redis session : '{}'"
            .format(self.credentials[0]))
        if not self.credentials[1]:
            try:
                self.retrieve_credentials(request)
            except:
                if not self.backend_id:
                    self.backend_id = self.authenticated_on_backend()
                self.credentials[
                    1] = self.redis_portal_session.getAutologonPassword(
                        str(self.workflow.id), self.backend_id,
                        self.credentials[0])
        logger.debug(
            "AUTH::get_credentials: User's password successfully retrieven/decrypted from Redis session"
        )

    def ask_learning_credentials(self, **kwargs):
        # FIXME : workflow.auth_portal ?
        response = learning_authentication_response(
            kwargs.get('request'),
            self.workflow.template.id,
            # FIXME : auth_portal ?
            # self.workflow.auth_portal or
            self.workflow.get_redirect_uri(),
            "None",
            kwargs.get('fields'),
            error=kwargs.get('error', None))

        portal_cookie_name = kwargs.get('portal_cookie_name', None)
        if portal_cookie_name:
            response.set_cookie(
                portal_cookie_name,
                self.redis_portal_session.key,
                domain=self.get_redirect_url_domain(),
                httponly=True,
                secure=self.get_redirect_url().startswith('https'))

        return response
Ejemplo n.º 2
0
class OAUTH2Authentication(Authentication):
    def __init__(self, workflow_id):
        assert (workflow_id)
        self.application = Workflow.objects.get(pk=workflow_id)
        self.redis_base = REDISBase()

    def retrieve_credentials(self, username, password, portal_cookie):
        assert (username)
        assert (password)
        self.credentials = [username, password]
        self.redis_portal_session = REDISPortalSession(self.redis_base,
                                                       portal_cookie)

    def authenticate(self):
        self.oauth2_token = self.redis_portal_session.get_oauth2_token(
            self.authenticated_on_backend())
        if not self.oauth2_token:
            authentication_results = super().authenticate(None)
            logger.debug(
                "OAUTH2_AUTH::authenticate: Oauth2 attributes : {}".format(
                    str(authentication_results['data'])))
            if authentication_results['data'].get('oauth2', None) is not None:
                self.oauth2_token = Uuid4().generate()
                self.register_authentication(
                    authentication_results['data']['oauth2'])
                authentication_results = authentication_results['data'][
                    'oauth2']
            elif self.application.enable_oauth2:
                authentication_results = {
                    'token_return_type': 'both',
                    'token_ttl': self.application.auth_timeout,
                    'scope': '{}'
                }
                self.oauth2_token = Uuid4().generate()
                self.register_authentication(authentication_results)
            else:
                raise AuthenticationError(
                    "OAUTH2_AUTH::authenticate: OAuth2 is not enabled on this app nor on this repository"
                )
        else:
            # REPLACE CREDENTIAL 'user"
            self.redis_oauth2_session = REDISOauth2Session(
                self.redis_base, "oauth2_" + self.oauth2_token)
            authentication_results = self.redis_oauth2_session.keys
        return authentication_results

    def register_authentication(self, authentication_results):
        self.redis_oauth2_session = REDISOauth2Session(
            self.redis_base, "oauth2_" + self.oauth2_token)
        self.redis_oauth2_session.register_authentication(
            authentication_results, authentication_results['token_ttl'])
        logger.debug(
            "AUTH::register_user: Redis oauth2 session successfully written in Redis"
        )

    def generate_response(self, authentication_results):
        body = {"token_type": "Bearer", "access_token": self.oauth2_token}

        if authentication_results.get('token_return_type') == 'header':
            response = HttpResponse()
            response['Authorization'] = body["token_type"] + " " + body[
                "access_token"]

        elif authentication_results.get('token_return_type') == 'json':
            response = JsonResponse(body)

        elif authentication_results.get('token_return_type') == 'both':
            response = JsonResponse(body)
            response['Authorization'] = body["token_type"] + " " + body[
                "access_token"]

        return response
Ejemplo n.º 3
0
class Authentication(object):
    def __init__(self, token_name, redis_token, app_cookie, portal_cookie):
        self.token_name = token_name
        self.anonymous_token = redis_token
        self.redis_base = REDISBase()
        self.redis_session = REDISAppSession(self.redis_base,
                                             token=redis_token,
                                             cookie=app_cookie)
        self.redis_portal_session = REDISPortalSession(self.redis_base,
                                                       portal_cookie)
        self.application = Application.objects.with_id(
            ObjectId(self.redis_session.keys['application_id']))
        self.backend_id = self.authenticated_on_backend()
        self.redis_oauth2_session = REDISOauth2Session(
            self.redis_base,
            self.redis_portal_session.get_oauth2_token(self.backend_id))

        if not self.application.need_auth:
            raise RedirectionNeededError(
                "Application '{}' does not need authentication".format(
                    self.application.name), self.get_redirect_url())
        self.credentials = ["", ""]

    def is_authenticated(self):
        if self.redis_session.is_authenticated(
        ) and self.redis_portal_session.exists():
            # If user authenticated, retrieve its login
            self.credentials[0] = self.redis_session.get_login()
            return True
        return False

    def double_authentication_required(self):
        return (self.application.otp_repository
                and not self.redis_session.is_double_authenticated())

    def authenticated_on_backend(self):
        backend_list = self.application.getAuthBackendFallback()
        backend_list.append(self.application.getAuthBackend())
        for backend in backend_list:
            if self.redis_portal_session.authenticated_backend(
                    backend.id) == '1':
                return str(backend.id)
        return ""

    def authenticate_on_backend(self):
        backend_list = self.application.getAuthBackendFallback()
        backend_list.append(self.application.getAuthBackend())
        e, login = None, ""
        for backend in backend_list:
            if self.redis_portal_session.authenticated_backend(
                    backend.id) == '1':
                # The user is authenticated on backend, but he's not necessarily authorized on the app
                # The ACL is only supported by LDAP
                if isinstance(backend, LDAPRepository):
                    # Retrieve needed infos in redis
                    login = self.redis_portal_session.keys['login_' +
                                                           str(backend.id)]
                    app_id = self.redis_portal_session.keys['app_id_' +
                                                            str(backend.id)]
                    password = self.redis_portal_session.getAutologonPassword(
                        app_id, str(backend.id), login)
                    # And try to re-authenticate user to verify credentials and ACLs
                    try:
                        backend.get_backend().authenticate(
                            login,
                            password,
                            acls=self.application.access_mode,
                            logger=logger)
                        logger.info(
                            "User '{}' successfully re-authenticated on {} for SSO needs"
                            .format(login, backend.repo_name))
                    except Exception as e:
                        logger.error(
                            "Error while trying to re-authenticate user '{}' on '{}' for SSO needs : {}"
                            .format(login, backend.repo_name, e))
                        continue
                return str(backend.id)
        if login and e:
            raise e
        return ""

    def set_authentication_params(self, repo, authentication_results):
        if authentication_results:
            self.backend_id = str(repo.id)

            if isinstance(authentication_results, User):
                result = {
                    'data': {
                        'password_expired': False,
                        'account_locked':
                        (not authentication_results.is_active),
                        'user_email': authentication_results.email
                    },
                    'backend': repo
                }
                if self.application.enable_oauth2:
                    result['data']['oauth2'] = {
                        'scope': '{}',
                        'token_return_type': 'both',
                        'token_ttl': self.application.auth_timeout
                    }
            logger.debug(
                "AUTH::set_authentication_params: Authentication results : {}".
                format(authentication_results))
            return result
        else:
            raise AuthenticationError(
                "AUTH::set_authentication_params: Authentication results is empty : '{}' for username '{}'"
                .format(authentication_results, self.credentials[0]))

    def authenticate(self, request):
        error = None

        login_user = self.credentials[0]
        login_password = self.credentials[1]

        login_pattern = '^[A-Za-z0-9@\.\-_!\?\$]+$'
        if not re.match(login_pattern, login_user):
            logger.info(
                "AUTH::authenticate: USERNAME INJECTION : '{}' !!!".format(
                    login_user))
            login_user = ""

        try:
            backend = self.application.getAuthBackend()
            if isinstance(backend.get_backend(), MongoEngineBackend):
                authentication_results = self.set_authentication_params(
                    backend,
                    backend.get_backend().authenticate(login_user,
                                                       login_password))
            else:
                authentication_results = backend.get_backend().authenticate(
                    login_user,
                    login_password,
                    acls=self.application.access_mode,
                    logger=logger)
            logger.info(
                "AUTH::authenticate: User '{}' successfully authenticated on backend '{}'"
                .format(login_user, backend))
            self.backend_id = str(backend.id)
            return authentication_results

        except (AuthenticationError, ACLError, DBAPIError, PyMongoError,
                LDAPError) as e:
            error = e
            logger.error(
                "AUTH::authenticate: Authentication failure for username '{}' on primary backend '{}' : '{}'"
                .format(login_user, str(backend), str(e)))
            for fallback_backend in self.application.getAuthBackendFallback():
                try:
                    if isinstance(fallback_backend.get_backend(),
                                  MongoEngineBackend):
                        authentication_results = self.set_authentication_params(
                            fallback_backend,
                            fallback_backend.get_backend().authenticate(
                                login_user, login_password))
                    else:
                        authentication_results = fallback_backend.get_backend(
                        ).authenticate(login_user,
                                       login_password,
                                       acls=self.application.access_mode,
                                       logger=logger)
                    self.backend_id = str(fallback_backend.id)
                    logger.info(
                        "AUTH::authenticate: User '{}' successfully authenticated on fallback backend '{}'"
                        .format(login_user, fallback_backend))
                    return authentication_results

                except (AuthenticationError, ACLError, DBAPIError,
                        PyMongoError, LDAPError) as e:
                    error = e
                    logger.error(
                        "AUTH::authenticate: Authentication failure for username '{}' on fallback backend '{}' : '{}'"
                        .format(login_user, str(fallback_backend), str(e)))
                    continue

        raise error or AuthenticationError

    def register_user(self, authentication_results):
        app_cookie = self.redis_session.register_authentication(
            str(self.application.id), self.credentials[0],
            self.application.auth_timeout)
        logger.debug(
            "AUTH::register_user: Authentication results successfully written in Redis session"
        )
        oauth2_token = None
        if self.application.enable_oauth2:
            oauth2_token = Uuid4().generate()
            self.redis_oauth2_session = REDISOauth2Session(
                self.redis_base, "oauth2_" + oauth2_token)
            if authentication_results['data'].get('oauth2', None):
                self.redis_oauth2_session.register_authentication(
                    authentication_results['data']['oauth2'],
                    authentication_results['data']['oauth2']['token_ttl'])
            else:
                self.redis_oauth2_session.register_authentication(
                    {
                        'scope': '{}',
                        'token_return_type': 'both',
                        'token_ttl': self.application.auth_timeout
                    }, self.application.auth_timeout)
            logger.debug(
                "AUTH::register_user: Redis oauth2 session successfully written in Redis"
            )
        portal_cookie = self.redis_portal_session.register_authentication(
            str(self.application.id), str(self.application.name),
            str(self.backend_id), self.application.get_redirect_uri(),
            self.application.otp_repository, self.credentials[0],
            self.credentials[1], oauth2_token, app_cookie,
            authentication_results['data'], self.application.auth_timeout)
        logger.debug(
            "AUTH::register_user: Authentication results successfully written in Redis portal session"
        )
        return app_cookie, portal_cookie, oauth2_token

    def register_sso(self, backend_id):
        username = self.redis_portal_session.keys['login_' + backend_id]
        saved_app_id = self.redis_portal_session.keys['app_id_' + backend_id]
        oauth2_token = None
        try:
            # TODO: Create Oauth2 if does not exists and oauth2 enabled
            oauth2_token = self.redis_portal_session.keys['oauth2_' +
                                                          backend_id]
            self.redis_oauth2_session = REDISOauth2Session(
                self.redis_session.handler, "oauth2_" + oauth2_token)
            logger.debug(
                "AUTH::register_sso: Redis oauth2 session successfully retrieven"
            )
        except Exception as e:
            logger.error(
                "Unable to retrieve Oauth2 token for user '{}', it may be possible if Oauth2 is not activated on repository"
                .format(username))
        password = self.redis_portal_session.getAutologonPassword(
            saved_app_id, backend_id, username)
        logger.debug(
            "AUTH::register_sso: Password successfully retrieven from Redis portal session"
        )
        app_cookie = self.redis_session.register_authentication(
            str(self.application.id), username, self.application.auth_timeout)
        portal_cookie = self.redis_portal_session.register_sso(
            self.redis_session.key, self.application.auth_timeout, backend_id,
            str(self.application.id), str(self.application.get_redirect_uri()),
            username, oauth2_token)
        logger.debug(
            "AUTH::register_sso: SSO informations successfully written in Redis for user '{}'"
            .format(username))
        self.credentials = [username, password]
        if self.double_authentication_required():
            self.redis_session.register_doubleauthentication()
            logger.debug(
                "AUTH::register_sso: DoubleAuthentication required : successfully written in Redis for user '{}'"
                .format(username))
        return app_cookie, portal_cookie, oauth2_token

    def get_redirect_url(self):
        try:
            return self.application.redirect_uri or self.redis_session.keys.get(
                'url', None)
        except:
            return self.redis_session.keys.get(
                'url', None) or self.application.get_redirect_uri()

    def get_url_portal(self):
        try:
            return self.application.auth_portal or self.application.get_redirect_uri(
            )
        except:
            return self.application.get_redirect_uri()

    def get_redirect_url_domain(self):
        return split_domain(self.get_redirect_url())

    def get_credentials(self, request):
        if not self.credentials[0]:
            try:
                self.retrieve_credentials(request)
            except:
                self.credentials[0] = self.redis_session.get_login()
        logger.debug(
            "AUTH::get_credentials: User's login successfully retrieven from Redis session : '{}'"
            .format(self.credentials[0]))
        if not self.credentials[1]:
            try:
                self.retrieve_credentials(request)
            except:
                if not self.backend_id:
                    self.backend_id = self.authenticated_on_backend()
                self.credentials[
                    1] = self.redis_portal_session.getAutologonPassword(
                        str(self.application.id), self.backend_id,
                        self.credentials[0])
        logger.debug(
            "AUTH::get_credentials: User's password successfully retrieven/decrypted from Redis session"
        )

    def ask_learning_credentials(self, **kwargs):
        response = learning_authentication_response(
            kwargs.get('request'),
            self.application.template,
            self.application.auth_portal
            or self.application.get_redirect_uri(),
            self.token_name,
            "None",
            kwargs.get('fields'),
            error=kwargs.get('error', None))

        portal_cookie_name = kwargs.get('portal_cookie_name', None)
        if portal_cookie_name:
            response.set_cookie(
                portal_cookie_name,
                self.redis_portal_session.key,
                domain=self.get_redirect_url_domain(),
                httponly=True,
                secure=self.get_redirect_url().startswith('https'))

        return response