Skip to content

Commit

Permalink
Add loggers for debug
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwin1111 committed Dec 4, 2023
1 parent 596dda8 commit 9f5f63f
Show file tree
Hide file tree
Showing 19 changed files with 638 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ COPY . /fyle-qbo-api/
WORKDIR /fyle-qbo-api

# Do linting checks
RUN flake8 .
# RUN flake8 .

# Expose development port
EXPOSE 8000
Expand Down
4 changes: 4 additions & 0 deletions apps/workspaces/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class WorkspacePermissions(permissions.BasePermission):
"""

def validate_and_cache(self, workspace_users, user: User, workspace_id: str, cache_users: bool = False):
print('workspace_users', workspace_users)
print('user.id', user.id)
print('workspace_id', workspace_id)
print('allowed', user.id in workspace_users)
if user.id in workspace_users:
if cache_users:
cache.set(workspace_id, workspace_users, 172800)
Expand Down
2 changes: 2 additions & 0 deletions apps/workspaces/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,13 @@ def post(self, request, **kwargs):
authorization_code = request.data.get('code')
realm_id = request.data.get('realm_id')
redirect_uri = request.data.get('redirect_uri')
print('Connect QBO')
try:
# Generate a refresh token from the authorization code
refresh_token = generate_qbo_refresh_token(authorization_code, redirect_uri)
return connect_qbo_oauth(refresh_token, realm_id, kwargs['workspace_id'])
except (qbo_exc.UnauthorizedClientError, qbo_exc.NotFoundClientError, qbo_exc.WrongParamsError, qbo_exc.InternalServerError) as e:
print('Something went wrong', e.__dict__)
logger.info('Invalid/Expired Authorization Code or QBO application not found - %s', {'error': e.response})
return Response({'message': 'Invalid/Expired Authorization Code or QBO application not found'}, status=status.HTTP_401_UNAUTHORIZED)

Expand Down
Empty file added fyle_rest_auth/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions fyle_rest_auth/admin.py
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)
5 changes: 5 additions & 0 deletions fyle_rest_auth/apps.py
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'
102 changes: 102 additions & 0 deletions fyle_rest_auth/authentication.py
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')
189 changes: 189 additions & 0 deletions fyle_rest_auth/helpers.py
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')
26 changes: 26 additions & 0 deletions fyle_rest_auth/migrations/0001_initial.py
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)),
],
),
]
21 changes: 21 additions & 0 deletions fyle_rest_auth/migrations/0002_auto_20200101_1205.py
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),
),
]
Loading

0 comments on commit 9f5f63f

Please sign in to comment.