diff --git a/api/urls.py b/api/urls.py index c4881b36..d413b521 100755 --- a/api/urls.py +++ b/api/urls.py @@ -18,7 +18,6 @@ from drf_yasg import openapi from api.views import ( - ApiAccountsActivateUsernameTempIdentifier, ApiAccountsDescribe, ApiGroupsCreate, ApiGroupsInfo, @@ -127,10 +126,6 @@ ObjectIdRootObjectIdVersion.as_view(), ), path("", ObjectIdRootObjectId.as_view()), - path( - "api/accounts/activate//", - ApiAccountsActivateUsernameTempIdentifier.as_view(), - ), path("api/accounts/describe/", ApiAccountsDescribe.as_view()), path("api/groups/group_info/", ApiGroupsInfo.as_view()), path("api/groups/create/", ApiGroupsCreate.as_view()), diff --git a/api/views.py b/api/views.py index 281a421d..29110d31 100755 --- a/api/views.py +++ b/api/views.py @@ -130,71 +130,6 @@ def check_get(request) -> Response: # Placeholder return Response(status=status.HTTP_200_OK) - -class ApiAccountsActivateUsernameTempIdentifier(APIView): - """ - Activate an account - - -------------------- - - This endpoint is a GET request to activate a new account. - To activate an account during registration we receive an email or a - temporary identifier to authenticate and activate account. This endpoint - will check the validity of the provided temporary identifier for a specific - user account. This is open to anyone to activate a new account, as long as - they have a valid token generated by this host. This allows other users - to act as the verification layer in addition to the system. - - """ - - authentication_classes = [] - permission_classes = [] - - # For the success and error messages - renderer_classes = [TemplateHTMLRenderer] - template_name = "api/account_activation_message.html" - - auth = [] - auth.append( - openapi.Parameter( - "username", - openapi.IN_PATH, - description="Username to be authenticated.", - type=openapi.TYPE_STRING, - ) - ) - auth.append( - openapi.Parameter( - "temp_identifier", - openapi.IN_PATH, - description="The temporary identifier needed to authenticate the activation. This " - "is found in the temporary account table (i.e. where an account is " - "staged).", - type=openapi.TYPE_STRING, - ) - ) - - @swagger_auto_schema( - manual_parameters=auth, - responses={ - 200: "Account has been activated.", - 403: "Requestor's credentials were rejected.", - }, - tags=["Account Management"], - ) - def get(self, request, username: str, temp_identifier: str): - check_get(request) - checked = None - if checked is None: - return GET_activate_account( - username=username, temp_identifier=temp_identifier - ) - else: - return Response( - {"activation_success": False, "status": status.HTTP_400_BAD_REQUEST} - ) - - # Source: https://www.django-rest-framework.org/api-guide/authentication/#by-exposing-an-api-endpoint class ApiAccountsDescribe(APIView): """ diff --git a/authentication/apis.py b/authentication/apis.py index 9d8d9df4..e7f7d9ec 100644 --- a/authentication/apis.py +++ b/authentication/apis.py @@ -18,7 +18,7 @@ create_bcodb_user, send_bcodb, validate_auth_service, - new_user_email + send_new_user_email ) class NewAccountApi(APIView): @@ -33,14 +33,6 @@ class NewAccountApi(APIView): The account create depends on creation of an account in the associated user database. The authentication as well as the user database host information is used to make this request. - - ```JSON - { - "hostname": "http://localhost:8000", - "email": "example_email@example.com", - "token": "eyJ1c2VyX2lkIjoyNCwidXNlcm5hbWUiOiJoYWRsZXlraW5nIiwiZXhwIjoxNjQwNzE5NTUwLCJlbWFpbCI6ImhhZGxleV9raW5nQGd3dS5lZHUiLCJvcmlnX2lhdCI6MTY0MDExNDc1MH0.7G3VPmxUBOWFfu-fMt1_UsWAcH_Gd1DfpQa83EwFwYY" - } - ``` """ class InputSerializer(serializers.Serializer): @@ -109,6 +101,7 @@ def post(self, request) -> Response: status=status.HTTP_201_CREATED, data={"message":"Testing account request successful!!"} ) + if check_user_email(email) is True: return Response( status=status.HTTP_409_CONFLICT, @@ -129,14 +122,84 @@ def post(self, request) -> Response: ) try: - new_user_email(serializer.validated_data) - return Response(status=status.HTTP_201_CREATED, data={"message":""}) + send_new_user_email(serializer.validated_data) + return Response( + status=status.HTTP_201_CREATED, + data={"message":"Account request granted. Check your email"\ + + " for an activation link."} + ) except Exception as error: return Response( status=status.HTTP_500_INTERNAL_SERVER_ERROR, data={"message": str(error)} ) +class AccountActivateApi(APIView): + """ + Activate an account + + -------------------- + + This endpoint is a GET request to activate a new account. + To activate an account during registration the userwill receive an email + or a temporary identifier to authenticate and activate account. This + endpoint will check the validity of the provided temporary identifier for + a specific user account. This is open to anyone to activate a new account, + as long as they have a valid token generated by this host. This can allow + other users to act as the verification layer in addition to the system. + """ + + authentication_classes = [] + permission_classes = [] + + auth = [] + auth.append( + openapi.Parameter( + "username", + openapi.IN_PATH, + description="Username to be authenticated.", + type=openapi.TYPE_STRING, + default="test@test.test" + ) + ) + auth.append( + openapi.Parameter( + "temp_identifier", + openapi.IN_PATH, + description="The temporary identifier sent", + type=openapi.TYPE_STRING, + default="testTempIdentifier123456789" + ) + ) + + @swagger_auto_schema( + manual_parameters=auth, + responses={ + 200: "Account has been activated.", + 403: "Requestor's credentials were rejected.", + }, + tags=["Authentication and Account Management"], + ) + + def get(self, request, username: str, temp_identifier: str) -> Response: + if check_user_email(username) is True: + return Response( + status=status.HTTP_409_CONFLICT, + data={ + "message":f"CONFLICT: That account, {username}, has already "\ + + "been activated." + } + ) + new_user = check_new_user(username, temp_identifier) + print(new_user) + create_bcodb_user(new_user.email) + new_user.delete() + return Response( + status=status.HTTP_200_OK, + data={"message":f"Account for {username} has been activated"} + ) + + class RegisterUserNoVerificationAPI(APIView): """Register BCODB API View to register a new BCODB user with out an email verification step. @@ -181,14 +244,15 @@ def post(self, request): user_info.is_valid(raise_exception=True) token = user_info.validated_data['token'] url = user_info.validated_data['hostname'] + email = user_info.validated_data['email'] 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: + if check_user_email(email) is True: return Response( status=status.HTTP_409_CONFLICT, data={"message": "A BCODB account with that email already exists"} ) - user = create_bcodb_user(user_info=user_info.validated_data) + user = create_bcodb_user(email) data = json.dumps(get_user_info(user), default=str) response = send_bcodb( data=data, request_info=user_info.validated_data diff --git a/authentication/selectors.py b/authentication/selectors.py index 030fdb84..a4595f65 100644 --- a/authentication/selectors.py +++ b/authentication/selectors.py @@ -45,14 +45,18 @@ def check_user_email(email: str)-> bool: except User.DoesNotExist: return False -def check_new_user(email: str)-> bool: +def check_new_user(email: str, temp_identifier:str=None): """Check for new user - Using the provided email check for a new user in the DB + Using the provided email check for a new user in the DB. + If the temp id is supplied and matches it will return the new user object. """ - + try: - if NewUser.objects.get(email=email): + new_user = NewUser.objects.get(email=email) + if new_user.temp_identifier == temp_identifier: + return new_user + else: return True except NewUser.DoesNotExist: return False diff --git a/authentication/services.py b/authentication/services.py index acf0d2ce..6b542608 100644 --- a/authentication/services.py +++ b/authentication/services.py @@ -153,7 +153,7 @@ def validate_token(token: str, url: str)-> bool: return True @transaction.atomic -def new_user_email(user_info: dict) -> 0: +def send_new_user_email(user_info: dict) -> 0: """Send New User Email New BCODB user authentication email @@ -171,7 +171,7 @@ def new_user_email(user_info: dict) -> 0: subject="Registration for BioCompute Portal", message="Testing.", html_message='

