예제 #1
0
    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()
예제 #2
0
    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'
            }
        }
예제 #3
0
		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}) ) )}
예제 #4
0
    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':
                '******'
            }
        }
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    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()
예제 #9
0
    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)
예제 #10
0
    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)
예제 #11
0
    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
예제 #12
0
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
예제 #13
0
    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')
예제 #14
0
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
예제 #15
0
                    }, 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
예제 #16
0
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