diff --git a/server/authentication/apis.py b/server/authentication/apis.py index 7857a6a5..07f39f31 100644 --- a/server/authentication/apis.py +++ b/server/authentication/apis.py @@ -4,7 +4,7 @@ """ import jwt -from bcodb.services import add_authentication +from bcodb.services import add_authentication, remove_authentication from bcodb.selectors import get_all_bcodbs from django.conf import settings from django.contrib.auth.models import User @@ -12,6 +12,7 @@ from django.dispatch import receiver from django.urls import reverse from django_rest_passwordreset.signals import reset_password_token_created +from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import permissions, status, serializers, exceptions from rest_framework.response import Response @@ -79,8 +80,15 @@ class OrcidUserInfoApi(APIView): authentication_classes = [] permission_classes = [] - + authentication = [ + openapi.Parameter( + "Authorization", + openapi.IN_HEADER, + description="Authorization Token", + type=openapi.TYPE_STRING, + )] @swagger_auto_schema( + manual_parameters=authentication, responses={ 200: "Request is successful.", 401: "A user with that ORCID does not exist.", @@ -90,7 +98,11 @@ class OrcidUserInfoApi(APIView): ) def post(self, request): - """ + """Orcid User Info Api + ---------------------- + No schema for this request since only the Authorization header is required. + The word 'Bearer' must be included in the header. + For example: 'Bearer #####################################################' """ if 'Authorization' in request.headers: type, token = request.headers['Authorization'].split(' ') @@ -178,7 +190,18 @@ def post(self, request): auth_code = self.request.GET['code'] orcid_auth = orcid_auth_code(auth_code, path='/profile') if "access_token" not in orcid_auth: - return Response(status=status.HTTP_401_UNAUTHORIZED, data={"message": orcid_auth['error_description']}) + return Response( + status=status.HTTP_401_UNAUTHORIZED, + data={"message": orcid_auth['error_description']} + ) + + conflict = user_from_orcid(orcid_auth['orcid']) + if conflict != 0: + return Response( + status=status.HTTP_403_FORBIDDEN, + data={"message": "A user with that ORCID already exists"}, + ) + user = request.user token = request.headers["Authorization"].removeprefix("Bearer ") profile = profile_from_username(user.username) @@ -190,10 +213,46 @@ def post(self, request): "sub": orcid_auth['orcid'] } for bcodb in bcodbs: - add_authentication(token, auth_obj, bcodb) + add_authentication(auth_obj, bcodb) return Response( - status=status.HTTP_200_OK, data=custom_jwt_handler(token, user) - ) + status=status.HTTP_200_OK, data=custom_jwt_handler(token, user) + ) + +class OrcidRemoveApi(APIView): + """Remove Orcid API + This API view allows for a user to remove an ORCID for OAuth authentication. The + request should include a valid JWT token in the authorization header. + + Returns the updated user information in the response body. + """ + + @swagger_auto_schema( + responses={ + 200: "Add ORCID successful.", + 401: "Unathorized.", + 500: "Error" + }, + tags=["Account Management"], + ) + def post(self, request): + """""" + user = request.user + profile = profile_from_username(user.username) + token = request.headers["Authorization"].removeprefix("Bearer ") + auth_obj = { + "iss": settings.ORCID_URL, + "sub": profile.orcid.split('/')[-1] + } + profile.orcid = "" + profile.save() + bcodbs = get_all_bcodbs(profile) + for bcodb in bcodbs: + remove_authentication(auth_obj, bcodb) + return Response( + status=status.HTTP_200_OK, data=custom_jwt_handler(token, user) + ) + + class GoogleUsername(serializers.CharField): """ Custom serializer field for Google username that removes whitespace from diff --git a/server/authentication/urls.py b/server/authentication/urls.py index 88c5d80f..843cb910 100644 --- a/server/authentication/urls.py +++ b/server/authentication/urls.py @@ -4,7 +4,7 @@ refresh_jwt_token, verify_jwt_token, ) -from authentication.apis import GoogleLoginApi, GoogleRegisterApi, OrcidLoginApi, OrcidUserInfoApi, OrcidAddApi +from authentication.apis import GoogleLoginApi, GoogleRegisterApi, OrcidLoginApi, OrcidUserInfoApi, OrcidAddApi, OrcidRemoveApi from users.apis import UserCreateApi urlpatterns = [ @@ -17,6 +17,7 @@ path("orcid/login/", OrcidLoginApi.as_view()), path("orcid/user_info/", OrcidUserInfoApi.as_view()), path("orcid/add/", OrcidAddApi.as_view()), + path("orcid/remove/", OrcidRemoveApi.as_view()), # path("orcid/register/", GoogleRegisterApi.as_view()), path("password_reset/", include('django_rest_passwordreset.urls', namespace='password_reset')), ] diff --git a/server/bcodb/services.py b/server/bcodb/services.py index e4bb0357..70d0f3d0 100644 --- a/server/bcodb/services.py +++ b/server/bcodb/services.py @@ -70,7 +70,7 @@ def create_bcodb(data: dict) -> BcoDb: return bcodb_serializer.data -def add_authentication(token: str, auth_object: dict, bcodb: BcoDb): +def add_authentication(auth_object: dict, bcodb: BcoDb): """Add Authentication Adds an authentication object to the BCODB object. """ @@ -88,7 +88,7 @@ def add_authentication(token: str, auth_object: dict, bcodb: BcoDb): except Exception as err: print(err) -def remove_authentication(token: str, auth_object: dict, bcodb: BcoDb): +def remove_authentication(auth_object: dict, bcodb: BcoDb): """Remove Authentication Removes an authentication object to the BCODB object. """ @@ -97,7 +97,7 @@ def remove_authentication(token: str, auth_object: dict, bcodb: BcoDb): url=bcodb.public_hostname + "/api/auth/remove/", data=json.dumps(auth_object), headers= { - "Authorization": "Bearer " + token, + "Authorization": "Token " + bcodb.token, "Content-type": "application/json; charset=UTF-8", } )