Example #1
0
    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 __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 = ["", ""]
Example #3
0
    def retrieve_credentials(self, request):
        """ Get portal_cookie name and application_cookie name from cluster """
        portal_cookie_name = self.cluster.getPortalCookie()
        """ Get portal cookie value (if exists) """
        portal_cookie = request.COOKIES.get(portal_cookie_name, None)
        assert portal_cookie, "SELF:: Portal cookie not found"

        self.redis_portal_session = REDISPortalSession(self.redis_base,
                                                       portal_cookie)
        assert self.redis_portal_session.exists(
        ), "SELF:: Invalid portal session"

        # And get username from redis_portal_session
        self.username = self.redis_portal_session.keys.get(
            'login_' + str(self.application.getAuthBackend().id)
        ) or (([
            self.redis_portal_session.keys.get('login_' +
                                               str(backend_fallback.id))
            for backend_fallback in self.application.getAuthBackendFallback()
            if self.redis_portal_session.keys.get('login_' +
                                                  str(backend_fallback.id))
        ] or [None])[0])
        assert self.username, "Unable to find username in portal session !"
Example #4
0
class SELFService(object):
    def __init__(self, app_id, token_name):
        # DoesNotExists
        self.application = Application.objects.with_id(ObjectId(app_id))
        self.redis_base = REDISBase()
        self.token_name = token_name
        self.cluster = Cluster.objects.get()

        if not self.application.need_auth:
            raise RedirectionNeededError(
                "Application '{}' does not need authentication".format(
                    self.application.name),
                self.application.get_redirect_uri())

    def get_username_by_email(self, backend, fallback_backends, email):
        e = None
        try:
            if isinstance(backend.get_backend(), MongoEngineBackend):
                user = User.objects.get(
                    email=email)  # TODO : Except User.doesNotExists
                result = {'user': user, 'backend': backend}
            else:
                result = backend.get_backend().search_user_by_email(email)
            logger.info(
                "SELF::get_user_by_email: User '{}' successfully found on backend '{}'"
                .format(result['user'], result['backend'].repository))
            self.backend_id = str(backend.id)  # TODO : Needed ?????
            return result

        except Exception as e:
            logger.error(
                "SELF::get_user_by_email: Failed to find email '{}' on primary backend '{}' : '{}'"
                .format(email, str(backend), str(e)))
            logger.exception(e)
            for fallback_backend in fallback_backends:
                try:
                    if isinstance(fallback_backend.get_backend(),
                                  MongoEngineBackend):
                        user = User.objects.get(
                            email=email)  # TODO : Except User.doesNotExists
                        result = {'user': user, 'backend': backend}
                    else:
                        result = fallback_backend.get_backend(
                        ).search_user_by_email(email)
                    logger.info(
                        "SELF::get_user_by_email: User '{}' successfully found on backend '{}'"
                        .format(result['user'], result['backend']))
                    self.backend_id = str(fallback_backend.id)
                    return result

                except Exception as e:
                    logger.error(
                        "SELF::get_user_by_email: Failed to find email '{}' on primary backend '{}' : '{}'"
                        .format(email, str(backend), str(e)))
                    logger.exception(e)

        raise e or AuthenticationError

    def set_authentication_params(self, repo, authentication_results,
                                  username):
        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
                }

            logger.debug(
                "AUTH::set_authentication_params: Authentication results : {}".
                format(authentication_results))
            return result
        else:
            raise AuthenticationError(
                "SELF::authenticate: Authentication result is empty for username '{}'"
                .format(username))

    def authenticate_on_backend(self, backend, username, password):

        if isinstance(backend.get_backend(), MongoEngineBackend):
            authentication_results = self.set_authentication_params(
                backend,
                backend.get_backend().authenticate(username, password),
                username)
        else:
            authentication_results = backend.get_backend().authenticate(
                username,
                password,
                acls=self.application.access_mode,
                logger=logger)

        logger.info(
            "AUTH::authenticate: User '{}' successfully authenticated on backend '{}'"
            .format(username, backend))
        self.backend_id = str(backend.id)

        return authentication_results

    def retrieve_credentials(self, request):
        """ Get portal_cookie name and application_cookie name from cluster """
        portal_cookie_name = self.cluster.getPortalCookie()
        """ Get portal cookie value (if exists) """
        portal_cookie = request.COOKIES.get(portal_cookie_name, None)
        assert portal_cookie, "SELF:: Portal cookie not found"

        self.redis_portal_session = REDISPortalSession(self.redis_base,
                                                       portal_cookie)
        assert self.redis_portal_session.exists(
        ), "SELF:: Invalid portal session"

        # And get username from redis_portal_session
        self.username = self.redis_portal_session.keys.get(
            'login_' + str(self.application.getAuthBackend().id)
        ) or (([
            self.redis_portal_session.keys.get('login_' +
                                               str(backend_fallback.id))
            for backend_fallback in self.application.getAuthBackendFallback()
            if self.redis_portal_session.keys.get('login_' +
                                                  str(backend_fallback.id))
        ] or [None])[0])
        assert self.username, "Unable to find username in portal session !"

    def perform_action(self):
        # Retrieve all the hkeys in redis matching "backend_(app_id) = (backend_id)"
        # with portal_cookie
        backends_apps = self.redis_portal_session.get_auth_backends()

        backends = list()
        apps = list()
        for key, item in backends_apps.items():
            # Extract the id of the application in "backend_(id)"
            app = key[8:]
            if app not in apps:
                apps.append(app)
            # The item is the backend
            if item not in backends:
                backends.append(item)

        logger.debug(
            "User successfully authenticated on following apps : '{}'".format(
                apps))
        logger.debug(
            "User successfully authenticated on following backends : '{}'".
            format(backends))

        # Retrieve all the apps which need auth AND which the backend or backend_fallback is in common with the backend the user is logged on
        # And retrieve all the apps that does not need authentication
        Query = (Q(need_auth=True) &
                 (Q(auth_backend__in=backends) |
                  Q(auth_backend_fallbacks__in=backends))) | Q(need_auth=False)
        auth_apps = Application.objects(Query).only('name', 'public_name',
                                                    'public_dir', 'id', 'type',
                                                    'listeners', 'need_auth')

        final_apps = list()
        for app in auth_apps:
            final_apps.append({
                'name': app.name,
                'url': str(app.get_redirect_uri()),
                'status': app.need_auth and str(app.id) in apps
            })

        return final_apps

    def main_response(self, request, app_list, error=None):
        return self_message_main(request, self.application, self.token_name,
                                 app_list, self.username, error)

    def message_response(self, message):
        return self_message_response(self.application, self.token_name,
                                     message)

    def ask_credentials_response(self, request, action, error_msg):
        return self_ask_passwords(
            request, self.application, self.token_name,
            request.GET.get("rdm", None) or request.POST.get('rdm', None),
            action, error_msg)
