def test_bad_request_verification_login_data_type(self): ''' Goal test requests with invalid bodies - incorrect datatype. Actions: dummy_payload is invalid : incorrect datatype. ''' self.prepare_user() client = self.app.test_client() valid_preaccess_token = Token( payload_data={ 'route': 'login', 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }) client.set_cookie('', 'preaccess_token', valid_preaccess_token.value) invalid_payload = copy.deepcopy( self.dummy_identification_payload['login']) invalid_payload['identification'] = 1 response = client.post('/api/tokens/verification', json=invalid_payload) self.assertEqual(response.status_code, 400) self.discharge_user()
def setUp(self): self.preaccess_token = lambda route: Token( payload_data={ 'route': route, 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }) self.verification_token = lambda **update: Token( payload_data={ 'token_type': 'verification', 'exp': current_app.config['VERIFICATION_EXP'], **update }) self.dummy_auth_payload = { 'signup': { 'password': '******', 'keyring': { 'public_key': 5, 'g': 9055, 'm': 9059, 'private_key': { 'iv': 'str', 'data': 'str' } } }, 'login': { 'password': '******' } } self.dummy_identification_payload = { 'signup': { 'email': '*****@*****.**', 'username': '******', 'name': 'Testname', 'about': 'test_about' }, 'login': { 'identification': 'test_username' } }
def validate(*args,**kwargs): #Exceptions assert len(args)>1, Exception('No headers has been found.') assert isinstance((headers:=args[1]),dict), TypeError('Headers must be a dictionary.') assert (True if (authorization:=kwargs.pop('authorization',{token_type:{}}))=={token_type:{}} else (True if authorization.get(token_type,None) is None else False)), Exception(f'"{token_type}" is already in the authorization chain.') #Validation #Initialize lambda functions for the 1.: search = lambda head,raw: match.group() if (match:=re.search(f'(?<={head})([^\s]+)',raw)) else None token_raw_data = lambda l,d: search('Bearer ',d) if l=='Authorization' else (search(f'{token_type}_token=',d) if l=='Cookie' else d) locate = lambda l,h: {'location':l, 'object':Token(raw_data=token_raw_data(l,raw) ) } if (raw:=h.get(l)) else {'location':l,'object':None} #Initialize lambda functions for the 3.2: find_case = lambda cases: next(iter(tupled)) if (tupled:=tuple(filter(lambda case: case is not None,cases))) else None map_cases = lambda **credentials: map(lambda identification: case if (case:=UserService(**{identification:credentials[identification]})).id else None,credentials) resolve_user = lambda **identification: find_case(map_cases(**identification)) either = auth_field_case if (auth_field_case:=locate('Authorization',headers)).get('object') else (cookie_field_case if (cookie_field_case:=locate('Cookie',headers)).get('object') else {'location':None,'object':None}) #Validation:1. if (token:=locate(location,headers) if location else either)['object'] and token['object'].is_valid and token['object']['token_type']==token_type: #Validation:2. #Validation:3 ~> valid_ownership|valid_verification|valid_preaccess. valid_preaccess = lambda t : {'valid':True,'status_code':200} if any(True for route in ('signup','login') if t['object']['route']==route) else {'valid':False,'status_code':401} valid_verification = lambda t : {'valid':True,'status_code':200} if \ ( None!=t['object']['activity'] ==\ ( (None if (resolve_user(username=(identification:=t['object']['identification_data'])['username'],email=identification['email'])) else 0 )\ if (preaccess:=Token(raw_data=t['object']['preaccess']))['route']=='signup' else\ (resolved_user.activity_state if (resolved_user:=resolve_user(username=(identification:=t['object']['identification_data']['identification']),email=identification)) else None) ) \ )\ else {'valid':False,'status_code':401} valid_ownership = lambda t: {'valid':True,'status_code':200, 'owner':owner} if\ None != (owner:=UserService(id=t['object']['user_id'])).activity_state == t['object']['activity']\ else {'valid':False,'status_code':401, 'owner':None} authorization[token_type] = {'token':token,\ **(valid_ownership(token) if token_type in ('grant','access','confirmation')\ else (valid_verification(token) if token_type=='verification'\ else (valid_preaccess(token) if token_type=='preaccess'\ else {'valid':True,'status_code':200}) ) )}
def setUp(self): ''' Goal: set up necessary instance attributes - during every test: access_token:lambda function - generates a Token instance , meant to be used as a access token. Parameters:update - key word argument - meant to store data required for the access tokens , according to established guidelines - activity and user_id. Returns: Token instance dummy_identification_paylaod:dict - a dictionary which shall contain identification payloads for the actions , such as : signup or login. Returns: None ''' self.confirmation_token = lambda **update: Token( payload_data={ 'token_type': 'confirmation', 'exp': current_app.config['CONFIRMATION_EXP'], **update }) self.dummy_identification_payload = { 'signup': { 'email': '*****@*****.**', 'username': '******', 'name': 'Testname', 'about': 'test_about' }, 'login': { 'identification': 'test_username' } } self.dummy_action_payload = { 'put': { 'action': 'put' }, 'delete': { 'action': 'delete' } } self.dummy_json_payload = { 'put': { 'reset': { 'password': { 'current': '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'new': '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a09' }, 'private_key': { 'iv': 'iv value', 'data': 'encrypted data' } } }, 'delete': { 'password': '******' } }
def test_valid_verification_signup(self): ''' Goal: test verification request aimed at the signup with valid body & headers. ''' client = self.app.test_client() valid_preaccess_token = Token( payload_data={ 'route': 'signup', 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }) client.set_cookie('', 'preaccess_token', valid_preaccess_token.value) response = client.post( '/api/tokens/verification', json=self.dummy_identification_payload['signup']) self.assertEqual(response.status_code, 201)
def test_unauthorized_verification_signup_preaccess_route_absent(self): ''' Goal: test invalid authorization - the preaccess token /w absent route. Actions: dymmy_payload is valid according to the signup , route is absent: ''' client = self.app.test_client() invalid_preaccess_token = Token( payload_data={ 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }) client.set_cookie('', 'preaccess_token', invalid_preaccess_token.value) response = client.post( '/api/tokens/verification', json=self.dummy_identification_payload['signup']) self.assertEqual(response.status_code, 401)
def setUp(self): ''' Goal: set up necessary instance attributes - during every test: access_token:lambda function - generates a Token instance , meant to be used as a access token. Parameters:update - key word argument - meant to store data required for the access tokens , according to established guidelines - activity and user_id. Returns: Token instance dummy_identification_paylaod:dict - a dictionary which shall contain identification payloads for the actions , such as : signup or login. Returns: None ''' self.access_token = lambda **update: Token( payload_data={ 'token_type': 'access', 'exp': current_app.config['ACCESS_EXP'], **update }) self.dummy_identification_payload = ({ 'signup': { 'email': '*****@*****.**', 'username': '******', 'name': 'Testname0', 'about': 'test_about' }, 'login': { 'identification': 'test_username1' } }, { 'signup': { 'email': '*****@*****.**', 'username': '******', 'name': 'Testname1', 'about': 'test_about' }, 'login': { 'identification': 'test_username1' } }) self.namespace = '/socket/notification' self.socket = socket self.socket.init_app(self.app)
def test_unauthorized_verification_login_preaccess_token_type_absent(self): ''' Goal: test invalid authorization - the preaccess token /w absent type. Actions: dymmy_payload is valid according to the login , token type is absent: ''' self.prepare_user() client = self.app.test_client() invalid_preaccess_token = Token( payload_data={ 'route': 'login', 'exp': current_app.config['PREACCESS_EXP'] }) client.set_cookie('', 'preaccess_token', invalid_preaccess_token.value) response = client.post('/api/tokens/verification', json=self.dummy_identification_payload['login']) self.assertEqual(response.status_code, 401) self.discharge_user()
def test_bad_request_verification_signup_absent_key(self): ''' Goal test requests with invalid bodies - incorrect keys. Actions: dummy_payload is invalid : incorrect key/value pairs. ''' client = self.app.test_client() valid_preaccess_token = Token( payload_data={ 'route': 'signup', 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }) client.set_cookie('', 'preaccess_token', valid_preaccess_token.value) invalid_payload = copy.deepcopy( self.dummy_identification_payload['signup']) invalid_payload.pop('name') response = client.post('/api/tokens/verification', json=invalid_payload) self.assertEqual(response.status_code, 400)
def test_unauthorized_verification_signup_preaccess_signature_expired( self): ''' Goal: test invalid authorization - the preaccess token /w expired signature. Actions: dymmy_payload is valid according to the signup , signature has expired: ''' client = self.app.test_client() invalid_preaccess_token = Token(payload_data={ 'route': 'signup', 'token_type': 'preaccess', 'exp': { 'seconds': 0 } }) time.sleep(1) client.set_cookie('', 'preaccess_token', invalid_preaccess_token.value) response = client.post( '/api/tokens/verification', json=self.dummy_identification_payload['signup']) self.assertEqual(response.status_code, 401)
def accept(self, headers, data, **kwargs): ''' Goal : Mail verification_token to an email , based on the provided <identification_data> - thus allowing a user to proceed to verify themselves. Arguments:headers:dict, data:dict, kwargs:key-word-argument. headers : meant to contain all headers data , in this particular case - a cookie field as a sign of authority, must contain a preaccess_token = {route:["signup"/"login"],token_type:"preaccess",dnt}. Note: This argument is used in the authorized decorator - to perform proper authorization process, the result of which is stored in the kwargs. To know more about the authorized decorator - view a separate documentation for the authorized method in the chathouse/utilities/security/validation/headers.py. data : meant to store any data that's passed into the request - json body. The could be 2 cases: signup:<identification_data>:{email:str,username:str,name:str,about:str} or login:<identification_data>={identification:str} Note: This argument is used in the verification process of the incoming request data, which is handled by the derived class template - which on itself is a result of create_a_template function, meant to return a proper template instance according to the route. To know more about the create_a_template - view a separate documentation for the create_a_template function in the ./template.py. kwargs: meant to store any data passed into the accept method, from the initial requrest, up to the authorization steps. In particular stores the authorization key , which on it's own stores shall store more nested information related to a token_type. In this instance the token_type is the preaccess one - so kwargs shall store:{ authorization:{ preaccess:{ valid:bool, status:int, token:{ object:None|Token, location:str } } } } Full verification: 0.Verify the preaccess_token; If invalid respond with 401; Otherwise resume with the next steps. 1.Extract the route value from the token and create a proper template. 2.Validate the data against the proper template. 3.Resolve the user's email and activity using the resolve function, which returns a : dictionary of {'email':str,'activity':int} | None , based on the route and identification data. If the email has been resolved : Proceed to send the email and respond accordingly with 201. Otherwise: Respond accordingly with 400. Lambda functions: [Note each lambda function shall be called from bottom up.]s find_case: Goal: return the very first case of existing UserService from the submited search cases. Arguments: cases:map of cases:UserService|None. Returns: very first UserService instance. [Note at this point there must be a UserService instance, with the help of explicit validation with any(). But if there isn't one - error will not arise due to the tuple verification] map_cases: Goal:create a map object of iterated UserService instances for email and username cases. Iteration itself checks if the instance has an id -> returns the UserService if instance has an id else None. Arguments: data:key word argument. Returns: map object. resolve: Goal: resolve email based on the identification data , by verify if the such data is appropriate according to a certain route. If the route is signup: Get respective map_cases, then : If not even one case (not any function) is valid return: {'email':as email from the provided data, 'activity': as 0} Otherwise return None Otherwise: Get respective map_cases, then convert cases into a tuple: If there is a case, which isn't None - then return the very next element of the filtered cases , where every case must not be a None - thus returning: {'email':resolved user's email, 'activity':current activity_dnt state of the resolved user}. Otherwise return None. Returns: {email:str,activir:int}|None. Generation: verification_token={identification_data:<identification_data>,token_type:"verification", activity:<current_activity_dnt_state>, dnt,preaccess:<preaccess_token>}. Returns: If verified: 201,{success:True,email_sent:True} Otherwise: Failed to verify the token: 401,{success:False,message:"Unauthorized."} Failed to validate the json body: 400,{success:False,message:"Incorrect json data."} ''' #Code #Exceptions assert all( map(lambda argument: isinstance(argument, dict), (headers, data)) ), TypeError( 'Arguments , such as : headers and data - must be dictionaries') #Lambda functions find_case = lambda cases: next(iter(tupled)) if (tupled := (tuple( filter(lambda case: case is not None, cases)))) else None map_cases = lambda **credentials: map( lambda identification: case if (case := UserService(**{ identification: credentials[identification] })).id else None, credentials) resolve = lambda route,data: ( {'email':data['email'],'activity':0} if not any(map_cases(email=data['email'],username=data['username'])) else None ) if route=='signup' \ else ({'email':case.email, 'activity': case.activity_state } if any( (cases:=tuple( map_cases(email=data['identification'],username=data['identification']) ) ) ) and (case:=find_case(cases)) else None) #Step 0. if not kwargs['authorization']['preaccess']['valid']: return { 'success': 'False', 'message': 'Unauthorized!', 'reason': 'Invalid preaccess token.' }, 401 #Step 1. template = create_a_template(route := kwargs['authorization'] ['preaccess']['token']['object']['route']) #Step 2. #Step 3. if template.validate(**data) and (resolution := resolve( route, (data := template.data))) is not None: verification_token = Token( payload_data={ 'identification_data': data, 'token_type': 'verification', 'activity': resolution['activity'], 'preaccess': kwargs['authorization']['preaccess']['token'] ['object'].value, 'exp': current_app.config['VERIFICATION_EXP'] }) MailService.send( recipients=[resolution['email']], body= f"There has been a request to {route}, using this email. If you wish to proceed with the verification , follow this url http://127.0.0.1:5000/verify/{verification_token.value} .\nOtherwise please ignore this email.", subject="Verification email.") return {'success': 'True', 'message': 'Email has been sent.'}, 201
class PostConfirmationStrategy(Strategy): ''' PostVerificationStrategy - a class, meant to be used to perform the required actions according to the defined strategy in the accept method. Inherits: Strategy class. Methods: accept - meant to perform the acceptance of the request headers and data (initated in the PostVerificationController.handle(some headers,some data)), Core action: validation: headers with the help of authorized decorator; data with the help of certain VerificationTemplate. response: based on the validation come up with a respective response. ''' @authorized(token_type='access', location='Authorization') def accept(self, headers, data, **kwargs): ''' Goal : Mail verification_token to an email , based on the provided <identification_data> - thus allowing a user to proceed to verify themselves. Arguments:headers:dict, data:dict, kwargs:key-word-argument. headers : meant to contain all headers data , in this particular case - a cookie field as a sign of authority, must contain a preaccess_token = {route:["signup"/"login"],token_type:"preaccess",dnt}. Note: This argument is used in the authorized decorator - to perform proper authorization process, the result of which is stored in the kwargs. To know more about the authorized decorator - view a separate documentation for the authorized method in the chathouse/utilities/security/validation/headers.py. data : meant to store any data that's passed into the request - json body: action:str(delete|put) Note: This argument is used in the verification process of the incoming request data, which is handled by the derived class template - which on itself is a result of create_a_template function, meant to return a proper template instance according to the route. To know more about the create_a_template - view a separate documentation for the create_a_template function in the ./template.py. kwargs: meant to store any data passed into the accept method, from the initial requrest, up to the authorization steps. In particular stores the authorization key , which on it's own stores shall store more nested information related to a token_type. In this instance the token_type is the preaccess one - so kwargs shall store:{ authorization:{ access:{ valid:bool, status:int, owner:UserService|None, token:{ object:None|Token, location:str|None } } } } Full verification: 0.Verify the access_token; If invalid respond with 401; Otherwise resume with the next steps. 1.Create a proper template for validating provided data. 2.Validate the data against the proper template. 3.If validation has been successful -> update the current activity state of the requester/owner of the access token. [-]If either 2.|3. has been unsuccessful - respond with 400 [+]Otherwise proceed to the generate a confirmation token and send it to the requester/access token owner as an email. Responding with 201 and a proper message. Generation: confirmation_token={user_id:int(request's/owner's id), action:str(requested action),token_type:str("confirmation"), activity:int(current user's activity state), dnt:float }. Returns: If verified: 201,{success:True,email_sent:True} Otherwise: Failed to verify the token: 401,{success:False,message:"Unauthorized."} Failed to validate the json body: 400,{success:False,message:"Incorrect json data."} ''' #Code #Exceptions assert all( map(lambda argument: isinstance(argument, dict), (headers, data)) ), TypeError( 'Arguments , such as : headers and data - must be dictionaries') #Step 0. if not kwargs['authorization']['access']['valid'] or ( owner := kwargs['authorization']['access']['owner']) is None: return { 'success': 'False', 'message': 'Unauthorized!', 'reason': 'Invalid access_token' }, 401 #Step 1. template = create_a_template() #Step 2. #Step 3. if template.validate(**data) and owner.update_activity(): data = template.data confirmation_token = Token( payload_data={ 'user_id': owner.id, 'action': data['action'], 'token_type': 'confirmation', 'activity': owner.activity_state, 'exp': current_app.config['CONFIRMATION_EXP'] }) MailService.send( recipients=[owner.email], body= f"Greetings, {owner.username}!\nThere has been a request to {'reset' if data['action']=='put' else data['action']} your data. If you wish to proceed with the confirmation , follow this url http://127.0.0.1:5000/confirm/{confirmation_token.value} .\nOtherwise please ignore this email.", subject="Confirmation email.") return { 'success': 'True', 'message': 'Confirmation email has been sent.' }, 201 else: return {'success': 'False', 'message': 'Data is invalid.'}, 400
def accept(self, headers, data, **kwargs): ''' Goal: validate/provide access to the signup page - based on the provided tokens. Arguments:headers:dict, data:dict, kwargs:key-word-argument. headers : meant to contain all headers data , in this particular case - Cookie field as a sign of authority for the grant_token and another Cookie field for the preaccess_token: grant_token = {user_id: value:int, token_type: "grant":str, activity: UserService(id=value of user_id).activity , dnt:float} preaccess_token = {route:str('signup'),token_type:str('preaccess')} Note: This argument is used in the authorized decorator - to perform proper authorization process, the result of which is stored in the kwargs. To know more about the authorized decorator - view a separate documentation for the authorized method in the chathouse/utilities/security/validation/headers.py. data : meant to store any data that's passed into the request - in this case data is irrelevant: kwargs: meant to store any data passed into the accept method, from the initial requrest, up to the authorization steps. In particular stores the authorization key , which on it's own shall store nested information related to a token_type. In this instance the token_type could be the grant or the verification one - so kwargs shall store:{ authorization:{ grant:{ valid:bool, status:int, owner:UserService|None, token:{ object:None|Token, location:str|None } } preaccess:{ valid:bool, status:int, token:{ object:None|Token, location:str|None } } } } Actions: 1.If the request contains a valid grant token, then a user is already signed in - redirect them to the authorized route. Otherwise proceed to the next step. 2.If the provided headers doesnt contain a valid preaccess token / or the route doesn't correspond the requested one - signup: Set the preaccess_token with proper route,type and expiration value - as a HTTPonly Cookie with samesite set to Strict. Then in either way render a respective template for the requested route. Exceptions: Raises: TypeError - if headers and data arguments are not dictionaries. ''' #Code: #Exceptions: assert all( map(lambda argument: isinstance(argument, dict), (headers, data)) ), TypeError( 'Arguments , such as : headers and data - must be dictionaries') if kwargs['authorization']['grant']['valid']: return redirect(url_for('authorized.chat')) response = make_response( render_template('/public/auth.html', route="signup")) if not (valid := kwargs['authorization']['preaccess']['valid'] ) or kwargs['authorization']['preaccess']['token']['object'][ 'route'] != 'signup': response.set_cookie( 'preaccess_token', Token( payload_data={ 'route': 'signup', 'token_type': 'preaccess', 'exp': current_app.config['PREACCESS_EXP'] }).value, httponly=True, samesite='Strict')
class PostGrantStrategy(Strategy): ''' PostGrantStrategy - a class, meant to be used to perform the required actions according to the defined strategy in the accept method. Inherits: Strategy class. Methods: accept - meant to perform the acceptance of the request headers and data (initated in the PostGrantController.handle(some headers,some data)), Core action: validation: headers with the help of authorized decorator; data with the help of certain GrantTemplate. response: based on the validation come up with a respective response. ''' @authorized(token_type='verification', location='Authorization') def accept(self, headers, data, **kwargs): ''' Goal : Generate a grant token, based on the <identification_data> from the verification_token and the <authentication_data> from the data of the request. Arguments:headers:dict, data:dict, kwargs:key-word-argument. headers : meant to contain all headers data , in this particular case - an Authorization field as a sign of authority, must contain a Bearer token , which is the verification_token: {identification_data:<identification_data>,token_type:"verification",activity:int,dnt:float,preaccess:<preaccess_token>}. Note: This argument is used in the authorized decorator - to perform proper authorization process, the result of which is stored in the kwargs. To know more about the authorized decorator - view a separate documentation for the authorized method in the chathouse/utilities/security/validation/headers.py. data : meant to store any data that's passed into the request - json body. The could be 2 cases: signup: <authentication_data>:{ password:str, keyring:{ public_key:str, private_key:{ iv:str, data:str }, g:int, m:int } } or login: <authentication_data>:{ password:str } Note: This argument is used in the verification process of the incoming request data, which is handled by the derived class template - which on itself is a result of create_a_template function, meant to return a proper template instance according to the route. To know more about the create_a_template - view a separate documentation for the create_a_template function in the ./template.py. kwargs: meant to store any data passed into the accept method, from the initial requrest, up to the authorization steps. In particular stores the authorization key , which on it's own stores shall store more nested information related to a token_type. In this instance the token_type is the preaccess one - so kwargs shall store:{ authorization:{ verification:{ valid:bool, status:int, token:{ object:None|Token, location:str } } } } Full verification: 0.Verify the verification_token: 1.Extract the preaccess token from the verification_token and verify it and check existance of the route value. If 0./1. is invalid respond with 401, message:"Invalid verification/preaccess token."; Otherwise resume with the next steps. 2.Having validated the preaccess , extract the route value and create a proper template. 3.Verify that the data is a dictionary an proceed to validate the data against the proper template. 4.Resolve the appropriate user_service object (/w existing or non existing inner instance of the user) , using the resolve function , which returns a UserService|None, based on the route and <identification_data> 4.If the user_service has been resolved: Then according to the route: 5. If route is signup: 5.S.1. If either provided Diffie Hellman parameters or public key is invalid: Respond with a 409, 'Invalid Diffie Hellman data.' Otherwise proceed to the next step: 5.S.2. Establish a payload using the proper_payload function and If user_service.signup(payload) has been successful: Generate a grant_token and the response shall contain it with a 201. Otherwise: Respond with 409, 'Provided data is invalid.' if the prodived public_key doesn't already exists otherwise 'Please submit again.' 5.L.1. Otherwise if the route is login and user_service.login( established payload) has been successful: Generate a grant_token, retrieve a private key from the keyring and DH parameters => inject them into the response, and respond with a 200. At this point the activity_dnt has been upgraded to avoid the usage of previously generated valid tokens. Otherwise: Respond with 401, message:"Invalid authentication data". Otherwise: Respond accordingly with 400, 'Invalid payload data' Lambda functions: map_cases: Goal:create a map object of iterated UserService instances for email and username cases. Iteration itself checks if the isntance has an id -> returns the UserService case if instance has an id else None. Arguments: data:key word argument. Returns: map object. resolve: Goal: resolve email based on the identification data , by verify if the such data is appropriate according to a certain route. If the route is signup: Get respective map_cases, then : If not even one case (not any function) is valid return then return a UserService instance with empty inner instance of the user. Otherwise return None Otherwise: Get respective map_cases, then convert cases into a tuple: If any case isn't None - then return next element of the filtered cases , where every case must not be a None - thus returning an email. Otherwise return None. Returns: str|None , str - represents an email value. valid_dh_parameters: Goal: verify if the provided dh parameters are the same as the configured ones, and the provided public is in bounds 0<public_key<modulus. Helps to omit requests, which contain invalid DH parameters meant for the key establishments. Arguments:initial_keyring:dict - the keyring from the provided data. Returns: True|False Note: When called the g,m values are popped - so, the keyring now has a following structure: keyring:{ public_key:str, private_key:{ iv:str, data:str } } proper_payload: Goal: construct a proper payload based on the incoming_route value and incoming_data. Arguments: incoming_route:str,incoming_data:dict. Actions: If the route is signup ( Note: at this point incoming_data consists of : password:str , keyring:{ public_key:str , private_key: { iv:str,data:str } } ): return { key_data : pop the keyring from the incoming_data user_data : {unpack the <identification_data> from the verification token , and unpack the incoming_data} }. Otherwise the route is login ( Note at this point incoming_data consists of : password:str ): return incoming_data. Returns: a dictionary. token_payload: Goal: construct a proper payload_data for the Token, based on the provided service instance. Arguments: service:UserService instance. Returns: an empty dictionary if the service instance has no inner instance/state, otherwise poceed to create a dictionary of: user_id:int(user's id), token_type:str('grant'), activity:int(activity_state of the UserService), exp:dict(configured expiration value for the grant_token - 30 minutes) Generation: grant_token={user_id: value:int, token_type: "grant":str, activity: timestamp of UserService(id=value of user_id).activity_dnt , dnt:float} Returns: If either verification token or preaccess token(from the verification token) is invalid or the preaccess token has no route value. Return 401, message:"Invalid verification/preaccess token." Otherwise: If route is signup: If either provided Diffie Hellman parameters or public key is invalid: Respond with a 409, 'Invalid Diffie Hellman data.' Otherwise: If the user_service.signup(appropriate payload) has been successful: Return 200, {grant_token:<grant_token>} Otherwise: Return 409, 'Provided data is invalid.' if the prodived public_key doesn't already exists otherwise 'Please submit again.' Otherwise if the route is login and user_service.login(appropriate payload) has been successful: Return 200, {grant_token:<grant_token>,keyring:{raw:{private_key:<encrypted private_key>}, g:<G>, m:<M>}} Otherwise: Return 401, 'Invalid authentication data.' Exceptions: Raises: TypeError - if headers and data arguments are not dictionaries. ValueError - if the current app doesn't contain domain Diffie Hellman parameters. ''' #Code: #Exceptions: assert all( map(lambda argument: isinstance(argument, dict), (headers, data)) ), TypeError( 'Arguments , such as : headers and data - must be dictionaries') assert all( map(lambda parameter: isinstance(parameter, int), current_app.config.get('DH_PARAMETERS', (None, ))) ), ValueError( 'Configuration of the current app must contain domain Diffie Hellman parameters.' ) #Lambda functions: find_case = lambda cases: next(iter(tupled)) if (tupled := tuple( filter(lambda case: case is not None, cases))) else None map_cases = lambda **credentials: map( lambda identification: case if (case := UserService(**{ identification: credentials[identification] })).id else None, credentials) resolve = lambda route,data: (UserService() if not any(map_cases(email=data.get('email',''),username=data.get('username',''))) else None ) if route=='signup'\ else (case if any( (cases:=tuple( map_cases(email=data.get('identification',''),username=data.get('identification','') ) ) ) ) and (case:=find_case(cases)) else None) valid_dh_parameters = lambda initial_keyring: current_app.config[ 'DH_PARAMETERS'] == ( initial_keyring.pop('g', None), initial_keyring.pop('m', None)) and 0 < initial_keyring.get( 'public_key', -1) < current_app.config['DH_PARAMETERS'][1] proper_payload = lambda incoming_data,incoming_route: { \ 'key_data':incoming_data.pop('keyring', {'public_key':None,'private_key':None}),\ 'user_data':{\ **kwargs['authorization']['verification']['token']['object']['identification_data'],\ **incoming_data\ }\ } if incoming_route=='signup' else incoming_data token_payload = lambda service: {'user_id':service.id,\ 'token_type':'grant',\ 'activity':service.activity_state,\ 'exp':current_app.config['GRANT_EXP']}\ if getattr(service,'id',None) is not None else {} #Step 0. #Step 1. if not kwargs['authorization']['verification']['valid'] or not ( preaccess_token := Token(raw_data=kwargs['authorization']['verification']['token'] ['object']['preaccess']) ).is_valid or not preaccess_token['route']: return { 'success': 'False', 'message': 'Unauthorized!', 'reason': 'Invalid verification/preaccess token.' }, 401 #Step 2. template = create_a_template(route := preaccess_token['route']) #Step 3. #Step 4. if template.validate(**data) and (user_service := resolve( route, kwargs['authorization']['verification']['token'] ['object']['identification_data'])) is not None: data = template.data #Step 5. if route == 'signup': #Step 5.S.1 if not (dh_is_valid := valid_dh_parameters(data['keyring'])): return { 'success': 'False', 'reason': 'Invalid Diffie Hellman data.' }, 409 #Step 5.S.2 if user_service.signup( **(su_payload := proper_payload(data, route))): return {'success':'True',\ 'grant_token':Token(payload_data=token_payload(user_service)).value\ },201
}, 409 #Step 5.S.2 if user_service.signup( **(su_payload := proper_payload(data, route))): return {'success':'True',\ 'grant_token':Token(payload_data=token_payload(user_service)).value\ },201 else: return {'success':'False',\ 'reason': ('Conflict. Please submit again.' if KeyringService(public_key=su_payload['key_data']['public_key']).id else 'Provided data is invalid.') },\ 409 #Step 5.L.1 elif route == 'login' and user_service.login( **proper_payload(data, route)): return {'success':'True',\ 'grant_token':Token(payload_data=token_payload(user_service)).value,\ 'keyring':{ 'raw':{'private_key':user_service.keyring.private_key}, **dict(zip('gm',current_app.config['DH_PARAMETERS'])) } },201 else: return { 'success': 'False', 'reason': 'Invalid authentication data.' }, 401 else: return {'success': 'False', 'reason': 'Invalid payload data.'}, 400
class GetAccessStrategy(Strategy): ''' GetAccessStrategy - a class, meant to be used to perform the required actions according to the defined strategy in the accept method. Inherits: Strategy class. Methods: accept - meant to perform the acceptance of the request headers and data (initated in the GetAccessController.handle(some headers,some data)), Core action: validation: headers with the help of authorized decorator. response: based on the validation come up with a respective response. ''' @authorized(token_type='grant') def accept(self, headers, data, **kwargs): ''' Goal : Generate an access token, based on the provided grant_token and it's payload. Arguments:headers:dict, data:dict, kwargs:key-word-argument. headers : meant to contain all headers data , in this particular case - an Authorization|Cookie field as a sign of authority, must contain a "Bearer <token>"|"grant_token=<token>" , which is the grant_token: grant_token = {user_id: value:int, token_type: "grant":str, activity: timestamp of UserService(id=value of user_id).activity_dnt , dnt:float} Note: This argument is used in the authorized decorator - to perform proper authorization process, the result of which is stored in the kwargs. To know more about the authorized decorator - view a separate documentation for the authorized method in the chathouse/utilities/security/validation/headers.py. data : meant to store any data that's passed into the request - in this case data is irrelevant: kwargs: meant to store any data passed into the accept method, from the initial requrest, up to the authorization steps. In particular stores the authorization key , which on it's own stores shall store more nested information related to a token_type. In this instance the token_type is the grant one - so kwargs shall store:{ authorization:{ grant:{ valid:bool, status:int, owner:UserService|None, token:{ object:None|Token, location:str } } } } Full verification: 0.Verify the grant_token , which on it's own - verifies ownership - makes sure of the existance of a user with the user_id - establishing a UserService, and verifies the provided reference to the activity_dnt state with the current one related to the UserService : If 0. is invalid respond with 401, message:"Invalid grant token."; Otherwise head to the generation phase. ss Generation: access_token={user_id: <owner.id>:int, token_type: "access":str, activity: <timestamp of owner.actiivity_dnt>:int , dnt:float} Returns: If the grant token(the ownership,signature) is invalid: Return 401, message:"Unauthorized!","reason":"Invalid grant token." Otherwise: Return 200, {access_token:<access_token>} Exceptions: Raises: TypeError - if headers and data arguments are not dictionaries. ''' #Code: #Exceptions: assert all( map(lambda argument: isinstance(argument, dict), (headers, data)) ), TypeError( 'Arguments , such as : headers and data - must be dictionaries') #Step 0. if not kwargs['authorization']['grant']['valid'] or ( owner := kwargs['authorization']['grant']['owner']) is None: return { 'success': 'False', 'message': 'Unauthorized!', 'reason': 'Invalid grant token.' }, 401 return { 'success': 'True', 'access_token': Token( payload_data={ 'user_id': owner.id, 'token_type': 'access', 'activity': owner.activity_state, 'exp': current_app.config['ACCESS_EXP'] }).value }, 200