Skip to content

Commit

Permalink
OAuth2 integration start (#157)
Browse files Browse the repository at this point in the history
* OAuth2 integration start
Changes to be committed:
	modified:   bco_api/api/views.py
	modified:   bco_api/bco_api/settings.py
	modified:   bco_api/bco_api/urls.py
	modified:   requirements.txt

* Major refactor

* ObtainJSONWebToken

* Saving changes
Changes to be committed:
	modified:   api/scripts/method_specific/POST_api_accounts_describe.py
	modified:   api/views.py
	modified:   authentication/services.py
	modified:   bcodb/settings.py
	modified:   requirements.txt

* Enabled OAuth creation of BCO Draft
Changes to be committed:
	modified:   api/scripts/method_specific/POST_api_objects_drafts_create.py
	new file:   authentication/admin.py
	modified:   authentication/apis.py
	new file:   authentication/apps.py
	new file:   authentication/migrations/0001_initial.py
	new file:   authentication/migrations/__init__.py
	modified:   authentication/models.py
	modified:   authentication/selectors.py
	modified:   authentication/services.py
	modified:   bcodb/settings.py

* Cleaning
Changes to be committed:
	modified:   api/model/groups.py
	modified:   authentication/services.py
  • Loading branch information
HadleyKing committed May 4, 2023
1 parent eec7b26 commit 98d3fe0
Show file tree
Hide file tree
Showing 115 changed files with 417 additions and 14 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion bco_api/api/model/groups.py → api/model/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ def associate_user_group(sender, instance, created, **kwargs):
Group.objects.create(name=instance)
group = Group.objects.get(name=instance)
group.user_set.add(instance)
if instance.username not in ["anon", "bco_drafter", "bco_publisher"]:
if instance.username not in ["anon", "bco_drafter", "bco_publisher", "AnonymousUser"]:
User.objects.get(username=instance).groups.add(
Group.objects.get(name="bco_drafter")
)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ def POST_api_accounts_describe(token):

# Instantiate UserUtils
uu = UserUtils.UserUtils()

# Get the user's information
return Response(
data=uu.get_user_info(username=Token.objects.get(key=processed).user.username),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
from django.conf import settings
from django.contrib.auth.models import Group
from django.utils import timezone
from rest_framework import status
from rest_framework import status, authtoken
from rest_framework.response import Response

from authentication.selectors import get_user_from_auth_token

def post_api_objects_drafts_create(request):
"""Create BCO Draft
Expand All @@ -31,7 +31,10 @@ def post_api_objects_drafts_create(request):
"""

db_utils = DbUtils.DbUtils()
user = UserUtils.UserUtils().user_from_request(request=request)
try:
user = UserUtils.UserUtils().user_from_request(request=request)
except authtoken.models.Token.DoesNotExist:
user = get_user_from_auth_token(request.META.get("HTTP_AUTHORIZATION").split(" ")[1])
prefix_perms = UserUtils.UserUtils().prefix_perms_for_user(
flatten=True, user_object=user, specific_permission=["add"]
)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 11 additions & 1 deletion bco_api/api/views.py → api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
Django views for BCODB API
"""

import jwt
from django.contrib.auth.models import User
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.authtoken.models import Token
from api.permissions import RequestorInPrefixAdminsGroup
from api.scripts.method_specific.GET_activate_account import GET_activate_account
from api.scripts.method_specific.GET_draft_object_by_id import get_draft_object_by_id
Expand Down Expand Up @@ -228,10 +231,17 @@ def post(self, request):
Pass the request to the handling function
Source: https://stackoverflow.com/a/31813810
"""
if "Authorization" in request.headers:

if request.headers["Authorization"].split(" ")[0] == "Token":
return POST_api_accounts_describe(
token=request.META.get("HTTP_AUTHORIZATION")
)
if request.headers["Authorization"].split(" ")[0] == "Bearer":
jw_token=request.META.get("HTTP_AUTHORIZATION").split(" ")[1]
unverified_payload = jwt.decode(jw_token, None, False)
user = User.objects.get(email=unverified_payload['email'])
token = "Thing "+ str(Token.objects.get(user=user))
return POST_api_accounts_describe(token)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)

Expand Down
File renamed without changes.
7 changes: 7 additions & 0 deletions authentication/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Authentication Admin Pannel
"""

from django.contrib import admin
from authentication.models import Authentication

admin.site.register(Authentication)
51 changes: 51 additions & 0 deletions authentication/apis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# authentication/apis.py

import json
from django.contrib.auth.models import User
from rest_framework import status, serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from authentication.selectors import check_user_email, get_user_info
from authentication.services import validate_token, create_bcodb, send_bcodb
from authentication.models import Authentication

class RegisterBcodbAPI(APIView):
"""
"""

class InputSerializer(serializers.Serializer):
hostname= serializers.URLField()
email = serializers.EmailField()
token = serializers.CharField()

class Meta:
model = User
fields = ["__all__"]

authentication_classes = []
permission_classes = []

def post(self, request):
"""
"""
user_info = self.InputSerializer(data=request.data)
user_info.is_valid(raise_exception=True)
token = user_info.validated_data['token']
url = user_info.validated_data['hostname']
if validate_token(token, url) is False:
return Response(status=status.HTTP_401_UNAUTHORIZED, data={"message": "portal authentication was invalid"})
if check_user_email(user_info.validated_data['email']) is True:
return Response(
status=status.HTTP_409_CONFLICT,
data={"message": "A BCODB account with that email already exists"}
)
user = create_bcodb(user_info=user_info.validated_data)
data = json.dumps(get_user_info(user), default=str)
response = send_bcodb(
data=data, request_info=user_info.validated_data
)
if response.status_code == 200:
return Response(status=status.HTTP_201_CREATED, data={"message": "user account created"})



6 changes: 6 additions & 0 deletions authentication/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class Authentication(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "authentication"
25 changes: 25 additions & 0 deletions authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 3.2.10 on 2023-03-27 20:46

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='Authentication',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('auth_service', models.JSONField(default=list)),
('username', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='username')),
],
),
]
File renamed without changes.
13 changes: 13 additions & 0 deletions authentication/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import json
from django.db import models
from django.contrib.auth.models import User

class Authentication(models.Model):
""""""
username = models.ForeignKey(User, on_delete=models.CASCADE, to_field="username")
auth_service = models.JSONField(default=list)


def __username__(self):
"""String for representing the model in Admin site."""
return str(self.username)
78 changes: 78 additions & 0 deletions authentication/selectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# authentication/selectors.py

import jwt
from django.conf import settings
from django.contrib.auth.models import User, Permission
from authentication.models import Authentication
from rest_framework.authtoken.models import Token

def get_user_from_auth_token(token: str)-> User:
"""Get user from Auth Token
"""
payload = jwt.decode(token, None, False)

if payload['iss'] == 'https://orcid.org' or payload['iss'] == 'https://sandbox.orcid.org':
try:
return User.objects.get(username=Authentication.objects.get(auth_service__icontains=payload['iss']).username)
except User.DoesNotExist:
return None
if payload['iss'] == 'accounts.google.com':
try:
return User.objects.get(email=payload['email'])
except User.DoesNotExist:
return None

def check_user_email(email: str)-> bool:
"""Check for user
Using the provided email check for a user in the DB
"""
try:
if User.objects.get(email=email):
return True
except User.DoesNotExist:
return False

def get_user_info(user: User) -> dict:
"""Get User Info
Arguments
---------
user: the user object.
Returns
-------
A dict with the user information.
"""

token = Token.objects.get(user=user.pk)
other_info = {
"permissions": {},
"account_creation": "",
"account_expiration": "",
}
user_perms = {"user": [], "groups": []}

for permission in user.user_permissions.all():
if permission.name not in user_perms["user"]:
user_perms["user"].append(permission.name)

for group in user.groups.all():
if group.name not in user_perms["groups"]:
user_perms["groups"].append(group.name)
for permission in Permission.objects.filter(group=group):
if permission.name not in user_perms["user"]:
user_perms["user"].append(permission.name)

other_info["permissions"] = user_perms

other_info["account_creation"] = user.date_joined

return {
"hostname": settings.ALLOWED_HOSTS[0],
"human_readable_hostname": settings.HUMAN_READABLE_HOSTNAME,
"public_hostname": settings.PUBLIC_HOSTNAME,
"token": token.key,
"username": user.username,
"other_info": other_info,
}
Loading

0 comments on commit 98d3fe0

Please sign in to comment.