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 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
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