Please click this link within the next' \ - + ' 10 minutes to activate your BioCompute Portal account: ' \ + + ' 24 hours to activate your BioCompute Portal account: ' \ + f'{activation_link}' \ + '.

', from_email="mail_sender@portal.aws.biochemistry.gwu.edu", @@ -182,13 +182,13 @@ def new_user_email(user_info: dict) -> 0: print("Email signal sent") return 0 -def create_bcodb_user(user_info: dict) -> User: +def create_bcodb_user(email: str) -> User: """Create BCODB user """ - username = user_info["email"].split("@")[0] + username = email.split("@")[0] user = User.objects.create_user( - username=username, email=user_info["email"] + username=username, email=email ) user.set_unusable_password() user.full_clean() diff --git a/authentication/urls.py b/authentication/urls.py index e5a62ff5..f0774221 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -1,10 +1,23 @@ # authentication/urls.py from django.urls import path -from authentication.apis import RegisterBcodbAPI, AddAuthenticationApi, RemoveAuthenticationApi, ResetTokenApi +from authentication.apis import ( + NewAccountApi, + AccountActivateApi, + RegisterUserNoVerificationAPI, + AddAuthenticationApi, + RemoveAuthenticationApi, + ResetTokenApi +) urlpatterns = [ - path("auth/register/", RegisterBcodbAPI.as_view()), + path( + "accounts/activate//", + AccountActivateApi.as_view(), + ), + # path("api/accounts/describe/", ApiAccountsDescribe.as_view()), + path("accounts/new/", NewAccountApi.as_view()), + path("auth/register/", RegisterUserNoVerificationAPI.as_view()), path("auth/add/", AddAuthenticationApi.as_view()), path("auth/remove/", RemoveAuthenticationApi.as_view()), path("auth/reset_token/", ResetTokenApi.as_view())