Example #5
0
    def retrieve_credentials(self, request):
        """ We may have a password reset token in URI """
        rdm = request.GET.get("rdm", None) or request.POST.get('rdm', None)

        if not rdm:
            super(SELFServiceChange, self).retrieve_credentials(request)

        old_password = None  # None if rdm
        new_passwd = request.POST['password_1']
        new_passwd_cfrm = request.POST['password_2']
        if new_passwd != new_passwd_cfrm:
            raise PasswordMatchError("Password and confirmation mismatches")

        auth_backend = self.application.getAuthBackend()
        auth_backend_fallbacks = self.application.getAuthBackendFallback()

        if rdm:
            assert re_match("^[0-9a-f-]+$",
                            rdm), "PORTAL::self: Injection attempt on 'rdm'"

            email = self.redis_base.hget('password_reset_' + rdm, 'email')
            assert email, "SELF::Change: Invalid Random Key provided: '{}'".format(
                rdm)

            user_infos = self.get_username_by_email(auth_backend,
                                                    auth_backend_fallbacks,
                                                    email)
            self.username = user_infos['user']
            self.backend = user_infos['backend']

        else:
            # Get old_password
            old_password = request.POST[
                'password_old']  # If raise -> ask credentials

            # Get redis_portal_session
            portal_cookie = request.COOKIES.get(self.cluster.getPortalCookie())
            self.redis_portal_session = REDISPortalSession(
                self.redis_base, portal_cookie)
            # If not present -> 403
            assert self.redis_portal_session.exists(
            ), "PORTAL::self: portal session is not valid !"

            # And get username & backend from redis_portal_session
            user_infos = dict()
            auth_backend = self.application.getAuthBackend()
            if self.redis_portal_session.keys.get(
                    'login_' + str(self.application.getAuthBackend().id)):
                try:
                    user_infos = self.authenticate_on_backend(
                        auth_backend, self.username, old_password)
                    self.username = self.redis_portal_session.keys.get(
                        'login_' + str(auth_backend.id))
                    self.backend = auth_backend
                except Exception as e:
                    logger.error(
                        "Seems to have wrong old password on backend : '{}', exception details : "
                        + self.application.getAuthBackend().repo_name)
                    logger.exception(e)
            if not user_infos:
                for backend_fallback in self.application.getAuthBackendFallback(
                ):
                    if self.redis_portal_session.keys.get(
                            'login_' + str(backend_fallback.id)):
                        try:
                            user_infos = self.authenticate_on_backend(
                                backend_fallback, self.username, old_password)
                            self.username = self.redis_portal_session.keys.get(
                                'login_' + str(backend_fallback.id))
                            self.backend = backend_fallback
                            break
                        except:
                            logger.error(
                                "Seems to have wrong old password on backend : "
                                + backend_fallback.repo_name)

            if not self.backend:
                raise AuthenticationError("Wrong old password")
            logger.debug(
                "PORTAL::self: Found username from portal session: {}".format(
                    self.username))

        return old_password
