-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
596dda8
commit 9f5f63f
Showing
19 changed files
with
638 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
""" | ||
Registering models in Django Admin | ||
""" | ||
from django.contrib import admin | ||
|
||
from .models import AuthToken | ||
|
||
|
||
admin.site.register(AuthToken) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class FyleRestAuthConfig(AppConfig): | ||
name = 'fyle_rest_auth' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
from typing import Dict | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.core.cache import cache | ||
|
||
from rest_framework.authentication import BaseAuthentication | ||
from rest_framework.exceptions import AuthenticationFailed, ValidationError | ||
|
||
from .helpers import get_fyle_admin | ||
from .models import AuthToken | ||
from .utils import AuthUtils | ||
|
||
User = get_user_model() | ||
auth = AuthUtils() | ||
|
||
|
||
class FyleJWTAuthentication(BaseAuthentication): | ||
""" | ||
Fyle Authentication class | ||
""" | ||
def authenticate(self, request): | ||
""" | ||
Authentication function | ||
""" | ||
access_token_string = self.get_header(request) | ||
|
||
user = self.validate_token( | ||
access_token_string=access_token_string, | ||
origin_address=auth.get_origin_address(request) | ||
) | ||
|
||
try: | ||
user = User.objects.get(email=user['email'], user_id=user['user_id']) | ||
print('user',user) | ||
token = AuthToken.objects.get(user=user) | ||
print('success', token) | ||
except User.DoesNotExist: | ||
raise ValidationError('User not found for this token') | ||
except AuthToken.DoesNotExist: | ||
raise ValidationError('Login details not found for the user') | ||
|
||
return user, None | ||
|
||
@staticmethod | ||
def get_header(request) -> str: | ||
""" | ||
Extracts the header containing the JSON web token from the given | ||
request. | ||
""" | ||
header = request.META.get('HTTP_AUTHORIZATION') | ||
|
||
return header | ||
|
||
@staticmethod | ||
def validate_token(access_token_string: str, origin_address: str) -> Dict: | ||
""" | ||
Validate the access token | ||
:param origin_address: | ||
:param access_token_string: | ||
:return: | ||
""" | ||
if not access_token_string: | ||
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 ValidationError('Invalid access token structure') | ||
|
||
print('access_token_tokenizer', access_token_tokenizer) | ||
unique_key_generator = access_token_tokenizer[1].split('.') | ||
email_unique_key = 'email_{0}'.format(unique_key_generator[2]) | ||
user_unique_key = 'user_{0}'.format(unique_key_generator[2]) | ||
|
||
email = cache.get(email_unique_key) | ||
user = cache.get(user_unique_key) | ||
|
||
if not (email and user): | ||
cache.delete_many([email_unique_key, user_unique_key]) | ||
|
||
try: | ||
employee_info = get_fyle_admin(access_token_string.split(' ')[1], origin_address) | ||
except Exception: | ||
raise AuthenticationFailed('Invalid access token') | ||
|
||
cache.set(email_unique_key, employee_info['data']['user']['email']) | ||
cache.set(user_unique_key, employee_info['data']['user']['id']) | ||
|
||
print('employee_info', employee_info) | ||
return { | ||
'email': employee_info['data']['user']['email'], | ||
'user_id': employee_info['data']['user']['id'] | ||
} | ||
|
||
elif email and user: | ||
print('email', email) | ||
print('user', user) | ||
return { | ||
'email': email, | ||
'user_id': user | ||
} | ||
|
||
raise AuthenticationFailed('Invalid access token') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
import logging | ||
import traceback | ||
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 django_q.tasks import async_task | ||
|
||
from .utils import AuthUtils, post_request, get_request | ||
from .models import AuthToken | ||
|
||
auth = AuthUtils() | ||
|
||
logger = logging.getLogger(__name__) | ||
logger.level = logging.INFO | ||
|
||
|
||
def validate_code_and_login(request): | ||
authorization_code = request.data.get('code') | ||
try: | ||
if not authorization_code: | ||
raise ValidationError('authorization code not found') | ||
|
||
tokens = auth.generate_fyle_refresh_token(authorization_code=authorization_code) | ||
|
||
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['data']['user']['id'], | ||
email=employee_info['data']['user']['email'] | ||
) | ||
|
||
AuthToken.objects.update_or_create( | ||
user=user, | ||
defaults={ | ||
'refresh_token': tokens['refresh_token'] | ||
} | ||
) | ||
|
||
serializer = import_string(settings.FYLE_REST_AUTH_SERIALIZERS['USER_DETAILS_SERIALIZER']) | ||
tokens['user'] = serializer(user).data | ||
tokens['user']['full_name'] = employee_info['data']['user']['full_name'] | ||
tokens['user']['org_id'] = employee_info['data']['org']['id'] | ||
tokens['user']['org_name'] = employee_info['data']['org']['name'] | ||
|
||
# Update Fyle Credentials with latest healthy token | ||
if 'async_update_user' in settings.FYLE_REST_AUTH_SETTINGS \ | ||
and settings.FYLE_REST_AUTH_SETTINGS['async_update_user']: | ||
async_task( | ||
'apps.workspaces.tasks.async_update_fyle_credentials', | ||
employee_info['data']['org']['id'], tokens['refresh_token'] | ||
) | ||
|
||
return tokens | ||
|
||
except ValidationError as error: | ||
logger.info(error) | ||
raise | ||
|
||
except Exception as error: | ||
logger.error(traceback.format_exc()) | ||
raise ValidationError(error) | ||
|
||
|
||
def validate_refresh_token_and_login(request): | ||
""" | ||
Takes refresh_token from payload | ||
GET Fyle Admin info | ||
Get Or Create User | ||
Saves AuthToken | ||
Return Tokens | ||
""" | ||
refresh_token = request.data.get('refresh_token') | ||
try: | ||
if not refresh_token: | ||
raise ValidationError('refresh token not found') | ||
|
||
tokens = auth.refresh_access_token(refresh_token) | ||
|
||
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['data']['user']['id'], | ||
email=employee_info['data']['user']['email'] | ||
) | ||
|
||
AuthToken.objects.update_or_create( | ||
user=user, | ||
defaults={ | ||
'refresh_token': refresh_token | ||
} | ||
) | ||
|
||
serializer = import_string(settings.FYLE_REST_AUTH_SERIALIZERS['USER_DETAILS_SERIALIZER']) | ||
tokens['user'] = serializer(user).data | ||
tokens['user']['full_name'] = employee_info['data']['user']['full_name'] | ||
tokens['user']['org_id'] = employee_info['data']['org']['id'] | ||
tokens['user']['org_name'] = employee_info['data']['org']['name'] | ||
|
||
# Update Fyle Credentials with latest healthy token | ||
if 'async_update_user' in settings.FYLE_REST_AUTH_SETTINGS \ | ||
and settings.FYLE_REST_AUTH_SETTINGS['async_update_user']: | ||
async_task( | ||
'apps.workspaces.tasks.async_update_fyle_credentials', | ||
employee_info['data']['org']['id'], tokens['refresh_token'] | ||
) | ||
|
||
return tokens | ||
|
||
except ValidationError as error: | ||
logger.info(error) | ||
raise | ||
|
||
except Exception as error: | ||
logger.error(traceback.format_exc()) | ||
raise ValidationError(error) | ||
|
||
|
||
def validate_and_refresh_token(request): | ||
refresh_token = request.data.get('refresh_token') | ||
try: | ||
if not refresh_token: | ||
raise ValidationError('refresh token not found') | ||
|
||
tokens = auth.refresh_access_token(refresh_token) | ||
|
||
employee_info = get_fyle_admin(tokens['access_token'], auth.get_origin_address(request)) | ||
users = get_user_model() | ||
|
||
user = users.objects.filter( | ||
email=employee_info['data']['user']['email'], user_id=employee_info['data']['user']['id'] | ||
).first() | ||
|
||
if not user: | ||
raise ValidationError('User record not found, please login') | ||
|
||
auth_token = AuthToken.objects.get(user=user) | ||
auth_token.refresh_token = refresh_token | ||
auth_token.save() | ||
|
||
serializer = import_string(settings.FYLE_REST_AUTH_SERIALIZERS['USER_DETAILS_SERIALIZER']) | ||
tokens['user'] = serializer(user).data | ||
tokens['refresh_token'] = refresh_token | ||
|
||
return tokens | ||
|
||
except ValidationError as error: | ||
logger.info(error) | ||
raise | ||
|
||
except Exception as error: | ||
logger.error(traceback.format_exc()) | ||
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'] or \ | ||
('FYLE_MODULE' in settings.FYLE_REST_AUTH_SERIALIZERS and \ | ||
settings.FYLE_REST_AUTH_SERIALIZERS['FYLE_MODULE'] == 'PARTNER_DASHBOARD'): | ||
return employee_detail | ||
else: | ||
raise ValidationError('User is not an admin') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Generated by Django 3.0.1 on 2020-01-01 11:15 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
initial = True | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='AuthTokens', | ||
fields=[ | ||
('id', models.AutoField(primary_key=True, serialize=False)), | ||
('refresh_token', models.TextField(help_text='Fyle refresh token')), | ||
('access_token', models.TextField(help_text='Fyle access token')), | ||
('user', models.ForeignKey(help_text='User table relation', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Generated by Django 3.0.1 on 2020-01-01 12:05 | ||
|
||
from django.conf import settings | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('fyle_rest_auth', '0001_initial'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterField( | ||
model_name='authtokens', | ||
name='user', | ||
field=models.OneToOneField(help_text='User table relation', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), | ||
), | ||
] |
Oops, something went wrong.