diff --git a/fyle_rest_auth/authentication.py b/fyle_rest_auth/authentication.py index adda7ae..52cf408 100644 --- a/fyle_rest_auth/authentication.py +++ b/fyle_rest_auth/authentication.py @@ -1,15 +1,12 @@ -import json from typing import Dict -import requests - from django.contrib.auth import get_user_model -from django.conf import settings from django.core.cache import cache from rest_framework.authentication import BaseAuthentication -from rest_framework import exceptions +from rest_framework.exceptions import AuthenticationFailed, ValidationError +from .helpers import get_fyle_admin from .models import AuthToken from .utils import AuthUtils @@ -36,9 +33,9 @@ def authenticate(self, request): user = User.objects.get(email=user['email'], user_id=user['user_id']) AuthToken.objects.get(user=user) except User.DoesNotExist: - raise exceptions.AuthenticationFailed('User not found for this token') + raise ValidationError('User not found for this token') except AuthToken.DoesNotExist: - raise exceptions.AuthenticationFailed('Login details not found for the user') + raise ValidationError('Login details not found for the user') return user, None @@ -61,11 +58,11 @@ def validate_token(access_token_string: str, origin_address: str) -> Dict: :return: """ if not access_token_string: - raise exceptions.AuthenticationFailed('Access token missing') + raise ValidationError('Access token missing') access_token_tokenizer = access_token_string.split(' ') if not access_token_tokenizer or len(access_token_tokenizer) != 2 or access_token_tokenizer[0] != 'Bearer': - raise exceptions.AuthenticationFailed('Invalid access token structure') + raise ValidationError('Invalid access token structure') unique_key_generator = access_token_tokenizer[1].split('.') email_unique_key = 'email_{0}'.format(unique_key_generator[2]) @@ -77,25 +74,18 @@ def validate_token(access_token_string: str, origin_address: str) -> Dict: if not (email and user): cache.delete_many([email_unique_key, user_unique_key]) - fyle_base_url = settings.FYLE_BASE_URL - my_profile_uri = '{0}/api/tpa/v1/employees/my_profile'.format(fyle_base_url) - api_headers = { - 'Authorization': '{0}'.format(access_token_string), - 'X-Forwarded-For': origin_address - } - - response = requests.get(my_profile_uri, headers=api_headers) + try: + employee_info = get_fyle_admin(access_token_string.split(' ')[1], origin_address) + except Exception: + raise AuthenticationFailed('Invalid access token') - if response.status_code == 200: - result = json.loads(response.text)['data'] + cache.set(email_unique_key, employee_info['data']['user']['email']) + cache.set(user_unique_key, employee_info['data']['user']['id']) - cache.set(email_unique_key, result['employee_email']) - cache.set(user_unique_key, result['user_id']) - - return { - 'email': result['employee_email'], - 'user_id': result['user_id'] - } + return { + 'email': employee_info['data']['user']['email'], + 'user_id': employee_info['data']['user']['id'] + } elif email and user: return { @@ -103,4 +93,4 @@ def validate_token(access_token_string: str, origin_address: str) -> Dict: 'user_id': user } - raise exceptions.AuthenticationFailed('Invalid access token') + raise AuthenticationFailed('Invalid access token') diff --git a/fyle_rest_auth/helpers.py b/fyle_rest_auth/helpers.py index 2a5b7b1..2f2a339 100644 --- a/fyle_rest_auth/helpers.py +++ b/fyle_rest_auth/helpers.py @@ -1,9 +1,12 @@ -from rest_framework.exceptions import AuthenticationFailed +from typing import Dict + +from rest_framework.exceptions import ValidationError + from django.contrib.auth import get_user_model from django.conf import settings from django.utils.module_loading import import_string -from .utils import AuthUtils +from .utils import AuthUtils, post_request, get_request from .models import AuthToken auth = AuthUtils() @@ -13,16 +16,16 @@ def validate_code_and_login(request): authorization_code = request.data.get('code') try: if not authorization_code: - raise AuthenticationFailed('authorization code not found') + raise ValidationError('authorization code not found') tokens = auth.generate_fyle_refresh_token(authorization_code=authorization_code) - employee_info = auth.get_fyle_user(tokens['refresh_token'], auth.get_origin_address(request)) + employee_info = get_fyle_admin(tokens['access_token'], auth.get_origin_address(request)) users = get_user_model() user, _ = users.objects.get_or_create( - user_id=employee_info['user_id'], - email=employee_info['employee_email'] + user_id=employee_info['data']['user']['id'], + email=employee_info['data']['user']['email'] ) AuthToken.objects.update_or_create( @@ -38,23 +41,26 @@ def validate_code_and_login(request): return tokens except Exception as error: - raise AuthenticationFailed(error) + raise ValidationError(error) + def validate_and_refresh_token(request): refresh_token = request.data.get('refresh_token') try: if not refresh_token: - raise AuthenticationFailed('refresh token not found') + raise ValidationError('refresh token not found') tokens = auth.refresh_access_token(refresh_token) - employee_info = auth.get_fyle_user(refresh_token, auth.get_origin_address(request)) + employee_info = get_fyle_admin(tokens['access_token'], auth.get_origin_address(request)) users = get_user_model() - user = users.objects.filter(email=employee_info['employee_email'], user_id=employee_info['user_id']).first() + user = users.objects.filter( + email=employee_info['data']['user']['email'], user_id=employee_info['data']['user']['id'] + ).first() if not user: - raise AuthenticationFailed('User record not found, please login') + raise ValidationError('User record not found, please login') auth_token = AuthToken.objects.get(user=user) auth_token.refresh_token = refresh_token @@ -67,4 +73,32 @@ def validate_and_refresh_token(request): return tokens except Exception as error: - raise AuthenticationFailed(error) + raise ValidationError(error) + + +def get_cluster_domain(access_token: str, origin_address: str = None) -> str: + """ + Get cluster domain name from fyle + :param access_token: (str) + :return: cluster_domain (str) + """ + cluster_api_url = '{0}/oauth/cluster/'.format(settings.FYLE_BASE_URL) + + return post_request(cluster_api_url, {}, access_token, origin_address)['cluster_domain'] + + +def get_fyle_admin(access_token: str, origin_address: str = None) -> Dict: + """ + Get user profile from fyle + :param access_token: (str) + :return: user_profile (dict) + """ + cluster_domain = get_cluster_domain(access_token, origin_address) + + profile_api_url = '{}/platform/v1beta/spender/my_profile'.format(cluster_domain) + employee_detail = get_request(profile_api_url, access_token, origin_address) + + if 'ADMIN' in employee_detail['data']['roles']: + return employee_detail + else: + raise Exception('User is not an admin') diff --git a/fyle_rest_auth/utils.py b/fyle_rest_auth/utils.py index 2aae2ab..6c8bffb 100644 --- a/fyle_rest_auth/utils.py +++ b/fyle_rest_auth/utils.py @@ -4,10 +4,53 @@ import json from typing import Dict -import requests from django.conf import settings -from fylesdk import FyleSDK, UnauthorizedClientError, NotFoundClientError, InternalServerError, WrongParamsError +import requests + + +def post_request(url, body, access_token: str = None, origin_address: str = None) -> Dict: + """ + Create a HTTP post request. + """ + api_headers = { + 'content-type': 'application/json', + 'X-Forwarded-For': origin_address + } + + if access_token: + api_headers['Authorization'] = 'Bearer {0}'.format(access_token) + + response = requests.post( + url, + headers=api_headers, + data=body + ) + + if response.status_code == 200: + return json.loads(response.text) + else: + raise Exception(response.text) + + +def get_request(url, access_token, origin_address: str = None): + """ + Create a HTTP get request. + """ + api_headers = { + 'Authorization': 'Bearer {0}'.format(access_token), + 'X-Forwarded-For': origin_address + } + + response = requests.get( + url, + headers=api_headers + ) + + if response.status_code == 200: + return json.loads(response.text) + else: + raise Exception(response.text) class AuthUtils: @@ -44,48 +87,8 @@ def refresh_access_token(self, refresh_token: str) -> Dict: 'refresh_token': refresh_token } - return self.post(url=self.token_url, body=api_data) + return post_request(self.token_url, api_data) - def get_fyle_user(self, refresh_token: str, origin_address: str = None) -> Dict: - """ - Get Fyle user detail - """ - connection = FyleSDK( - base_url=self.base_url, - client_id=self.client_id, - client_secret=self.client_secret, - refresh_token=refresh_token, - origin_address=origin_address - ) - - employee_detail = connection.Employees.get_my_profile()['data'] - - return employee_detail - - @staticmethod - def post(url, body): - """ - Send Post request - """ - response = requests.post(url, data=body) - - if response.status_code == 200: - return json.loads(response.text) - - elif response.status_code == 401: - raise UnauthorizedClientError('Wrong client secret or/and refresh token', response.text) - - elif response.status_code == 404: - raise NotFoundClientError('Client ID doesn\'t exist', response.text) - - elif response.status_code == 400: - raise WrongParamsError('Some of the parameters were wrong', response.text) - - elif response.status_code == 500: - raise InternalServerError('Internal server error', response.text) - - else: - raise InternalServerError('Internal server error', response.text) @staticmethod def get_origin_address(request): diff --git a/requirements.txt b/requirements.txt index 2fe1dd7..29c9d4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ charset-normalizer==2.0.8 Django==3.1.14 django-rest-framework==0.1.0 djangorestframework==3.11.2 -fylesdk==2.3.0 idna==2.8 isort==4.3.21 lazy-object-proxy==1.4.3 diff --git a/setup.py b/setup.py index 385cc8c..e325a68 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name='fyle-rest-auth', - version='0.9.0', + version='1.0.0', author='Shwetabh Kumar', author_email='shwetabh.kumar@fyle.in', description='Django application to implement OAuth 2.0 using Fyle in Django rest framework', @@ -19,8 +19,7 @@ url='https://github.com/fylein/fyle-rest-auth', packages=setuptools.find_packages(), install_requires=['requests>=2.25.0', 'django>=3.0.2', - 'django-rest-framework==0.1.0', - 'fylesdk>=2.2.0'], + 'django-rest-framework==0.1.0'], include_package_data=True, classifiers=[ 'Framework :: Django',