Example #6
0
class SELFServiceChange(SELFService):
    def __init__(self, app_id, token_name):
        super(SELFServiceChange, self).__init__(app_id, token_name)
        self.backend = None

    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 retrieve_credentials(self, request):
        """ We may have a password reset token in URI """
        rdm = request.GET.get("rdm", None) or request.POST.get('rdm', None)

        if not rdm:
            super(SELFServiceChange, self).retrieve_credentials(request)

        old_password = None  # None if rdm
        new_passwd = request.POST['password_1']
        new_passwd_cfrm = request.POST['password_2']
        if new_passwd != new_passwd_cfrm:
            raise PasswordMatchError("Password and confirmation mismatches")

        auth_backend = self.application.getAuthBackend()
        auth_backend_fallbacks = self.application.getAuthBackendFallback()

        if rdm:
            assert re_match("^[0-9a-f-]+$",
                            rdm), "PORTAL::self: Injection attempt on 'rdm'"

            email = self.redis_base.hget('password_reset_' + rdm, 'email')
            assert email, "SELF::Change: Invalid Random Key provided: '{}'".format(
                rdm)

            user_infos = self.get_username_by_email(auth_backend,
                                                    auth_backend_fallbacks,
                                                    email)
            self.username = user_infos['user']
            self.backend = user_infos['backend']

        else:
            # Get old_password
            old_password = request.POST[
                'password_old']  # If raise -> ask credentials

            # Get redis_portal_session
            portal_cookie = request.COOKIES.get(self.cluster.getPortalCookie())
            self.redis_portal_session = REDISPortalSession(
                self.redis_base, portal_cookie)
            # If not present -> 403
            assert self.redis_portal_session.exists(
            ), "PORTAL::self: portal session is not valid !"

            # And get username & backend from redis_portal_session
            user_infos = dict()
            auth_backend = self.application.getAuthBackend()
            if self.redis_portal_session.keys.get(
                    'login_' + str(self.application.getAuthBackend().id)):
                try:
                    user_infos = self.authenticate_on_backend(
                        auth_backend, self.username, old_password)
                    self.username = self.redis_portal_session.keys.get(
                        'login_' + str(auth_backend.id))
                    self.backend = auth_backend
                except Exception as e:
                    logger.error(
                        "Seems to have wrong old password on backend : '{}', exception details : "
                        + self.application.getAuthBackend().repo_name)
                    logger.exception(e)
            if not user_infos:
                for backend_fallback in self.application.getAuthBackendFallback(
                ):
                    if self.redis_portal_session.keys.get(
                            'login_' + str(backend_fallback.id)):
                        try:
                            user_infos = self.authenticate_on_backend(
                                backend_fallback, self.username, old_password)
                            self.username = self.redis_portal_session.keys.get(
                                'login_' + str(backend_fallback.id))
                            self.backend = backend_fallback
                            break
                        except:
                            logger.error(
                                "Seems to have wrong old password on backend : "
                                + backend_fallback.repo_name)

            if not self.backend:
                raise AuthenticationError("Wrong old password")
            logger.debug(
                "PORTAL::self: Found username from portal session: {}".format(
                    self.username))

        return old_password

    # Change password
    def perform_action(self, request, old_password):
        new_passwd = request.POST['password_1']
        new_passwd_cfrm = request.POST['password_2']

        rdm = (request.GET.get("rdm", None) or request.POST.get('rdm', None))

        # If not rdm : Verify password
        if not rdm:
            saved_app_id = self.redis_portal_session.keys['app_id_' +
                                                          str(self.backend.id)]
            saved_app = Application.objects(id=ObjectId(saved_app_id)).only(
                'id', 'name', 'pw_min_len', 'pw_min_upper', 'pw_min_lower',
                'pw_min_number', 'pw_min_symbol').first()
            if not self.redis_portal_session.getAutologonPassword(
                    str(saved_app.id), str(self.backend.id), self.username):
                raise AuthenticationError("Wrong old password")
        else:
            saved_app = self.application

        #Check if password meets required complexity
        upper_case = 0
        lower_case = 0
        number = 0
        symbol = 0

        min_len = int(saved_app.pw_min_len)
        min_upper = int(saved_app.pw_min_upper)
        min_lower = int(saved_app.pw_min_lower)
        min_number = int(saved_app.pw_min_number)
        min_symbol = int(saved_app.pw_min_symbol)

        for i in new_passwd:
            if i.isupper():
                upper_case += 1
            elif i.islower():
                lower_case += 1
            elif i.isdigit():
                number += 1
            else:
                symbol += 1

        if not (len(new_passwd) >= min_len and upper_case >= min_upper
                and lower_case >= min_lower and number >= min_number
                and symbol >= min_symbol):
            logger.info("SELF::change_password: Password is too weak")
            raise AuthenticationError(
                "Password do not meet complexity requirements")

        if issubclass(self.backend.__class__, Backend):
            self.backend.change_password(
                self.username,
                old_password,
                new_passwd,
                krb5_service=self.application.app_krb_service)
        elif isinstance(self.backend.get_backend(), MongoEngineBackend):
            user = User.objects.get(username=str(self.username))
            new_password_hash = make_password(new_passwd)
            user.password = new_password_hash
            user.save()
        else:
            self.backend.get_backend().change_password(
                self.username,
                old_password,
                new_passwd,
                krb5_service=self.application.app_krb_service)
        logger.info(
            "SELF::change_password: Password successfully changed in backend")

        # If not rdm : set new password in Redis portal session
        if not rdm:
            if self.redis_portal_session.setAutologonPassword(
                    str(saved_app.id), str(saved_app.name), str(
                        self.backend.id), self.username, old_password,
                    new_passwd) is None:
                # If setAutologonPasswd return None : the old_password was incorrect
                raise AuthenticationError("Wrong old password")
            logger.info(
                "SELF::change_password: Password successfully changed in Redis"
            )

        return "Password successfully changed"
Example #7
0
 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)
Example #8
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
Example #9
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
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
Example #11
0
def handle_disconnect(request, workflow_id=None):
    """ Handle User Disconnection
    If we are here, mod_vulture has already delete the application session in redis
    According to the configuration of App, this handler will:
     - Display a "Logout Message"
     - Destroy the portal session
     - Redirect to the application (ie: display the Login portal)

    :param request: Django request object
    :returns: Self-service portal
    """
    global_config = Cluster.get_global_config()
    """ Try to find the application with the requested URI """
    try:
        workflow = Workflow.objects.get(pk=workflow_id)
    except:
        logger.error(
            "DISCONNECT::handle_disconnect: Unable to find workflow having id '{}'"
            .format(workflow_id))
        return HttpResponseForbidden("Invalid Workflow.")
    """ Get portal_cookie name from cluster """
    portal_cookie_name = global_config.portal_cookie_name
    """ Get portal cookie value (if exists) """
    portal_cookie = request.COOKIES.get(portal_cookie_name, None)
    if portal_cookie:
        logger.debug(
            "DISCONNECT::handle_disconnect: portal_cookie Found: {}".format(
                portal_cookie))
    else:
        logger.error(
            "DISCONNECT::handle_disconnect: portal_cookie not found !")
        return HttpResponseForbidden("Access Denied.")
    """ Connect to Redis """
    r = REDISBase()
    if not r:
        logger.info("PORTAL::self: Unable to connect to REDIS !")
        return HttpResponseServerError()

    portal_session = REDISPortalSession(r, portal_cookie)
    """ The user do not have a portal session: Access is forbidden """
    if not portal_session.exists():
        return HttpResponseForbidden("Invalid session.")

    # FIXME
    """ Destroy portal session if needed """
    if workflow.app_disconnect_portal:
        logger.info(
            "DISCONNECT::handle_disconnect: portal session '{}' has been destroyed"
            .format(portal_cookie))
        portal_session.destroy()

    # FIXME
    """ Display Logout message if needed (otherwise redirect to application) """
    if workflow.app_display_logout_message:
        template = workflow.template
        style = '<link rel="stylesheet" type="text/css" href="/' + str(
            global_config.public_token) + '/templates/portal_%s.css">' % (str(
                template.id))
        logger.debug(
            "DISCONNECT::handle_disconnect: Display template '{}'".format(
                template.name))
        return render_to_response("portal_%s_html_logout.conf" %
                                  (str(template.id)), {
                                      'style': style,
                                      'app_url': workflow.get_redirect_uri()
                                  },
                                  context_instance=RequestContext(request))
    else:
        logger.debug(
            "DISCONNECT::handle_disconnect: Redirecting to redirect_uri '{}'".
            format(workflow.get_redirect_uri()))
        return HttpResponseRedirect(workflow.get_redirect_uri())