From 3f5ff7d45cf25b0dd48d69befb09a81c4db2cce3 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Thu, 18 Jan 2024 10:43:39 -0500 Subject: [PATCH 001/222] [ALS-5508] Dependabot dep bumps - httpd - Jackson - deleted some commented out stuff --- pic-sure-auth-services/pom.xml | 59 +++------------------------------- 1 file changed, 5 insertions(+), 54 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index ed466699f..b57dca54c 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -11,8 +11,8 @@ pic-sure-auth-services war - - 2.10.0 + 2.12.7 + 2.12.7.1 2.3.0 @@ -32,7 +32,7 @@ org.apache.httpcomponents httpclient - 4.5.6 + 4.5.13 io.jsonwebtoken @@ -71,7 +71,7 @@ org.hibernate hibernate-core - 5.3.1.Final + 5.3.20.Final provided @@ -79,16 +79,6 @@ hibernate-envers 5.2.5.Final - - - - - - - - - - com.fasterxml.jackson.core jackson-core @@ -97,23 +87,8 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson.version.fourdigit} - - - - - - - - - - - - - - - org.springframework spring-web @@ -199,30 +174,6 @@ swagger-jaxrs2-servlet-initializer 2.0.0 - - - - - - - - - - - - - - - - - - - - - - ${project.artifactId} From 0da626ce38c322c327716a13ea40a74c8764ecf6 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Thu, 17 Aug 2023 10:15:39 -0400 Subject: [PATCH 002/222] Label checker GH action --- .github/workflows/label-checker.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/label-checker.yml diff --git a/.github/workflows/label-checker.yml b/.github/workflows/label-checker.yml new file mode 100644 index 000000000..abfc09b3c --- /dev/null +++ b/.github/workflows/label-checker.yml @@ -0,0 +1,21 @@ +name: Label Checker +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +jobs: + + check_labels: + name: Check labels + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:latest + with: + one_of: breaking-change,enhancement,bug,documentation,ignore-for-release + repo_token: ${{ secrets.GITHUB_TOKEN }} + From bac5e6d153be6fb2b48f4ebbe5e064f976ed9939 Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Tue, 19 Dec 2023 11:21:37 -0500 Subject: [PATCH 003/222] Add deps to label checker + release config --- .github/release.yml | 20 ++++++++++++++++++++ .github/workflows/label-checker.yml | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..0db6cd02d --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,20 @@ +changelog: + exclude: + labels: + - ignore-for-release + categories: + - title: Breaking Changes + labels: + - breaking-change + - title: Features + labels: + - feature + - title: Enhancements + labels: + - enhancement + - title: Bug Fixes + labels: + - bug + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/label-checker.yml b/.github/workflows/label-checker.yml index abfc09b3c..2bd9019bf 100644 --- a/.github/workflows/label-checker.yml +++ b/.github/workflows/label-checker.yml @@ -16,6 +16,6 @@ jobs: steps: - uses: docker://agilepathway/pull-request-label-checker:latest with: - one_of: breaking-change,enhancement,bug,documentation,ignore-for-release + one_of: breaking-change,enhancement,bug,documentation,ignore-for-release,dependencies repo_token: ${{ secrets.GITHUB_TOKEN }} From 4a4d1c20732caa3812d818a97861d6f92d3a7c75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:18:48 +0000 Subject: [PATCH 004/222] Bump com.jayway.jsonpath:json-path in /pic-sure-auth-services Bumps [com.jayway.jsonpath:json-path](https://github.com/jayway/JsonPath) from 2.4.0 to 2.9.0. - [Release notes](https://github.com/jayway/JsonPath/releases) - [Changelog](https://github.com/json-path/JsonPath/blob/master/changelog.md) - [Commits](https://github.com/jayway/JsonPath/compare/json-path-2.4.0...json-path-2.9.0) --- updated-dependencies: - dependency-name: com.jayway.jsonpath:json-path dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- pic-sure-auth-services/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index b57dca54c..4a753e76c 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -119,7 +119,7 @@ com.jayway.jsonpath json-path - 2.4.0 + 2.9.0 org.mockito From f58525cce34666f21c5eaa86e60b0652bb5a5fe3 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:18:19 -0500 Subject: [PATCH 005/222] [ALS-5612] Create stored procedure to create a user (#154) * Add a new stored procedure to enable user creation * Specific database Auth * Update CreateUserWithRole stored procedure in auth DB The stored procedure, CreateUserWithRole, in the 'auth' database has been updated to improve user creation. It now checks for existing users and roles, and generates a new UUID if needed. Additionally, it associates new users with roles if they exist. * Rename CreateUserWithRole stored procedure file * Add general metadata parameter to CreateUserWithRole procedure --- .../V4__ADD_CREATE_USER_STORED_PROCEDURE.sql | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql diff --git a/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql b/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql new file mode 100644 index 000000000..53c550fad --- /dev/null +++ b/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql @@ -0,0 +1,32 @@ +USE `auth`; + +DROP PROCEDURE IF EXISTS CreateUserWithRole; +DELIMITER // +CREATE PROCEDURE CreateUserWithRole ( + IN user_email VARCHAR(255), + IN connection_id VARCHAR(255), + IN role_name VARCHAR(255), + IN user_general_metadata varchar(255) +) +BEGIN + -- Attempt to retrieve the UUIDs for the user and role based on the provided information +SELECT @userUUID := uuid FROM auth.user WHERE email = user_email AND connectionId = connection_id; +SELECT @roleUUID := uuid FROM auth.role WHERE name = role_name; + +-- If the user does not exist, create a new user entry +IF @userUUID IS NULL THEN + -- Generate a new UUID for the user + SET @userUUID = UNHEX(REPLACE(UUID(), '-', '')); + -- Retrieve the UUID for the connection +SELECT @connectionUUID := uuid FROM auth.connection WHERE id = connection_id; +-- Insert the new user record into the user table +INSERT INTO auth.user (uuid, general_metadata, acceptedTOS, connectionId, email, matched, subject, is_active, long_term_token) +VALUES (@userUUID, user_general_metadata, CURRENT_TIMESTAMP, @connectionUUID, user_email, 0, NULL, 1, NULL); +END IF; + + -- If the role exists, associate the user with the role + IF @roleUUID IS NOT NULL THEN + INSERT INTO auth.user_role (user_id, role_id) VALUES (@userUUID, @roleUUID); +END IF; +END// +DELIMITER ; \ No newline at end of file From 0ed9bf07f19de71aef7bc5307d79d006709e7343 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:58:11 -0500 Subject: [PATCH 006/222] [ALS-5612] Updated stored procedure (#155) * Add connectionSubPrefix to user creation stored procedure * Refactor connection prefix manipulation in SQL procedure * Update user creation stored procedure The changes made address the process of user creation in the stored procedure. A new variable, @baseUUID, has been introduced for storing UUIDs during processing. Additionally, the preparation of @connectionSubPrefix has been adjusted to concatenate LONG_TERM_TOKEN with existing values instead of overriding them. * Add PIC-SURE User role assignment in CreateUserWithRole procedure Improved the CreateUserWithRole stored procedure in the auth-db. All new users are now automatically assigned the 'PIC-SURE User' role in addition to specific roles designated during account creation. This ensures all users have access to the base level of functionalities. --- .../V4__ADD_CREATE_USER_STORED_PROCEDURE.sql | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql b/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql index 53c550fad..af57999c5 100644 --- a/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql +++ b/pic-sure-auth-db/db/sql/V4__ADD_CREATE_USER_STORED_PROCEDURE.sql @@ -2,7 +2,7 @@ USE `auth`; DROP PROCEDURE IF EXISTS CreateUserWithRole; DELIMITER // -CREATE PROCEDURE CreateUserWithRole ( +CREATE PROCEDURE CreateUserWithRole( IN user_email VARCHAR(255), IN connection_id VARCHAR(255), IN role_name VARCHAR(255), @@ -10,23 +10,34 @@ CREATE PROCEDURE CreateUserWithRole ( ) BEGIN -- Attempt to retrieve the UUIDs for the user and role based on the provided information -SELECT @userUUID := uuid FROM auth.user WHERE email = user_email AND connectionId = connection_id; -SELECT @roleUUID := uuid FROM auth.role WHERE name = role_name; + SELECT @userUUID := uuid FROM auth.user WHERE email = user_email AND connectionId = connection_id; + SELECT @roleUUID := uuid FROM auth.role WHERE name = role_name; + SELECT @picsureUserRoleId := uuid FROM auth.role WHERE name = 'PIC-SURE User'; -- If the user does not exist, create a new user entry -IF @userUUID IS NULL THEN + IF @userUUID IS NULL THEN + set @baseUUID = UUID(); -- Generate a new UUID for the user - SET @userUUID = UNHEX(REPLACE(UUID(), '-', '')); + SET @userUUID = UNHEX(REPLACE(@baseUUID, '-', '')); -- Retrieve the UUID for the connection -SELECT @connectionUUID := uuid FROM auth.connection WHERE id = connection_id; + SELECT @connectionUUID := uuid FROM auth.connection WHERE id = connection_id; + SELECT @connectionSubPrefix := subPrefix FROM auth.connection WHERE id = connection_id; -- Insert the new user record into the user table -INSERT INTO auth.user (uuid, general_metadata, acceptedTOS, connectionId, email, matched, subject, is_active, long_term_token) -VALUES (@userUUID, user_general_metadata, CURRENT_TIMESTAMP, @connectionUUID, user_email, 0, NULL, 1, NULL); -END IF; + INSERT INTO auth.user (uuid, general_metadata, acceptedTOS, connectionId, email, matched, subject, is_active, + long_term_token) + VALUES (@userUUID, user_general_metadata, CURRENT_TIMESTAMP, @connectionUUID, user_email, 0, + concat(@connectionSubPrefix, REPLACE(@baseUUID, '-', '')), 1, NULL); + END IF; -- If the role exists, associate the user with the role IF @roleUUID IS NOT NULL THEN INSERT INTO auth.user_role (user_id, role_id) VALUES (@userUUID, @roleUUID); -END IF; + END IF; + + -- If the role is not PIC-SURE User, associate the user with the PIC-SURE User role as well + -- All users must have the PIC-SURE User role + IF @roleUUID IS NOT NULL AND @roleUUID != @picsureUserRoleId THEN + INSERT INTO auth.user_role (user_id, role_id) VALUES (@userUUID, @picsureUserRoleId); + END IF; END// DELIMITER ; \ No newline at end of file From 6403f7c21f529c8518bdf55f03e720caad361e26 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 20 Mar 2024 16:37:55 -0400 Subject: [PATCH 007/222] Start major refactor of PSAMA - Updated target java from 11 to 21 - Updated Spring dependencies to 3 or 6 based on the library - Started re-organizing the code base so there is an appropriate separation of concerns. The means we will have a repositories, services, controllers, configuration, filters, and more separated appropriately. - Fixed naming conventions and started to remove javax to jakarta or spring dependencies instead. --- pic-sure-auth-services/Dockerfile | 2 +- pic-sure-auth-services/bak/README.md | 1 + .../META-INF => bak}/persistence.xml | 2 +- .../standalone.xml | 0 pic-sure-auth-services/pom.xml | 236 ++---- .../hms/dbmi/avillach/auth/Application.java | 11 + .../avillach/auth/JAXRSConfiguration.java | 20 +- .../auth/data/entity/package-info.java | 4 - .../UserMetadataMappingRepository.java | 50 -- .../auth/{data => }/entity/AccessRule.java | 2 +- .../auth/{data => }/entity/Application.java | 2 +- .../auth/{data => }/entity/Connection.java | 2 +- .../auth/{data => }/entity/Privilege.java | 2 +- .../avillach/auth/{data => }/entity/Role.java | 3 +- .../{data => }/entity/TermsOfService.java | 2 +- .../avillach/auth/{data => }/entity/User.java | 699 +++++++++--------- .../entity/UserMetadataMapping.java | 2 +- .../avillach/auth/entity/package-info.java | 4 + .../exceptions/NotAuthorizedException.java | 7 + .../dbmi/avillach/auth/filter/JWTFilter.java | 206 ++++++ .../auth/{service => model}/AccessEmail.java | 6 +- .../auth/model/response/PICSUREResponse.java | 83 +++ .../repository/AccessRuleRepository.java | 10 +- .../repository/ApplicationRepository.java | 13 +- .../repository/ConnectionRepository.java | 21 +- .../repository/PrivilegeRepository.java | 12 +- .../{data => }/repository/RoleRepository.java | 11 +- .../repository/TermsOfServiceRepository.java | 14 +- .../UserMetadataMappingRepository.java | 27 + .../{data => }/repository/UserRepository.java | 28 +- .../{data => }/repository/package-info.java | 2 +- ...Service.java => AccessRuleController.java} | 62 +- .../auth/rest/ApplicationController.java | 86 +++ .../auth/rest/ApplicationService.java | 187 ----- .../avillach/auth/rest/AuthController.java | 55 ++ .../dbmi/avillach/auth/rest/AuthService.java | 54 -- .../auth/rest/ConnectionWebController.java | 83 +++ .../auth/rest/ConnectionWebService.java | 102 --- .../auth/rest/PrivilegeController.java | 77 ++ .../avillach/auth/rest/PrivilegeService.java | 107 --- .../{RoleService.java => RoleController.java} | 26 +- ...int.java => TermsOfSerivceController.java} | 18 +- .../avillach/auth/rest/TokenController.java | 319 ++++++++ .../dbmi/avillach/auth/rest/TokenService.java | 325 -------- .../{UserService.java => UserController.java} | 302 ++++---- ... => UserMetadataMappingWebController.java} | 25 +- .../auth/security/AuthSecurityContext.java | 98 +-- .../avillach/auth/security/JWTFilter.java | 260 ------- .../auth/security/SecurityConfig.java | 39 + .../service/OauthUserMatchingService.java | 134 ---- .../auth/service/PrivilegeService.java | 68 ++ .../service/UserMetadataMappingService.java | 57 -- .../service/auth/AuthenticationService.java | 158 ---- .../auth/AuthenticationService.java.orig | 143 ---- .../auth/service/auth/package-info.java | 4 - .../auth/service/impl/ApplicationService.java | 142 ++++ .../service/impl/AuthenticationService.java | 150 ++++ .../{auth => impl}/AuthorizationService.java | 38 +- .../service/{ => impl}/BaseEntityService.java | 43 +- .../service/impl/ConnectionWebService.java | 51 ++ .../FENCEAuthenticationService.java | 81 +- .../auth/service/{ => impl}/MailService.java | 9 +- .../impl/OauthUserMatchingService.java | 126 ++++ .../auth/service/{ => impl}/TOSService.java | 55 +- .../impl/UserMetadataMappingService.java | 57 ++ .../auth/service/impl/UserService.java | 67 ++ .../auth/service/impl/package-info.java | 1 + .../dbmi/avillach/auth/utils/AuthUtils.java | 127 ---- .../hms/dbmi/avillach/auth/utils/JWTUtil.java | 95 ++- ...itional-spring-configuration-metadata.json | 118 +++ .../src/main/resources/application.properties | 50 ++ .../src/main/webapp/META-INF/context.xml | 2 - .../src/main/webapp/WEB-INF/beans.xml | 6 - .../dbmi/avillach/ApplicationServiceTest.java | 12 +- .../avillach/Auth0MatchingServiceTest.java | 38 +- .../avillach/AuthorizationServiceTest.java | 4 +- .../AuthorizationServiceTestByUseCases.java | 4 +- 77 files changed, 2788 insertions(+), 2761 deletions(-) create mode 100644 pic-sure-auth-services/bak/README.md rename pic-sure-auth-services/{src/main/resources/META-INF => bak}/persistence.xml (92%) rename pic-sure-auth-services/{src/main/resources/wildfly-configuration => bak}/standalone.xml (100%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/package-info.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserMetadataMappingRepository.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/AccessRule.java (99%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/Application.java (98%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/Connection.java (96%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/Privilege.java (98%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/Role.java (92%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/TermsOfService.java (93%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/User.java (94%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/entity/UserMetadataMapping.java (95%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/package-info.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/exceptions/NotAuthorizedException.java create mode 100755 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{service => model}/AccessEmail.java (87%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/AccessRuleRepository.java (59%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/ApplicationRepository.java (52%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/ConnectionRepository.java (65%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/PrivilegeRepository.java (53%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/RoleRepository.java (57%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/TermsOfServiceRepository.java (71%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/UserRepository.java (86%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{data => }/repository/package-info.java (54%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{AccessRuleService.java => AccessRuleController.java} (71%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeService.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{RoleService.java => RoleController.java} (86%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{TermsOfServiceEndpoint.java => TermsOfSerivceController.java} (75%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenService.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{UserService.java => UserController.java} (67%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{UserMetadataMappingWebService.java => UserMetadataMappingWebController.java} (77%) delete mode 100755 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/JWTFilter.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/OauthUserMatchingService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/UserMetadataMappingService.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java.orig delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/package-info.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/{auth => impl}/AuthorizationService.java (95%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/{ => impl}/BaseEntityService.java (85%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/{auth => impl}/FENCEAuthenticationService.java (89%) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/{ => impl}/MailService.java (94%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/{ => impl}/TOSService.java (50%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/package-info.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java create mode 100644 pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 pic-sure-auth-services/src/main/resources/application.properties delete mode 100644 pic-sure-auth-services/src/main/webapp/META-INF/context.xml delete mode 100644 pic-sure-auth-services/src/main/webapp/WEB-INF/beans.xml diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index edf2df048..41b42c7d8 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -30,7 +30,7 @@ RUN wildfly/bin/jboss-cli.sh --command="module add --name=com.sql.mysql \ --resources=/modules/mysql-connector-java-5.1.38.jar --dependencies=javax.api" # Copy standalone.xml -COPY src/main/resources/wildfly-configuration/standalone.xml wildfly/standalone/configuration/ +COPY bak/standalone.xml wildfly/standalone/configuration/ # Copy war file COPY target/pic-sure-auth-services.war wildfly/standalone/deployments/pic-sure-auth-services.war diff --git a/pic-sure-auth-services/bak/README.md b/pic-sure-auth-services/bak/README.md new file mode 100644 index 000000000..0b4e9d4ce --- /dev/null +++ b/pic-sure-auth-services/bak/README.md @@ -0,0 +1 @@ +# This is a temporary location to keep files that I intend to delete, but want to keep around for a while just in case. \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/resources/META-INF/persistence.xml b/pic-sure-auth-services/bak/persistence.xml similarity index 92% rename from pic-sure-auth-services/src/main/resources/META-INF/persistence.xml rename to pic-sure-auth-services/bak/persistence.xml index 4955d4789..bee8e1af6 100644 --- a/pic-sure-auth-services/src/main/resources/META-INF/persistence.xml +++ b/pic-sure-auth-services/bak/persistence.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> java:jboss/datasources/AuthDS - edu.harvard.hms.dbmi.avillach.auth.data.entity.User + edu.harvard.hms.dbmi.avillach.auth.entity.User diff --git a/pic-sure-auth-services/src/main/resources/wildfly-configuration/standalone.xml b/pic-sure-auth-services/bak/standalone.xml similarity index 100% rename from pic-sure-auth-services/src/main/resources/wildfly-configuration/standalone.xml rename to pic-sure-auth-services/bak/standalone.xml diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 4a753e76c..8d5f6a954 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 pic-sure-auth-microapp @@ -12,88 +12,99 @@ war 2.12.7 - 2.12.7.1 2.3.0 - - - edu.harvard.hms.dbmi.avillach - pic-sure-util - 2.1.0-SNAPSHOT - - + edu.harvard.hms.dbmi.avillach pic-sure-api-data 2.1.0-SNAPSHOT + + + org.apache.logging.log4j + log4j-core + + + org.slf4j + slf4j-log4j12 + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.slf4j + slf4j-jdk14 + + + + - org.apache.httpcomponents - httpclient - 4.5.13 + org.springframework.boot + spring-boot + 3.2.3 + - io.jsonwebtoken - jjwt - 0.9.0 - - - javax - javaee-api - 8.0 - provided - - - org.hibernate.javax.persistence - hibernate-jpa-2.1-api - 1.0.2.Final - provided + org.springframework + spring-web + 6.1.5 + - org.hibernate - hibernate-annotations - 3.5.6-Final - provided + org.springframework.boot + spring-boot-starter-web + 3.2.3 - org.hibernate.javax.persistence - hibernate-jpa-2.0-api + org.springframework.boot + spring-boot-starter-logging + - com.auth0 - auth0 - 1.8.0 + org.springframework.boot + spring-boot-autoconfigure + 3.2.3 - org.hibernate - hibernate-core - 5.3.20.Final - provided + org.springframework.boot + spring-boot-starter-security + 3.2.3 + + + - org.hibernate - hibernate-envers - 5.2.5.Final + com.mysql + mysql-connector-j + 8.3.0 - com.fasterxml.jackson.core - jackson-core - ${jackson.version} + io.jsonwebtoken + jjwt + 0.9.0 - com.fasterxml.jackson.core - jackson-databind - ${jackson.version.fourdigit} + com.auth0 + auth0 + 1.8.0 - org.springframework - spring-web - 4.1.9.RELEASE + org.hibernate + hibernate-core + 5.3.20.Final + provided + junit junit @@ -101,32 +112,21 @@ test - - javax.xml.bind - jaxb-api - ${xml.bind.version} - - - com.sun.xml.bind - jaxb-core - ${xml.bind.version} - - - com.sun.xml.bind - jaxb-impl - ${xml.bind.version} - + com.jayway.jsonpath json-path 2.9.0 - - org.mockito - mockito-core - 2.23.4 - test - + + + org.mockito + mockito-core + 2.23.4 + test + + + com.github.spullara.mustache.java compiler @@ -174,6 +174,11 @@ swagger-jaxrs2-servlet-initializer 2.0.0 + + edu.harvard.hms.dbmi.avillach.picsure.auth.microapp + pic-sure-auth-microapp + 1.0-SNAPSHOT + ${project.artifactId} @@ -182,9 +187,9 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.11.0 - 11 + 21 @@ -195,34 +200,6 @@ false - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -271,59 +248,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java new file mode 100644 index 000000000..bd6737731 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java @@ -0,0 +1,11 @@ +package edu.harvard.hms.dbmi.avillach.auth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java index a89c0e3fd..a4845dd95 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java @@ -2,16 +2,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.type.MapType; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.TokenService; import io.swagger.jaxrs.config.BeanConfig; -import org.apache.commons.io.IOUtils; import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.slf4j.Logger; @@ -28,13 +26,7 @@ import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import javax.ws.rs.core.SecurityContext; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.*; -import javax.net.ssl.*; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/package-info.java deleted file mode 100644 index 718deb3b9..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Contains the entity objects for PSAMA. - */ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserMetadataMappingRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserMetadataMappingRepository.java deleted file mode 100644 index 0b939d5db..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserMetadataMappingRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; - -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.UserMetadataMapping; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; -import javax.transaction.Transactional; -import java.util.List; -import java.util.UUID; - -/** - *

Provides operations for the UserMetadataMapping entity to interact with a database.

- * @see UserMetadataMapping - */ -@Transactional -@ApplicationScoped -public class UserMetadataMappingRepository extends BaseRepository { - - @Inject - UserMetadataMappingRepository userMetadataMappingRepository; - - private Logger logger = LoggerFactory.getLogger(UserMetadataMappingRepository.class); - - protected UserMetadataMappingRepository() { - super(UserMetadataMapping.class); - } - - public List findByConnection(Connection connection) { - return userMetadataMappingRepository.getByColumn("connection", connection); - -// CriteriaBuilder cb = cb(); -// CriteriaQuery query = cb.createQuery(UserMetadataMapping.class); -// Root queryRoot = query.from(UserMetadataMapping.class); -// query.select(queryRoot); -// Join connectionJoin = queryRoot.join("connection"); -// return em.createQuery(query -// .where( -// cb.equal(connectionJoin.get("uuid"), connectionId))) -// .getResultList(); - } - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/AccessRule.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java similarity index 99% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/AccessRule.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java index ac3e4098c..bbb6a98fa 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/AccessRule.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java similarity index 98% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Application.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 615291691..36981cfd3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonInclude; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Connection.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java similarity index 96% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Connection.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java index e069dfa85..4c7e92a50 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Connection.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Privilege.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java similarity index 98% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Privilege.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java index c5ce1f65d..7076d1b9d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Privilege.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Role.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java similarity index 92% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Role.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java index 54320b61b..529e74657 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/Role.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java @@ -1,6 +1,5 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/TermsOfService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java similarity index 93% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/TermsOfService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java index 5ff8d2be1..f7615b68f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/TermsOfService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java similarity index 94% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/User.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 87a750595..66765f564 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -1,349 +1,350 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; -import org.hibernate.annotations.Type; - -import javax.persistence.*; -import java.io.Serializable; -import java.security.Principal; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * Defines a model of User behavior. - */ -@JsonInclude(JsonInclude.Include.NON_EMPTY) -@Entity(name = "user") -public class User extends BaseEntity implements Serializable, Principal { - - @Column(unique = true) - private String subject; - - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "user_role", - joinColumns = {@JoinColumn(name = "user_id", nullable = false, updatable = false)}, - inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false, updatable = false)}) - private Set roles; - - private String email; - - - /** - *

NOTICE

- *

- * When you update or create a user, - * please use connection.id as the input. The UserService is specifically using connection.id. - *

- *
- *

- * Note: This is because of the checkAssociation() method in UserService. - *


- * @see edu.harvard.hms.dbmi.avillach.auth.rest.UserService - */ - @ManyToOne - @JoinColumn(name = "connectionId") - private Connection connection; - - private boolean matched; - - private Date acceptedTOS; - - @Column(name = "auth0_metadata") - @Type(type = "text") - private String auth0metadata; - - @Column(name = "general_metadata") - @Type(type = "text") - private String generalMetadata; - - @Column(name = "is_active") - private boolean active = true; - - @Column(name = "long_term_token") - private String token; - - public String getSubject() { - return subject; - } - - public User setSubject(String subject) { - this.subject = subject; - return this; - } - - public Set getRoles() { - return roles; - } - - public User setRoles(Set roles) { - this.roles = roles; - return this; - } - - /** - * return all privileges in the roles as a set - * @return - */ - @JsonIgnore - public Set getTotalPrivilege(){ - if (roles == null) - return null; - - Set privileges = new HashSet<>(); - roles.stream().forEach(r -> privileges.addAll(r.getPrivileges())); - return privileges; - } - - /** - * return all privileges in the roles as a set - * @return - */ - @JsonIgnore - public Set getTotalAccessRule(){ - if (roles == null) - return null; - - Set accessRules = new HashSet<>(); - roles.stream(). - forEach(r -> r.getPrivileges().stream(). - forEach(p -> accessRules.addAll(p.getAccessRules()))); - return accessRules; - } - - /** - * return all privilege name in each role as a set. - * - * @return - */ - @JsonIgnore - public Set getPrivilegeNameSet(){ - Set totalPrivilegeSet = getTotalPrivilege(); - - if (totalPrivilegeSet == null) - return null; - - Set nameSet = new HashSet<>(); - totalPrivilegeSet.stream().forEach(p -> nameSet.add(p.getName())); - return nameSet; - } - - /** - * return privilege names in each role as a set based on Application given. - * - * @return - */ - @JsonIgnore - public Set getPrivilegeNameSetByApplication(Application application){ - Set totalPrivilegeSet = getTotalPrivilege(); - - if (totalPrivilegeSet == null) - return null; - - Set nameSet = new HashSet<>(); - if (application == null) - return nameSet; - - for (Privilege appPrivilege : application.getPrivileges()) { - for (Privilege userPrivilege : totalPrivilegeSet) { - if (appPrivilege.equals(userPrivilege)) - nameSet.add(userPrivilege.getName()); - } - } - return nameSet; - } - - /** - * return privileges in each role as a set based on Application given. - * - * @return - */ - @JsonIgnore - public Set getPrivilegesByApplication(Application application){ - if (application == null || application.getUuid() == null){ - return getTotalPrivilege(); - } - - if (roles == null) - return null; - - Set privileges = new HashSet<>(); - roles.stream(). - forEach(r -> privileges.addAll(r.getPrivileges() - .stream() - .filter(p -> application.getUuid() - .equals((p.getApplication()==null)? - null: - p.getApplication().getUuid())) - .collect(Collectors.toSet()))); - return privileges; - } - - @JsonIgnore - public String getPrivilegeString(){ - Set totalPrivilegeSet = getTotalPrivilege(); - - if (totalPrivilegeSet == null) - return null; - - return totalPrivilegeSet.stream().map(p -> p.getName()).collect(Collectors.joining(",")); - } - - @JsonIgnore - public String getRoleString(){ - return (roles==null)?null:roles.stream().map(r -> r.name) - .collect(Collectors.joining(",")); - } - - public String getAuth0metadata() { - return auth0metadata; - } - - public User setAuth0metadata(String auth0metadata) { - this.auth0metadata = auth0metadata; - return this; - } - - public String getGeneralMetadata() { - return generalMetadata; - } - - public User setGeneralMetadata(String generalMetadata) { - this.generalMetadata = generalMetadata; - return this; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public Connection getConnection() { - return connection; - } - - public User setConnection(Connection connection) { - this.connection = connection; - return this; - } - - public boolean isMatched() { - return matched; - } - - public void setMatched(boolean matched) { - this.matched = matched; - } - - public Date getAcceptedTOS() { - return acceptedTOS; - } - - public void setAcceptedTOS(Date acceptedTOS) { - this.acceptedTOS = acceptedTOS; - } - - @JsonIgnore - @Override - public String getName() { - return this.subject; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - /** - *

Inner class defining limited user attributes returned from the User endpoint.

- */ - @JsonInclude(JsonInclude.Include.NON_EMPTY) - public static class UserForDisplay { - String uuid; - String email; - Set privileges; - String token; - Set queryScopes; - private boolean acceptedTOS; - - public UserForDisplay() { - } - - public String getEmail() { - return email; - } - - public UserForDisplay setEmail(String email) { - this.email = email; - return this; - } - - public Set getPrivileges() { - return privileges; - } - - public UserForDisplay setPrivileges(Set privileges) { - this.privileges = privileges; - return this; - } - - public String getUuid() { - return uuid; - } - - public UserForDisplay setUuid(String uuid) { - this.uuid = uuid; - return this; - } - - public String getToken() { - return token; - } - - public UserForDisplay setToken(String token) { - this.token = token; - return this; - } - - public Set getQueryScopes() { - return queryScopes; - } - - public void setQueryScopes(Set queryScopes) { - this.queryScopes = queryScopes; - } - - public boolean getAcceptedTOS() { - return acceptedTOS; - } - - public UserForDisplay setAcceptedTOS(boolean acceptedTOS) { - this.acceptedTOS = acceptedTOS; - return this; - } - } - - public String toString() { - if(uuid == null) { - return "No UUID assigned___ " + subject + " ___ " + email + " ___ " + generalMetadata + " ___ " + auth0metadata + " ___ {" + ((connection==null)?null:connection.toString()) + "}"; - } - return uuid.toString() + " ___ " + subject + " ___ " + email + " ___ " + generalMetadata + " ___ " + auth0metadata + " ___ {" + ((connection==null)?null:connection.toString()) + "}"; - } -} +package edu.harvard.hms.dbmi.avillach.auth.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import edu.harvard.dbmi.avillach.data.entity.BaseEntity; +import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; +import org.hibernate.annotations.Type; + +import javax.persistence.*; +import java.io.Serializable; +import java.security.Principal; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Defines a model of User behavior. + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@Entity(name = "user") +public class User extends BaseEntity implements Serializable, Principal { + + @Column(unique = true) + private String subject; + + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name = "user_role", + joinColumns = {@JoinColumn(name = "user_id", nullable = false, updatable = false)}, + inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false, updatable = false)}) + private Set roles; + + private String email; + + + /** + *

NOTICE

+ *

+ * When you update or create a user, + * please use connection.id as the input. The UserService is specifically using connection.id. + *

+ *
+ *

+ * Note: This is because of the checkAssociation() method in UserService. + *


+ * @see UserController + */ + @ManyToOne + @JoinColumn(name = "connectionId") + private Connection connection; + + private boolean matched; + + private Date acceptedTOS; + + @Column(name = "auth0_metadata") + @Type(type = "text") + private String auth0metadata; + + @Column(name = "general_metadata") + @Type(type = "text") + private String generalMetadata; + + @Column(name = "is_active") + private boolean active = true; + + @Column(name = "long_term_token") + private String token; + + public String getSubject() { + return subject; + } + + public User setSubject(String subject) { + this.subject = subject; + return this; + } + + public Set getRoles() { + return roles; + } + + public User setRoles(Set roles) { + this.roles = roles; + return this; + } + + /** + * return all privileges in the roles as a set + * @return + */ + @JsonIgnore + public Set getTotalPrivilege(){ + if (roles == null) + return null; + + Set privileges = new HashSet<>(); + roles.stream().forEach(r -> privileges.addAll(r.getPrivileges())); + return privileges; + } + + /** + * return all privileges in the roles as a set + * @return + */ + @JsonIgnore + public Set getTotalAccessRule(){ + if (roles == null) + return null; + + Set accessRules = new HashSet<>(); + roles.stream(). + forEach(r -> r.getPrivileges().stream(). + forEach(p -> accessRules.addAll(p.getAccessRules()))); + return accessRules; + } + + /** + * return all privilege name in each role as a set. + * + * @return + */ + @JsonIgnore + public Set getPrivilegeNameSet(){ + Set totalPrivilegeSet = getTotalPrivilege(); + + if (totalPrivilegeSet == null) + return null; + + Set nameSet = new HashSet<>(); + totalPrivilegeSet.stream().forEach(p -> nameSet.add(p.getName())); + return nameSet; + } + + /** + * return privilege names in each role as a set based on Application given. + * + * @return + */ + @JsonIgnore + public Set getPrivilegeNameSetByApplication(Application application){ + Set totalPrivilegeSet = getTotalPrivilege(); + + if (totalPrivilegeSet == null) + return null; + + Set nameSet = new HashSet<>(); + if (application == null) + return nameSet; + + for (Privilege appPrivilege : application.getPrivileges()) { + for (Privilege userPrivilege : totalPrivilegeSet) { + if (appPrivilege.equals(userPrivilege)) + nameSet.add(userPrivilege.getName()); + } + } + return nameSet; + } + + /** + * return privileges in each role as a set based on Application given. + * + * @return + */ + @JsonIgnore + public Set getPrivilegesByApplication(Application application){ + if (application == null || application.getUuid() == null){ + return getTotalPrivilege(); + } + + if (roles == null) + return null; + + Set privileges = new HashSet<>(); + roles.stream(). + forEach(r -> privileges.addAll(r.getPrivileges() + .stream() + .filter(p -> application.getUuid() + .equals((p.getApplication()==null)? + null: + p.getApplication().getUuid())) + .collect(Collectors.toSet()))); + return privileges; + } + + @JsonIgnore + public String getPrivilegeString(){ + Set totalPrivilegeSet = getTotalPrivilege(); + + if (totalPrivilegeSet == null) + return null; + + return totalPrivilegeSet.stream().map(p -> p.getName()).collect(Collectors.joining(",")); + } + + @JsonIgnore + public String getRoleString(){ + return (roles==null)?null:roles.stream().map(r -> r.name) + .collect(Collectors.joining(",")); + } + + public String getAuth0metadata() { + return auth0metadata; + } + + public User setAuth0metadata(String auth0metadata) { + this.auth0metadata = auth0metadata; + return this; + } + + public String getGeneralMetadata() { + return generalMetadata; + } + + public User setGeneralMetadata(String generalMetadata) { + this.generalMetadata = generalMetadata; + return this; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Connection getConnection() { + return connection; + } + + public User setConnection(Connection connection) { + this.connection = connection; + return this; + } + + public boolean isMatched() { + return matched; + } + + public void setMatched(boolean matched) { + this.matched = matched; + } + + public Date getAcceptedTOS() { + return acceptedTOS; + } + + public void setAcceptedTOS(Date acceptedTOS) { + this.acceptedTOS = acceptedTOS; + } + + @JsonIgnore + @Override + public String getName() { + return this.subject; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + /** + *

Inner class defining limited user attributes returned from the User endpoint.

+ */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public static class UserForDisplay { + String uuid; + String email; + Set privileges; + String token; + Set queryScopes; + private boolean acceptedTOS; + + public UserForDisplay() { + } + + public String getEmail() { + return email; + } + + public UserForDisplay setEmail(String email) { + this.email = email; + return this; + } + + public Set getPrivileges() { + return privileges; + } + + public UserForDisplay setPrivileges(Set privileges) { + this.privileges = privileges; + return this; + } + + public String getUuid() { + return uuid; + } + + public UserForDisplay setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getToken() { + return token; + } + + public UserForDisplay setToken(String token) { + this.token = token; + return this; + } + + public Set getQueryScopes() { + return queryScopes; + } + + public void setQueryScopes(Set queryScopes) { + this.queryScopes = queryScopes; + } + + public boolean getAcceptedTOS() { + return acceptedTOS; + } + + public UserForDisplay setAcceptedTOS(boolean acceptedTOS) { + this.acceptedTOS = acceptedTOS; + return this; + } + } + + public String toString() { + if(uuid == null) { + return "No UUID assigned___ " + subject + " ___ " + email + " ___ " + generalMetadata + " ___ " + auth0metadata + " ___ {" + ((connection==null)?null:connection.toString()) + "}"; + } + return uuid.toString() + " ___ " + subject + " ___ " + email + " ___ " + generalMetadata + " ___ " + auth0metadata + " ___ {" + ((connection==null)?null:connection.toString()) + "}"; + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/UserMetadataMapping.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java similarity index 95% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/UserMetadataMapping.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java index c7a5a170f..5e44ffdfb 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/entity/UserMetadataMapping.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.entity; +package edu.harvard.hms.dbmi.avillach.auth.entity; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/package-info.java new file mode 100644 index 000000000..a3ef54e20 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains the entity objects for PSAMA. + */ +package edu.harvard.hms.dbmi.avillach.auth.entity; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/exceptions/NotAuthorizedException.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/exceptions/NotAuthorizedException.java new file mode 100644 index 000000000..050b6de05 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/exceptions/NotAuthorizedException.java @@ -0,0 +1,7 @@ +package edu.harvard.hms.dbmi.avillach.auth.exceptions; + +public class NotAuthorizedException extends RuntimeException { + public NotAuthorizedException(String message) { + super(message); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java new file mode 100755 index 000000000..00eef0b01 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -0,0 +1,206 @@ +package edu.harvard.hms.dbmi.avillach.auth.filter; + +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Set; +import java.util.UUID; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; + +/** + * The main gate for PSAMA that filters all incoming requests against PSAMA. + *

Design Logic

+ *
    + *
  • All incoming requests pass through this filter.
  • + *
  • To pass this filter, the incoming request needs a valid bearer token in its HTTP Authorization Header + * to represent a valid identity behind the token.
  • + *
  • In some cases, the incoming request doesn't need to hold a token. For example, when the request is to the authentication + * endpoint, swagger.json, or swagger.html.
  • + *
+ */ + +@Component +public class JWTFilter extends OncePerRequestFilter { + + private final static Logger logger = LoggerFactory.getLogger(JWTFilter.class); + + private final UserRepository userRepo; + + private final ApplicationRepository applicationRepo; + + private final TOSService tosService; + + @Value("${application.user.id.claim}") + private String USER_CLAIM_ID; + + @Autowired + public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService) { + this.userRepo = userRepo; + this.applicationRepo = applicationRepo; + this.tosService = tosService; + } + + /** + * Filter implementation that performs authentication and authorization checks based on the provided request headers. + * The filter checks for the presence of the "Authorization" header and validates the token. + * It sets the appropriate security context based on the type of token (long term token or PSAMA application token) and + * performs the necessary checks to ensure that the user or application is authorized to access the requested resource. + * This filter is called by the configured security filter chain in the SecurityConfig class. + * + * @param request the HttpServletRequest object + * @param response the HttpServletResponse object + * @param filterChain the FilterChain object + * @throws IOException if an I/O error occurs during the execution of the filter + */ + @Override + // Ends that are allowed are handled by the configured security filter chain in the SecurityConfig class + protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException { + // Get headers from the request + String authorizationHeader = request.getHeader("Authorization"); + + if (!StringUtils.isNotBlank(authorizationHeader)) { + // If the header is not present, then the request is not authorized + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No authorization header found."); + } else { + // If the header is present, we need to check the token + String token = authorizationHeader.substring(6).trim(); + logger.debug(" token: {}", token); + + // Parse the token + Jws jws = parseToken(token); // TODO: We shouldn't be implementing a method that should be in the JWTUtils class + String userId = jws.getBody().get(this.USER_CLAIM_ID, String.class); // TODO: Update when we remove the JAXRSConfiguration class + + if (userId.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { + // For profile information, we do indeed allow long term token + // to be a valid token. + if (request.getRequestURI().startsWith("/user/me")) { + // Get the subject claim, remove the LONG_TERM_TOKEN_PREFIX, and use that String value to + // look up the existing user. + String realClaimsSubject = jws.getBody().getSubject().substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); + + setSecurityContextForUser(request, response, realClaimsSubject); + } else { + logger.error("the long term token with subject, {}, cannot access to PSAMA.", userId); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Long term tokens cannot be used to access to PSAMA."); + } + + } + + if (authorizationHeader.startsWith(AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX)) { + logger.info("User Authentication Starts with {}", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX); + + // Check if user is attempting to access the correct introspect endpoint. If not reject the request + // log an error indicating the user's token may be being used by a malicious actor. + if (!request.getRequestURI().endsWith("token/inspect")) { + logger.error(userId + " attempted to perform request " + request.getRequestURI() + " token may be compromised."); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User is deactivated"); + } + + // Authenticate as Application + Application authenticatedApplication = applicationRepo.getById(UUID.fromString(userId.split("\\|")[1])); + if (authenticatedApplication == null) { + logger.error("Cannot find an application by userId: " + userId); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); + return; + } + + if (!authenticatedApplication.getToken().equals(token)) { + logger.error("filter() incoming application token - " + token + + " - is not the same as record, might because the token has been refreshed. Subject: " + userId); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token has been inactivated, please contact admin to grab you the latest one."); + } + + // This is the application token that is being used to authenticate the user by other applications + // Set the security context for the application + setSecurityContextForApplication(request, authenticatedApplication); + } else { + logger.debug("UserID: {} is not a long term token and not a PSAMA application token.", userId); + // Authenticate as User + setSecurityContextForUser(request, response, jws.getBody().getSubject()); + } + } + + } + + private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { + logger.info("Setting security context for application: {}", authenticatedApplication.getName()); + request.setAttribute("authenticatedApplication", authenticatedApplication); + } + + // TODO: Implement the ApplicationException thrown in this method + + /** + * Sets the security context for the given user. + * This method is responsible for validating the user claims, checking if the user is active, + * ensuring that the user has accepted the terms of service (if enabled), validating user roles and privileges, + * and setting the user object as an attribute in the request. + * + * @param request the HttpServletRequest object + * @param response the HttpServletResponse object + * @param realClaimsSubject the subject of the user's claims in the JWT token + */ + private void setSecurityContextForUser(HttpServletRequest request, HttpServletResponse response, String realClaimsSubject) { + logger.info("Setting security context for user: {}", realClaimsSubject); + + User authenticatedUser = userRepo.findBySubject(realClaimsSubject); + + if (authenticatedUser == null) { + logger.error("Cannot validate user claims, based on information stored in the JWT token."); + throw new IllegalArgumentException("Cannot validate user claims, based on information stored in the JWT token."); + } + + if (!authenticatedUser.isActive()) { + logger.warn("User with ID: " + authenticatedUser.getUuid() + " is deactivated."); + throw new NotAuthorizedException("User is deactivated"); + } + + if (JAXRSConfiguration.tosEnabled.startsWith("true") && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { + //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate + try { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User must accept terms of service"); + } catch (IOException e) { + logger.error("Failed to send response.", e); + } + } + + // Get the user's roles + Set userRoles = authenticatedUser.getRoles(); + + // Check if the user has any roles and privileges associated with them + if (userRoles == null || userRoles.isEmpty() || userRoles.stream().noneMatch(role -> role.getPrivileges() != null && !role.getPrivileges().isEmpty())) { + logger.error("User doesn't have any roles or privileges."); + try { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User doesn't have any roles or privileges."); + } catch (IOException e) { + logger.error("Failed to send response.", e); + } + } + + // TODO: Spring is generally expecting ROLE_ prefix for roles. We may need to add this prefix to all the user roles. + // We don't want to add this to the database, because it may break backward compatibility for the UI. + request.setAttribute("authenticatedUser", authenticatedUser); + } + +} \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/AccessEmail.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java similarity index 87% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/AccessEmail.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java index 082e27504..90b19ebd9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/AccessEmail.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java @@ -1,8 +1,8 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; +package edu.harvard.hms.dbmi.avillach.auth.model; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; import java.util.Set; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java new file mode 100644 index 000000000..d5244b2fc --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java @@ -0,0 +1,83 @@ +package edu.harvard.hms.dbmi.avillach.auth.model.response; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import java.util.HashMap; +import java.util.Map; + +public class PICSUREResponse { + + private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.APPLICATION_JSON; + private static final HttpStatus DEFAULT_RESPONSE_ERROR_CODE = HttpStatus.INTERNAL_SERVER_ERROR; + + public static ResponseEntity success() { + return new ResponseEntity<>(HttpStatus.OK); + } + + public static ResponseEntity success(Object content) { + return new ResponseEntity<>(content, HttpStatus.OK); + } + + public static ResponseEntity success(String message, Object content) { + Map response = new HashMap<>(); + response.put("message", message); + response.put("content", content); + return new ResponseEntity<>(response, HttpStatus.OK); + } + + public static ResponseEntity error(Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, content); + } + + public static ResponseEntity error(String message, Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); + } + + public static ResponseEntity error(HttpStatus status, Object content) { + if (status == null) { + status = DEFAULT_RESPONSE_ERROR_CODE; + } + return new ResponseEntity<>(content, status); + } + + public static ResponseEntity error(HttpStatus status, String message, Object content) { + Map response = new HashMap<>(); + response.put("message", message); + response.put("content", content); + return new ResponseEntity<>(response, status); + } + + public static ResponseEntity applicationError(Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, "Application error", content); + } + + public static ResponseEntity applicationError(String message, Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); + } + + public static ResponseEntity riError(Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, "RI error", content); + } + + public static ResponseEntity riError(String message, Object content) { + return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); + } + + public static ResponseEntity protocolError(Object content) { + return error(HttpStatus.BAD_REQUEST, content); + } + + public static ResponseEntity protocolError(String message, Object content) { + return error(HttpStatus.BAD_REQUEST, message, content); + } + + public static ResponseEntity unauthorizedError(Object content) { + return error(HttpStatus.UNAUTHORIZED, "Unauthorized", content); + } + + public static ResponseEntity unauthorizedError(String message, Object content) { + return error(HttpStatus.UNAUTHORIZED, message, content); + } +} + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/AccessRuleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java similarity index 59% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/AccessRuleRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java index ebe62bf66..2d9f9ec47 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/AccessRuleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java @@ -1,10 +1,9 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; -import javax.transaction.Transactional; import java.util.UUID; /** @@ -12,8 +11,7 @@ * * @see AccessRule */ -@ApplicationScoped -@Transactional +@Repository public class AccessRuleRepository extends BaseRepository { protected AccessRuleRepository() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ApplicationRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java similarity index 52% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ApplicationRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java index 56f7fb4c3..ceb0f2960 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ApplicationRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java @@ -1,10 +1,9 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; -import javax.transaction.Transactional; import java.util.UUID; /** @@ -12,8 +11,10 @@ * * @see Application */ -@ApplicationScoped -@Transactional + +// TODO: A repository class is not the right place to annotate with transactional. Verify? +// TODO: Is there a reason why we would scope this to the application? +@Repository public class ApplicationRepository extends BaseRepository { protected ApplicationRepository() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ConnectionRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java similarity index 65% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ConnectionRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java index 81238472b..3369bd3f8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/ConnectionRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java @@ -1,28 +1,23 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; import javax.persistence.NoResultException; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import javax.transaction.Transactional; import java.util.UUID; /** *

Provides operations for the Connection entity to interact with a database.

+ * * @see Connection */ -@Transactional -@ApplicationScoped +@Repository public class ConnectionRepository extends BaseRepository { - private Logger logger = LoggerFactory.getLogger(ConnectionRepository.class); - protected ConnectionRepository() { super(Connection.class); } @@ -34,10 +29,10 @@ public Connection findConnectionById(String connectionId) { CriteriaBuilder cb = cb(); try { return em.createQuery(query - .where( - eq(cb, queryRoot, "id", connectionId))) + .where( + eq(cb, queryRoot, "id", connectionId))) .getSingleResult(); - } catch (NoResultException e){ + } catch (NoResultException e) { return null; } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/PrivilegeRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java similarity index 53% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/PrivilegeRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java index ac22d0dc5..7d02a05ff 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/PrivilegeRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java @@ -1,19 +1,17 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; -import javax.transaction.Transactional; import java.util.UUID; /** *

Provides operations for the Privilege entity to interact with a database.

* @see Privilege */ -@ApplicationScoped -@Transactional + +@Repository public class PrivilegeRepository extends BaseRepository { protected PrivilegeRepository() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java similarity index 57% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java index 63aa7f92d..d2cb6ae1e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/RoleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java @@ -1,18 +1,17 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; -import javax.transaction.Transactional; import java.util.UUID; /** *

Provides operations for the Role entity to interact with a database.

* @see Role */ -@ApplicationScoped -@Transactional + +@Repository public class RoleRepository extends BaseRepository { protected RoleRepository() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/TermsOfServiceRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java similarity index 71% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/TermsOfServiceRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java index 60913eab1..eb4ef256e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/TermsOfServiceRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java @@ -1,27 +1,21 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.TermsOfService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import javax.transaction.Transactional; import java.util.UUID; /** *

Provides operations for the TermsOfService entity to interact with a database.

* @see TermsOfService */ -@Transactional -@ApplicationScoped +@Repository public class TermsOfServiceRepository extends BaseRepository { - private Logger logger = LoggerFactory.getLogger(TermsOfServiceRepository.class); - protected TermsOfServiceRepository() { super(TermsOfService.class); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java new file mode 100644 index 000000000..fa8d10cc9 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java @@ -0,0 +1,27 @@ +package edu.harvard.hms.dbmi.avillach.auth.repository; + +import edu.harvard.dbmi.avillach.data.repository.BaseRepository; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +/** + *

Provides operations for the UserMetadataMapping entity to interact with a database.

+ * @see UserMetadataMapping + */ + +@Repository +public class UserMetadataMappingRepository extends BaseRepository { + + protected UserMetadataMappingRepository() { + super(UserMetadataMapping.class); + } + + public List findByConnection(Connection connection) { + return getByColumn("connection", connection); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java similarity index 86% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java index a627ccd5f..257939dc3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/UserRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java @@ -1,18 +1,20 @@ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; +package edu.harvard.hms.dbmi.avillach.auth.repository; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.TermsOfService; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Repository; -import javax.enterprise.context.ApplicationScoped; import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; -import javax.persistence.criteria.*; -import javax.transaction.Transactional; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; import java.util.Date; import java.util.List; import java.util.Set; @@ -23,11 +25,11 @@ *

Provides operations for the User entity to interact with a database.

* @see User */ -@Transactional -@ApplicationScoped + +@Repository public class UserRepository extends BaseRepository { - private Logger logger = LoggerFactory.getLogger(UserRepository.class); + private final static Logger logger = LoggerFactory.getLogger(UserRepository.class); protected UserRepository() { super(User.class); @@ -99,10 +101,6 @@ public User findOrCreate(User inputUser) { private User createUser(User inputUser) { String subject = inputUser.getSubject(); -// if (subject == null && userId == null){ -// logger.error("createUser() cannot create user when both subject and userId are null"); -// return null; -// } logger.debug("createUser() creating user, subject: " + subject + " ......"); em().persist(inputUser); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/package-info.java similarity index 54% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/package-info.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/package-info.java index 1b40cbce9..6ab62fffc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/data/repository/package-info.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/package-info.java @@ -1,4 +1,4 @@ /** * Defines which entity objects should persist in the database. */ -package edu.harvard.hms.dbmi.avillach.auth.data.repository; \ No newline at end of file +package edu.harvard.hms.dbmi.avillach.auth.repository; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java similarity index 71% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index 9633b306c..314e03b57 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -1,23 +1,16 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.AccessRuleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import io.swagger.annotations.Api; +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; import java.util.List; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -26,38 +19,37 @@ /** *

Endpoint for service handling business logic for access rules.

*

Note: Only users with the super admin role can access this endpoint.

+ * + * Path: /accessRule */ -@Api -@Path("/accessRule") -public class AccessRuleService extends BaseEntityService { - Logger logger = LoggerFactory.getLogger(AccessRuleService.class); +@Controller("/accessRule") +public class AccessRuleController extends BaseEntityService { - @Inject - AccessRuleRepository accessRuleRepo; + private final AccessRuleRepository accessRuleRepo; - @Context - SecurityContext securityContext; +// @Context +// private SecurityContext securityContext; - public AccessRuleService() { + @Autowired + public AccessRuleController(AccessRuleRepository accessRuleRepo) { super(AccessRule.class); + this.accessRuleRepo = accessRuleRepo; } @ApiOperation(value = "GET information of one AccessRule with the UUID, requires ADMIN or SUPER_ADMIN role") - @GET - @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("/{accessRuleId}") - public Response getAccessRuleById( + @Secured(value = {ADMIN, SUPER_ADMIN}) + @GetMapping(value = "/{accessRuleId}") + public ResponseEntity getAccessRuleById( @ApiParam(value="The UUID of the accessRule to fetch information about") @PathParam("accessRuleId") String accessRuleId) { return getEntityById(accessRuleId,accessRuleRepo); } @ApiOperation(value = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") - @GET - @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("") - public Response getAccessRuleAll() { + @Secured({ADMIN, SUPER_ADMIN}) + @GetMapping("") + public ResponseEntity getAccessRuleAll() { return getEntityAll(accessRuleRepo); } @@ -66,7 +58,7 @@ public Response getAccessRuleAll() { @RolesAllowed(SUPER_ADMIN) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response addAccessRule( + public ResponseEntity addAccessRule( @ApiParam(required = true, value = "A list of AccessRule in JSON format") List accessRules){ accessRules.stream().forEach(accessRule -> { @@ -90,7 +82,7 @@ public Response addAccessRule( @RolesAllowed(SUPER_ADMIN) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response updateAccessRule( + public ResponseEntity updateAccessRule( @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") List accessRules){ return updateEntity(accessRules, accessRuleRepo); @@ -101,7 +93,7 @@ public Response updateAccessRule( @DELETE @RolesAllowed(SUPER_ADMIN) @Path("/{accessRuleId}") - public Response removeById( + public ResponseEntity removeById( @ApiParam(required = true, value = "A valid accessRule Id") @PathParam("accessRuleId") final String accessRuleId) { return removeEntityById(accessRuleId, accessRuleRepo); @@ -112,7 +104,7 @@ public Response removeById( @RolesAllowed(SUPER_ADMIN) @Path("/allTypes") @Produces(MediaType.APPLICATION_JSON) - public Response getAllTypes(){ + public ResponseEntity getAllTypes(){ return PICSUREResponse.success(AccessRule.TypeNaming.getTypeNameMap()); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java new file mode 100644 index 000000000..a190ef2b0 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -0,0 +1,86 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import jakarta.annotation.security.RolesAllowed; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.transaction.Transactional; +import java.util.List; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; + +/** + *

Endpoint for registering and administering applications. + *
+ * Note: Only users with the super admin role can access this endpoint.

+ */ +@Api +@Controller +@RequestMapping(value = "/application") +public class ApplicationController { + + private final ApplicationService applicationService; + + @Autowired + public ApplicationController(ApplicationService applicationService) { + this.applicationService = applicationService; + } + + @ApiOperation(value = "GET information of one Application with the UUID, no role restrictions") + @GetMapping(value = "/{applicationId}") + public ResponseEntity getApplicationById( + @ApiParam(required = true, value = "The UUID of the application to fetch information about") + @PathVariable("applicationId") String applicationId) { + return applicationService.getEntityById(applicationId); + } + + @ApiOperation(value = "GET a list of existing Applications, no role restrictions") + @GetMapping + public ResponseEntity getApplicationAll() { + return applicationService.getEntityAll(); + } + + @ApiOperation(value = "POST a list of Applications, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @PostMapping(value = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity addApplication( + @ApiParam(required = true, value = "A list of AccessRule in JSON format") + List applications) { + return applicationService.addNewApplications(applications); + } + + @ApiOperation(value = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @PutMapping(value = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity updateApplication( + @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") + List applications) { + return applicationService.updateApplications(applications); + } + + @ApiOperation(value = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @GetMapping(value = "/refreshToken/{applicationId}") + public ResponseEntity refreshApplicationToken( + @ApiParam(required = true, value = "A valid application Id") + @PathVariable("applicationId") String applicationId) { + return applicationService.refreshApplicationToken(applicationId); + } + + @ApiOperation(value = "DELETE an Application by Id only if the application is not associated by others, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @DeleteMapping(value = "/{applicationId}") + public ResponseEntity removeById( + @ApiParam(required = true, value = "A valid accessRule Id") + @PathVariable("applicationId") final String applicationId) { + return applicationService.deleteApplicationById(applicationId); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationService.java deleted file mode 100644 index f47031d91..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationService.java +++ /dev/null @@ -1,187 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.rest; - -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.exception.ProtocolException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import java.util.*; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; - -/** - *

Endpoint for registering and administering applications. - *
- * Note: Only users with the super admin role can access this endpoint.

- */ -@Api -@Path("/application") -public class ApplicationService extends BaseEntityService { - - private static final long ONE_YEAR = 1000L * 60 * 60 * 24 * 365; - - Logger logger = LoggerFactory.getLogger(ApplicationService.class); - - @Inject - ApplicationRepository applicationRepo; - - @Inject - PrivilegeRepository privilegeRepo; - - @Context - SecurityContext securityContext; - - public ApplicationService() { - super(Application.class); - } - - @ApiOperation(value = "GET information of one Application with the UUID, no role restrictions") - @GET - @Path("/{applicationId}") - public Response getApplicationById( - @ApiParam(required = true, value="The UUID of the application to fetch information about") - @PathParam("applicationId") String applicationId) { - return getEntityById(applicationId,applicationRepo); - } - - @ApiOperation(value = "GET a list of existing Applications, no role restrictions") - @GET - @Path("") - public Response getApplicationAll() { - return getEntityAll(applicationRepo); - } - - @ApiOperation(value = "POST a list of Applications, requires SUPER_ADMIN role") - @Transactional - @POST - @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") - public Response addApplication( - @ApiParam(required = true, value = "A list of AccessRule in JSON format") - List applications){ - checkAssociation(applications); - List appEntities = addOrUpdate(applications, true, applicationRepo); - for(Application application : appEntities) { - try{ - application.setToken( - generateApplicationToken(application) - ); - } catch(Exception e) { - logger.error("", e); - } - } - - return updateEntity(appEntities, applicationRepo); - } - - @ApiOperation(value = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") - @PUT - @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") - public Response updateApplication( - @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") - List applications){ - checkAssociation(applications); - return updateEntity(applications, applicationRepo); - } - - @ApiOperation(value = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") - @GET - @RolesAllowed({SUPER_ADMIN}) - @Path("/refreshToken/{applicationId}") - public Response refreshApplicationToken( - @ApiParam(required = true, value = "A valid application Id") - @PathParam("applicationId") String applicationId){ - Application application = applicationRepo.getById(UUID.fromString(applicationId)); - - if (application == null){ - logger.error("refreshApplicationToken() cannot find the application by applicationId: " + applicationId); - throw new ProtocolException("Cannot find application by the given applicationId: " + applicationId); - } - - String newToken = generateApplicationToken(application); - if (newToken != null){ - application.setToken( - newToken - ); - - applicationRepo.merge(application); - } else { - logger.error("refreshApplicationToken() token is null for application: " + applicationId); - throw new ApplicationException("Inner problem, please contact admin"); - } - - return PICSUREResponse.success(Map.of("token", newToken)); - - } - - @ApiOperation(value = "DELETE an Application by Id only if the application is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE - @RolesAllowed({SUPER_ADMIN}) - @Path("/{applicationId}") - public Response removeById( - @ApiParam(required = true, value = "A valid accessRule Id") - @PathParam("applicationId") final String applicationId) { - Application application = applicationRepo.getById(UUID.fromString(applicationId)); - return removeEntityById(applicationId, applicationRepo); - } - - private void checkAssociation(List applications){ - for (Application application: applications){ - if (application.getPrivileges() != null) { - Set privileges = new HashSet<>(); - application.getPrivileges().stream().forEach(p -> { - Privilege privilege = privilegeRepo.getById(p.getUuid()); - if (privilege != null){ - privilege.setApplication(application); - privileges.add(privilege); - } else { - logger.error("Didn't find privilege by uuid: " + p.getUuid()); - } - }); - application.setPrivileges(privileges); - - } - } - - } - - public String generateApplicationToken(Application application){ - if (application == null || application.getUuid() == null) { - logger.error("generateApplicationToken() application is null or uuid is missing to generate the application token"); - throw new ApplicationException("Cannot generate application token, please contact admin"); - } - - return JWTUtil.createJwtToken( - JAXRSConfiguration.clientSecret, null, null, - new HashMap<>( - Map.of( - "user_id", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getName() - ) - ), - AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getUuid().toString(), 365L * 1000 * 60 * 60 * 24); - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java new file mode 100644 index 000000000..cf891eca5 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -0,0 +1,55 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthenticationService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.FENCEAuthenticationService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.Map; + + +/** + *

The authentication endpoint for PSAMA.

+ */ +@Api +@Controller +@RequestMapping("/") +public class AuthController { + + private final static Logger logger = LoggerFactory.getLogger(AuthController.class.getName()); + + public final AuthorizationService authorizationService; + + public final AuthenticationService authenticationService; + public final FENCEAuthenticationService fenceAuthenticationService; + + @Autowired + public AuthController(AuthorizationService authorizationService, AuthenticationService authenticationService, FENCEAuthenticationService fenceAuthenticationService) { + this.authorizationService = authorizationService; + this.authenticationService = authenticationService; + this.fenceAuthenticationService = fenceAuthenticationService; + } + + @ApiOperation(value = "The authentication endpoint for retrieving a valid user token") + @PostMapping(path = "/authentication", consumes = "application/json", produces = "application/json") + public ResponseEntity authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) { + logger.debug("authentication() starting..."); + if (JAXRSConfiguration.idp_provider.equalsIgnoreCase("fence")) { + logger.debug("authentication() FENCE authentication"); + return fenceAuthenticationService.getFENCEProfile(authRequest); + } else { + logger.debug("authentication() default authentication"); + return authenticationService.getToken(authRequest); + } + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java deleted file mode 100644 index 5bcb58d76..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthService.java +++ /dev/null @@ -1,54 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.rest; - -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.AuthenticationService; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.AuthorizationService; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.FENCEAuthenticationService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.Response; -import java.util.Map; - -/** - *

The authentication endpoint for PSAMA.

- */ -@Api -@Path("/") -@Consumes("application/json") -@Produces("application/json") -public class AuthService { - - private Logger logger = LoggerFactory.getLogger(this.getClass()); - - @Inject - AuthorizationService authorizationService; - - @Inject - AuthenticationService authenticationService; - - @Inject - FENCEAuthenticationService fenceAuthenticationService; - - @ApiOperation(value = "The authentication endpoint for retrieving a valid user token") - @POST - @Path("/authentication") - public Response authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) { - logger.debug("authentication() starting..."); - if (JAXRSConfiguration.idp_provider.equalsIgnoreCase("fence")) { - logger.debug("authentication() FENCE authentication"); - return fenceAuthenticationService.getFENCEProfile(authRequest); - } else { - logger.debug("authentication() default authentication"); - return authenticationService.getToken(authRequest); - } - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java new file mode 100644 index 000000000..923f98556 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -0,0 +1,83 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.ConnectionWebService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import javax.transaction.Transactional; +import java.util.List; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; + +/** + *

Endpoint for service handling business logic for connections to PSAMA.
+ * Note: Only users with the super admin role can access this endpoint.

+ */ +@Api +@Controller +@RequestMapping("/connection") +public class ConnectionWebController { // TODO: This isn't a service class, it's a controller. We should refactor this so it doesn't extend BaseEntityService + + + private final ConnectionWebService connectionWebService; + + @Autowired + public ConnectionWebController(ConnectionWebService connectionWebSerivce) { + this.connectionWebService = connectionWebSerivce; + } + + @ApiOperation(value = "GET information of one Connection with the UUID, requires ADMIN or SUPER_ADMIN role") + @GetMapping(path ="/{connectionId}", produces = "application/json") + @Secured({SUPER_ADMIN, ADMIN}) + public ResponseEntity getConnectionById( + @ApiParam(required = true, value="The UUID of the Connection to fetch information about") + @PathVariable("connectionId") String connectionId) { + return connectionWebService.getEntityById(connectionId); + } + + @ApiOperation(value = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") + @GetMapping(path ="/", produces = "application/json") + @Secured({SUPER_ADMIN, ADMIN}) + public ResponseEntity getAllConnections() { + return connectionWebService.getEntityAll(); + } + + @ApiOperation(value = "POST a list of Connections, requires SUPER_ADMIN role") + @Transactional + @Secured({SUPER_ADMIN}) + @PostMapping(produces = "application/json", consumes = "application/json") + public ResponseEntity addConnection( + @ApiParam(required = true, value = "A list of Connections in JSON format") + List connections){ + return connectionWebService.addEntity(connections); + } + + @ApiOperation(value = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") + @Secured({SUPER_ADMIN}) + @PutMapping(produces = "application/json", consumes = "application/json") + public ResponseEntity updateConnection( + @ApiParam(required = true, value = "A list of Connection with fields to be updated in JSON format") + List connections){ + return connectionWebService.updateEntity(connections); + } + + @ApiOperation(value = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") + @Transactional + @Secured({SUPER_ADMIN}) + @DeleteMapping(path ="/{connectionId}", produces = "application/json") + public ResponseEntity removeById( + @ApiParam(required = true, value = "A valid connection Id") + @PathVariable("connectionId") final String connectionId) { + return connectionWebService.removeEntityById(connectionId); + } + + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebService.java deleted file mode 100644 index 325534686..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebService.java +++ /dev/null @@ -1,102 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.rest; - -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; - -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.List; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; - -/** - *

Endpoint for service handling business logic for connections to PSAMA.
- * Note: Only users with the super admin role can access this endpoint.

- */ -@Api -@Path("connection") -public class ConnectionWebService extends BaseEntityService { - - public ConnectionWebService() { - super(Connection.class); - } - - @Inject - ConnectionRepository connectionRepo; - - @ApiOperation(value = "GET information of one Connection with the UUID, requires ADMIN or SUPER_ADMIN role") - @Path("{connectionId}") - @GET - @Produces("application/json") - @RolesAllowed({SUPER_ADMIN, ADMIN}) - public Response getConnectionById( - @ApiParam(required = true, value="The UUID of the Connection to fetch information about") - @PathParam("connectionId") String connectionId) { - return getEntityById(connectionId,connectionRepo); - } - - @ApiOperation(value = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") - @GET - @Produces("application/json") - @RolesAllowed({SUPER_ADMIN, ADMIN}) - public Response getAllConnections() { - return getEntityAll(connectionRepo); - } - - @ApiOperation(value = "POST a list of Connections, requires SUPER_ADMIN role") - @Transactional - @POST - @Consumes(MediaType.APPLICATION_JSON) - @RolesAllowed({SUPER_ADMIN}) - @Path("/") - public Response addConnection( - @ApiParam(required = true, value = "A list of Connections in JSON format") - List connections){ - return addEntity(connections); - } - - @ApiOperation(value = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") - @PUT - @Consumes(MediaType.APPLICATION_JSON) - @RolesAllowed({SUPER_ADMIN}) - @Path("/") - public Response updateConnection( - @ApiParam(required = true, value = "A list of Connection with fields to be updated in JSON format") - List connections){ - return updateEntity(connections, connectionRepo); - } - - @ApiOperation(value = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE - @RolesAllowed({SUPER_ADMIN}) - @Path("/{connectionId}") - public Response removeById( - @ApiParam(required = true, value = "A valid connection Id") - @PathParam("connectionId") final String connectionId) { - return removeEntityById(connectionId, connectionRepo); - } - - private Response addEntity(List connections){ - for (Connection c : connections){ - if (c.getSubPrefix() == null || c.getRequiredFields() == null || c.getLabel() == null || c.getId() == null){ - return PICSUREResponse.protocolError("Id, Label, Subprefix, and RequiredFields cannot be null"); - } - Connection conn = connectionRepo.findConnectionById(c.getId()); - if (conn != null){ - return PICSUREResponse.protocolError("Id must be unique, a connection with id " + c.getId() + " already exists in the database"); - } - } - return addEntity(connections, connectionRepo); - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java new file mode 100644 index 000000000..5f1aa82bf --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -0,0 +1,77 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.service.PrivilegeService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import jakarta.annotation.security.RolesAllowed; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; + +/** + *

Endpoint for service handling business logic for privileges. + *
Note: Only users with the super admin role can access this endpoint.

+ */ +@Api +@RestController +@RequestMapping("/privilege") +public class PrivilegeController { + + private final PrivilegeService privilegeService; + + @Autowired + public PrivilegeController(PrivilegeService privilegeService) { + this.privilegeService = privilegeService; + } + + @ApiOperation(value = "GET information of one Privilege with the UUID, requires ADMIN or SUPER_ADMIN role") + @RolesAllowed({ADMIN, SUPER_ADMIN}) + @GetMapping(path = "/{privilegeId}", produces = "application/json") + public ResponseEntity getPrivilegeById( + @ApiParam(value="The UUID of the privilege to fetch information about") + @PathVariable("privilegeId") String privilegeId) { + return this.privilegeService.getEntityById(privilegeId); + } + + @ApiOperation(value = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") + @RolesAllowed({ADMIN, SUPER_ADMIN}) + @GetMapping(path = "/", produces = "application/json") + public ResponseEntity getPrivilegeAll() { + return this.privilegeService.getEntityAll(); + } + + @ApiOperation(value = "POST a list of privileges, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @PostMapping(path = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity addPrivilege( + @ApiParam(required = true, value = "A list of privileges in JSON format") + List privileges){ + return this.privilegeService.addEntity(privileges); + } + + @ApiOperation(value = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @PutMapping(path = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity updatePrivilege( + @ApiParam(required = true, value = "A list of privilege with fields to be updated in JSON format") + List privileges){ + return this.privilegeService.updateEntity(privileges); + } + + @ApiOperation(value = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") + @RolesAllowed({SUPER_ADMIN}) + @DeleteMapping(path = "/{privilegeId}", produces = "application/json") + public ResponseEntity removeById( + @ApiParam(required = true, value = "A valid privilege Id") + @PathVariable("privilegeId") final String privilegeId) { + return this.privilegeService.deletePrivilegeByPrivilegeId(privilegeId); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeService.java deleted file mode 100644 index e5274d56c..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeService.java +++ /dev/null @@ -1,107 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.rest; - -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import java.util.List; -import java.util.UUID; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; - -/** - *

Endpoint for service handling business logic for privileges. - *
Note: Only users with the super admin role can access this endpoint.

- */ -@Api -@Path("/privilege") -public class PrivilegeService extends BaseEntityService { - - Logger logger = LoggerFactory.getLogger(PrivilegeService.class); - - @Inject - PrivilegeRepository privilegeRepo; - - @Context - SecurityContext securityContext; - - public PrivilegeService() { - super(Privilege.class); - } - - @ApiOperation(value = "GET information of one Privilege with the UUID, requires ADMIN or SUPER_ADMIN role") - @GET - @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("/{privilegeId}") - public Response getPrivilegeById( - @ApiParam(value="The UUID of the privilege to fetch information about") - @PathParam("privilegeId") String privilegeId) { - return getEntityById(privilegeId,privilegeRepo); - } - - @ApiOperation(value = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") - @GET - @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("") - public Response getPrivilegeAll() { - return getEntityAll(privilegeRepo); - } - - @ApiOperation(value = "POST a list of privileges, requires SUPER_ADMIN role") - @POST - @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") - public Response addPrivilege( - @ApiParam(required = true, value = "A list of privileges in JSON format") - List privileges){ - return addEntity(privileges, privilegeRepo); - } - - @ApiOperation(value = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") - @PUT - @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") - public Response updatePrivilege( - @ApiParam(required = true, value = "A list of privilege with fields to be updated in JSON format") - List privileges){ - return updateEntity(privileges, privilegeRepo); - } - - @ApiOperation(value = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE - @RolesAllowed({SUPER_ADMIN}) - @Path("/{privilegeId}") - public Response removeById( - @ApiParam(required = true, value = "A valid privilege Id") - @PathParam("privilegeId") final String privilegeId) { - Privilege privilege = privilegeRepo.getById(UUID.fromString(privilegeId)); - if (ADMIN.equals(privilege.getName())){ - logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) - + ", is trying to remove the system admin privilege: " + ADMIN); - return PICSUREResponse.protocolError("System Admin privilege cannot be removed - uuid: " + privilege.getUuid().toString() - + ", name: " + privilege.getName()); - } - - return removeEntityById(privilegeId, privilegeRepo); - } - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java similarity index 86% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index c58cfdf04..ae8378cfa 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -2,11 +2,11 @@ import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -35,9 +35,9 @@ */ @Api @Path("/role") -public class RoleService extends BaseEntityService { +public class RoleController extends BaseEntityService { - Logger logger = LoggerFactory.getLogger(RoleService.class); + Logger logger = LoggerFactory.getLogger(RoleController.class); @Context SecurityContext securityContext; @@ -48,7 +48,7 @@ public class RoleService extends BaseEntityService { @Inject PrivilegeRepository privilegeRepo; - public RoleService() { + public RoleController() { super(Role.class); } @@ -56,7 +56,7 @@ public RoleService() { @GET @Path("/{roleId}") @RolesAllowed({ADMIN, SUPER_ADMIN}) - public Response getRoleById( + public ResponseEntity getRoleById( @ApiParam(value="The UUID of the Role to fetch information about") @PathParam("roleId") String roleId) { return getEntityById(roleId,roleRepo); @@ -66,7 +66,7 @@ public Response getRoleById( @GET @Path("") @RolesAllowed({ADMIN, SUPER_ADMIN}) - public Response getRoleAll() { + public ResponseEntity getRoleAll() { return getEntityAll(roleRepo); } @@ -76,7 +76,7 @@ public Response getRoleAll() { @RolesAllowed({SUPER_ADMIN}) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response addRole( + public ResponseEntity addRole( @ApiParam(required = true, value = "A list of Roles in JSON format") List roles){ checkPrivilegeAssociation(roles); @@ -89,7 +89,7 @@ public Response addRole( @RolesAllowed({SUPER_ADMIN}) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response updateRole( + public ResponseEntity updateRole( @ApiParam(required = true, value = "A list of Roles with fields to be updated in JSON format") List roles){ checkPrivilegeAssociation(roles); @@ -101,7 +101,7 @@ public Response updateRole( @DELETE @RolesAllowed({SUPER_ADMIN}) @Path("/{roleId}") - public Response removeById( + public ResponseEntity removeById( @ApiParam(required = true, value = "A valid Role Id") @PathParam("roleId") final String roleId) { Role role = roleRepo.getById(UUID.fromString(roleId)); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceEndpoint.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java similarity index 75% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceEndpoint.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java index 40aeb1e86..f4125a8e8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceEndpoint.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java @@ -1,8 +1,8 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.TermsOfService; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import edu.harvard.hms.dbmi.avillach.auth.service.TOSService; +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -20,9 +20,9 @@ *

Endpoint for creating and updating terms of service entities. Records when a user accepts a term of service.

*/ @Path("tos") -public class TermsOfServiceEndpoint extends BaseEntityService { +public class TermsOfSerivceController extends BaseEntityService { - public TermsOfServiceEndpoint() { + public TermsOfSerivceController() { super(TermsOfService.class); } @@ -33,7 +33,7 @@ public TermsOfServiceEndpoint() { @Path("/latest") @GET @Produces("text/html") - public Response getLatestTermsOfService(){ + public ResponseEntity getLatestTermsOfService(){ return Response.ok(tosService.getLatest()).build(); } @@ -42,7 +42,7 @@ public Response getLatestTermsOfService(){ @RolesAllowed({AuthNaming.AuthRoleNaming.ADMIN, SUPER_ADMIN}) @Consumes("text/html") @Produces("application/json") - public Response updateTermsOfService( + public ResponseEntity updateTermsOfService( @ApiParam(required = true, value = "A html page for updating") String html){ return Response.status(201).entity(tosService.updateTermsOfService(html)).build(); } @@ -51,7 +51,7 @@ public Response updateTermsOfService( @Path("/") @GET @Produces("text/plain") - public Response hasUserAcceptedTOS(@Context SecurityContext securityContext){ + public ResponseEntity hasUserAcceptedTOS(@Context SecurityContext securityContext){ String userSubject = securityContext.getUserPrincipal().getName(); return Response.ok(tosService.hasUserAcceptedLatest(userSubject)).build(); } @@ -60,7 +60,7 @@ public Response hasUserAcceptedTOS(@Context SecurityContext securityContext){ @Path("/accept") @POST @Produces("application/json") - public Response acceptTermsOfService(@Context SecurityContext securityContext){ + public ResponseEntity acceptTermsOfService(@Context SecurityContext securityContext){ String userSubject = securityContext.getUserPrincipal().getName(); tosService.acceptTermsOfService(userSubject); return Response.ok().build(); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java new file mode 100644 index 000000000..f3da366a1 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -0,0 +1,319 @@ +package edu.harvard.hms.dbmi.avillach.auth.rest; + +import edu.harvard.dbmi.avillach.util.exception.ApplicationException; +import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.security.Principal; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; + +/** + *

Token introspection endpoint called by an application to validate a user's token and permissions by request.

+ * + *

Here, a registered application asks if the user behind a token is allowed to perform certain activities by + * showing this endpoint the token and where the user wants to go.

+ *

To accomplish this, this endpoint validates the incoming token, then checks if the user behind the token + * is authorized to access the URLs they queried and send data along with them. The AuthorizationService class handles authorization + * {@link AuthorizationService} at the access rule level, but this endpoint handles token validation and pre-check at the privilege level.

+ */ +@Api +@Controller("/token") +public class TokenController { + + private Logger logger = LoggerFactory.getLogger(TokenController.class); + + private final UserRepository userRepo; + + private final AuthorizationService authorizationService; + + @Context + SecurityContext securityContext; + + @Autowired + public TokenController(UserRepository userRepo, AuthorizationService authorizationService) { + this.userRepo = userRepo; + this.authorizationService = authorizationService; + } + + @ApiOperation(value = "Token introspection endpoint for user to retrieve a valid token") + @PostMapping("/inspect") + @Consumes("application/json") + public ResponseEntity inspectToken( + @ApiParam(required = true, value = "A JSON object that at least" + + " include a user the token for validation") + Map inputMap) { + logger.info("TokenInspect starting..."); + TokenInspection tokenInspection = _inspectToken(inputMap); + if (tokenInspection.message != null) + tokenInspection.responseMap.put("message", tokenInspection.message); + + logger.info("Finished token introspection."); + return PICSUREResponse.success(tokenInspection.responseMap); + } + + /** + * This endpoint currently is only for a user token to be refreshed. + * Application token won't work here. + * + * @return + */ + @ApiOperation(value = "To refresh current user's token if the user is an active user") + @GET + @Path("/refresh") + public ResponseEntity refreshToken(@RequestHeader("Authorization") String authorizationHeader) { + logger.debug("RefreshToken starting..."); + + // still need to check if the user is in the database or not, + // just in case something changes in the middle + Principal principal = securityContext.getUserPrincipal(); + if (!(principal instanceof User)) { + logger.error("refreshToken() Security context didn't have a user stored."); + } + User user = (User) principal; + + if (user.getUuid() == null) { + logger.error("refreshToken() Stored user doesn't have a uuid."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + user = userRepo.getById(user.getUuid()); + if (user == null) { + logger.error("refreshToken() When retrieving current user, it returned null, the user might be removed from database"); + throw new NotAuthorizedException("User doesn't exist anymore"); + } + + if (!user.isActive()) { + logger.error("refreshToken() The user has just been deactivated."); + throw new NotAuthorizedException("User has been deactivated."); + } + + String subject = user.getSubject(); + if (subject == null || subject.isEmpty()) { + logger.error("refreshToken() subject doesn't exist in the user."); + } + + // parse origin token + Jws jws; + try { + String token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader).orElseThrow(() -> new NotAuthorizedException("Token not found")); + jws = parseToken(token); + + } catch (NotAuthorizedException ex) { + return PICSUREResponse.protocolError("Cannot parse original token"); + } + + Claims claims = jws.getBody(); + + // just check if the subject is along with the database record, + // just in case something has changed in middle + if (!subject.equals(claims.getSubject())) { + logger.error("refreshToken() user subject is not the same as the subject of the input token"); + return PICSUREResponse.applicationError("Inner application error, try again or contact admin."); + } + + Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); + String refreshedToken = JWTUtil.createJwtToken(JAXRSConfiguration.clientSecret, + claims.getId(), + claims.getIssuer(), + claims, + subject, + JAXRSConfiguration.tokenExpirationTime); + + logger.debug("Finished RefreshToken and new token has been generated."); + return PICSUREResponse.success(Map.of( + "token", refreshedToken, + "expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString() + )); + } + + + /** + * @param inputMap + * @return + */ + private TokenInspection _inspectToken(Map inputMap) { + logger.debug("_inspectToken, the incoming token map is: {}", inputMap.entrySet() + .stream() + .map(entry -> entry.getKey() + " - " + entry.getValue()) + .collect(Collectors.joining(", "))); + + TokenInspection tokenInspection = new TokenInspection(); + + String token = (String) inputMap.get("token"); + if (token == null || token.isEmpty()) { + logger.error("Token - " + token + " is blank"); + tokenInspection.message = "Token not found"; + return tokenInspection; + } + + // parse the token based on client secret + // don't need to check if jws is null or not, since parse function has already checked + Jws jws; + try { + jws = parseToken(token); + + /* + * token has been verified, now we remove it from inputMap, so further logs will not be able to log + * the token accidentally! + */ + inputMap.remove("token"); + } catch (NotAuthorizedException ex) { + // only when the token is for sure invalid, we can dump it into the log. + logger.error("_inspectToken() the token - " + token + " - is invalid with exception: " + ex.getMessage()); + tokenInspection.message = ex.getMessage(); + return tokenInspection; + } + + + Application application; + + try { + application = (Application) securityContext.getUserPrincipal(); + } catch (ClassCastException ex) { + logger.error(securityContext.getUserPrincipal().getName() + + " - " + securityContext.getUserPrincipal().getClass().getSimpleName() + + " - is trying to use token introspection endpoint" + + ", but it is not an application"); + throw new ApplicationException("The application token does not associate with an application but " + + securityContext.getUserPrincipal().getClass().getSimpleName()); + } + + // application null check should be finished when application token goes through the JWTFilter authentication process, + // here we just double check it to prevent a null application object goes further. + if (application == null) { + logger.error("_inspectToken() There is no application in securityContext, which shall not be."); + throw new ApplicationException("Inner application error, please ask admin to check the log."); + } + + String subject = jws.getBody().getSubject(); + + // get the user based on subject field in token + User user; + + // check if the token is the special LONG_TERM_TOKEN, + // the differences between this special token and normal token is + // one user only has one long_term_token stored in database, + // this token needs to be exactly the same as the database one. + // If the token refreshed, the old one will be invalid. But normal + // token will not invalid the old ones if refreshed. + boolean isLongTermToken = false; + if (subject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { + subject = subject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); + isLongTermToken = true; + } + + user = userRepo.getUniqueResultByColumn("subject", subject); + logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); + if (user == null) { + logger.error("_inspectToken() could not find user with subject " + subject); + tokenInspection.message = "user doesn't exist"; + return tokenInspection; + } + + + //Essentially we want to return jws.getBody() with an additional active: true field + //only under certain circumstances, the token will return active + boolean isAuthorizationPassed = false; + String errorMsg = null; + + // long term token needs to be the same as the token in the database user table, if + // not the token might has been compromised, which will not go through the authorization check + boolean isLongTermTokenCompromised = false; + if (isLongTermToken && !token.equals(user.getToken())) { + // in long_term_token mode, the token needs to be exactly the same as the token in user table + isLongTermTokenCompromised = true; + logger.error("_inspectToken User " + user.getUuid() + "|" + user.getSubject() + + "is sending a long term token that is not matching the record in database user table."); + errorMsg = "Cannot find matched long term token, your token might have been refreshed."; + } + + // we go through the authorization layer check only if we need to in order to improve the performance + // the logic here, if the token associated with a user, we will start the authorization check. + // If the current application has at least one privilege, the user must have one privilege associated to the application + // pass the accessRule check if there is any accessRules associated with. + if (application.getPrivileges() == null || application.getPrivileges().isEmpty()) { + // if no privileges associated + isAuthorizationPassed = true; + //we still want to log this, though. + logger.info("ACCESS_LOG ___ " + user.getUuid() + "," + user.getEmail() + "," + user.getName() + + " ___ has been granted access to execute query ___ " + inputMap.get("request") + " ___ in application ___ " + application.getName() + + " ___ NO APP PRIVILEGES DEFINED"); + } else if (!isLongTermTokenCompromised + && user.getRoles() != null + // The protocol between applications and PSAMA is application will + // attach everything that needs to be verified in request field of inputMap + // besides token. So here we should attach everything in request. + && authorizationService.isAuthorized(application, inputMap.get("request"), user)) { + isAuthorizationPassed = true; + } else { + // if isLongTermTokenCompromised flag is true, + // the error message has already been set previously + if (!isLongTermTokenCompromised) + errorMsg = "User doesn't have enough privileges."; + } + + if (isAuthorizationPassed) { + tokenInspection.responseMap.put("active", true); + ArrayList roles = new ArrayList(); + for (Privilege p : user.getTotalPrivilege()) { + roles.add(p.getName()); + } + tokenInspection.responseMap.put("roles", String.join(",", roles)); + } else { + if (errorMsg != null) + tokenInspection.message = errorMsg; + return tokenInspection; + } + + tokenInspection.responseMap.putAll(jws.getBody()); + + // attach all privileges associated with the application to the responseMap + tokenInspection.responseMap.put("privileges", user.getPrivilegeNameSetByApplication(application)); + + + logger.info("_inspectToken() Successfully inspect and return response map: " + + tokenInspection.responseMap.entrySet() + .stream() + .map(entry -> entry.getKey() + " - " + entry.getValue()) + .collect(Collectors.joining(", "))); + return tokenInspection; + } + + /** + * inner used token introspection class with active:false included + */ + private class TokenInspection { + Map responseMap = new HashMap<>(); + String message = null; + + public TokenInspection() { + responseMap.put("active", false); + } + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenService.java deleted file mode 100644 index fdbf75b58..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenService.java +++ /dev/null @@ -1,325 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.rest; - -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.AuthorizationService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; -import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import java.security.Principal; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.stream.Collectors; - -/** - *

Token introspection endpoint called by an application to validate a user's token and permissions by request.

- * - *

Here, a registered application asks if the user behind a token is allowed to perform certain activities by - * showing this endpoint the token and where the user wants to go.

- *

To accomplish this, this endpoint validates the incoming token, then checks if the user behind the token - * is authorized to access the URLs they queried and send data along with them. The AuthorizationService class handles authorization - * {@link AuthorizationService} at the access rule level, but this endpoint handles token validation and pre-check at the privilege level.

- */ -@Api -@Path("/token") -public class TokenService { - - private Logger logger = LoggerFactory.getLogger(TokenService.class); - - @Inject - UserRepository userRepo; - - @Inject - ApplicationRepository applicationRepo; - - @Inject - AuthorizationService authorizationService; - - @Context - SecurityContext securityContext; - - @ApiOperation(value = "Token introspection endpoint for user to retrieve a valid token") - @POST - @Path("/inspect") - @Consumes("application/json") - public Response inspectToken( - @ApiParam(required = true, value = "A JSON object that at least" + - " include a user the token for validation") - Map inputMap){ - logger.info("TokenInspect starting..."); - TokenInspection tokenInspection = _inspectToken(inputMap); - if (tokenInspection.message != null) - tokenInspection.responseMap.put("message", tokenInspection.message); - - logger.info("Finished token introspection."); - return PICSUREResponse.success(tokenInspection.responseMap); - } - - /** - * This endpoint currently is only for a user token to be refreshed. - * Application token won't work here. - * - * @return - */ - @ApiOperation(value = "To refresh current user's token if the user is an active user") - @GET - @Path("/refresh") - public Response refreshToken(@Context HttpHeaders httpHeaders){ - logger.debug("RefreshToken starting..."); - - // still need to check if the user is in the database or not, - // just in case something changes in the middle - Principal principal = securityContext.getUserPrincipal(); - if (!(principal instanceof User)){ - logger.error("refreshToken() Security context didn't have a user stored."); - } - User user = (User) principal; - - if (user.getUuid() == null){ - logger.error("refreshToken() Stored user doesn't have a uuid."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - user = userRepo.getById(user.getUuid()); - if (user == null){ - logger.error("refreshToken() When retrieving current user, it returned null, the user might be removed from database"); - throw new NotAuthorizedException("User doesn't exist anymore"); - } - - if (!user.isActive()){ - logger.error("refreshToken() The user has just been deactivated."); - throw new NotAuthorizedException("User has been deactivated."); - } - - String subject = user.getSubject(); - if (subject == null || subject.isEmpty()){ - logger.error("refreshToken() subject doesn't exist in the user."); - } - - // parse origin token - Jws jws; - try { - jws = AuthUtils.parseToken(JAXRSConfiguration.clientSecret, - // the original token should be able to grab from header, otherwise, it should be stopped - // at JWTFilter level - httpHeaders.getHeaderString(HttpHeaders.AUTHORIZATION) - .substring(6) - .trim()); - } catch (NotAuthorizedException ex) { - return PICSUREResponse.protocolError("Cannot parse original token"); - } - - Claims claims = jws.getBody(); - - // just check if the subject is along with the database record, - // just in case something has changed in middle - if (!subject.equals(claims.getSubject())){ - logger.error("refreshToken() user subject is not the same as the subject of the input token"); - return PICSUREResponse.applicationError("Inner application error, try again or contact admin."); - } - - Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); - String refreshedToken = JWTUtil.createJwtToken(JAXRSConfiguration.clientSecret, - claims.getId(), - claims.getIssuer(), - claims, - subject, - JAXRSConfiguration.tokenExpirationTime); - - logger.debug("Finished RefreshToken and new token has been generated."); - return PICSUREResponse.success(Map.of( - "token", refreshedToken, - "expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString() - )); - } - - - - /** - * @param inputMap - * @return - */ - private TokenInspection _inspectToken(Map inputMap){ - logger.debug("_inspectToken, the incoming token map is: {}", inputMap.entrySet() - .stream() - .map(entry -> entry.getKey() + " - " + entry.getValue()) - .collect(Collectors.joining(", "))); - - TokenInspection tokenInspection = new TokenInspection(); - - String token = (String)inputMap.get("token"); -// logger.debug("getting token: " + token); <- in any cases, token should not be shown in log if the token could be a valid token. - if (token == null || token.isEmpty()){ - logger.error("Token - "+ token + " is blank"); - tokenInspection.message = "Token not found"; - return tokenInspection; - } - - // parse the token based on client secret - // don't need to check if jws is null or not, since parse function has already checked - Jws jws; - try { - jws = AuthUtils.parseToken(JAXRSConfiguration.clientSecret, token); - - /** - * token has been verified, now we remove it from inputMap, so further logs will not be able to log - * the token accidentally! - */ - inputMap.remove("token"); - } catch (NotAuthorizedException ex) { - // only when the token is for sure invalid, we can dump it into the log. - logger.error("_inspectToken() the token - " + token + " - is invalid with exception: " + ex.getChallenges()); - tokenInspection.message = ex.getChallenges().toString(); - return tokenInspection; - } - - - Application application; - - try { - application = (Application) securityContext.getUserPrincipal(); - } catch (ClassCastException ex){ - logger.error(securityContext.getUserPrincipal().getName() - + " - " + securityContext.getUserPrincipal().getClass().getSimpleName() + - " - is trying to use token introspection endpoint" + - ", but it is not an application"); - throw new ApplicationException("The application token does not associate with an application but " - + securityContext.getUserPrincipal().getClass().getSimpleName()); - } - - // application null check should be finished when application token goes through the JWTFilter authentication process, - // here we just double check it to prevent a null application object goes further. - if (application == null){ - logger.error("_inspectToken() There is no application in securityContext, which shall not be."); - throw new ApplicationException("Inner application error, please ask admin to check the log."); - } - - String subject = jws.getBody().getSubject(); - - // get the user based on subject field in token - User user; - - // check if the token is the special LONG_TERM_TOKEN, - // the differences between this special token and normal token is - // one user only has one long_term_token stored in database, - // this token needs to be exactly the same as the database one. - // If the token refreshed, the old one will be invalid. But normal - // token will not invalid the old ones if refreshed. - boolean isLongTermToken = false; - if (subject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { - subject = subject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length()+1); - isLongTermToken = true; - } - - user = userRepo.getUniqueResultByColumn("subject", subject); - logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); - if (user == null) { - logger.error("_inspectToken() could not find user with subject " + subject); - tokenInspection.message = "user doesn't exist"; - return tokenInspection; - } - - - - //Essentially we want to return jws.getBody() with an additional active: true field - //only under certain circumstances, the token will return active - boolean isAuthorizationPassed = false; - String errorMsg = null; - - // long term token needs to be the same as the token in the database user table, if - // not the token might has been compromised, which will not go through the authorization check - boolean isLongTermTokenCompromised = false; - if (isLongTermToken && !token.equals(user.getToken())) { - // in long_term_token mode, the token needs to be exactly the same as the token in user table - isLongTermTokenCompromised = true; - logger.error("_inspectToken User " + user.getUuid() + "|" + user.getSubject() - + "is sending a long term token that is not matching the record in database user table."); - errorMsg = "Cannot find matched long term token, your token might have been refreshed."; - } - - // we go through the authorization layer check only if we need to in order to improve the performance - // the logic here, if the token associated with a user, we will start the authorization check. - // If the current application has at least one privilege, the user must have one privilege associated to the application - // pass the accessRule check if there is any accessRules associated with. - if (application.getPrivileges() == null || application.getPrivileges().isEmpty()){ - // if no privileges associated - isAuthorizationPassed = true; - //we still want to log this, though. - logger.info("ACCESS_LOG ___ " + user.getUuid() + "," + user.getEmail() + "," + user.getName() + - " ___ has been granted access to execute query ___ " + inputMap.get("request") + " ___ in application ___ " + application.getName() - + " ___ NO APP PRIVILEGES DEFINED"); - } else if ( !isLongTermTokenCompromised - && user.getRoles() != null - // The protocol between applications and PSAMA is application will - // attach everything that needs to be verified in request field of inputMap - // besides token. So here we should attach everything in request. - && authorizationService.isAuthorized(application, inputMap.get("request"), user)) { - isAuthorizationPassed = true; - } else { - // if isLongTermTokenCompromised flag is true, - // the error message has already been set previously - if (!isLongTermTokenCompromised) - errorMsg = "User doesn't have enough privileges."; - } - - if (isAuthorizationPassed){ - tokenInspection.responseMap.put("active", true); - ArrayList roles = new ArrayList(); - for(Privilege p : user.getTotalPrivilege()) { - roles.add(p.getName()); - } - tokenInspection.responseMap.put("roles", String.join(",", roles)); - } else { - if (errorMsg != null ) - tokenInspection.message = errorMsg; - return tokenInspection; - } - - tokenInspection.responseMap.putAll(jws.getBody()); - - // attach all privileges associated with the application to the responseMap - tokenInspection.responseMap.put("privileges", user.getPrivilegeNameSetByApplication(application)); - - - logger.info("_inspectToken() Successfully inspect and return response map: " - + tokenInspection.responseMap.entrySet() - .stream() - .map(entry -> entry.getKey() + " - " + entry.getValue()) - .collect(Collectors.joining(", "))); - return tokenInspection; - } - - /** - * inner used token introspection class with active:false included - */ - private class TokenInspection { - Map responseMap = new HashMap<>(); - String message = null; - - public TokenInspection() { - responseMap.put("active", false); - } - } - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java similarity index 67% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index eb9304b1f..532079cf1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -6,14 +6,14 @@ import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.*; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.*; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import edu.harvard.hms.dbmi.avillach.auth.service.MailService; +import edu.harvard.hms.dbmi.avillach.auth.entity.*; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.MailService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import edu.harvard.hms.dbmi.avillach.auth.utils.JsonUtils; import io.jsonwebtoken.Claims; @@ -21,17 +21,17 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; - +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestHeader; -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; import javax.mail.MessagingException; import javax.transaction.Transactional; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.*; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -44,56 +44,48 @@ *

Endpoint for service handling business logic for users.

*/ @Api -@Path("/user") -public class UserService extends BaseEntityService { - - Logger logger = LoggerFactory.getLogger(UserService.class); +@Controller("/user") +public class UserController extends BaseEntityService { - @Context - SecurityContext securityContext; + Logger logger = LoggerFactory.getLogger(UserController.class); - @Inject - UserRepository userRepo; +// @Context +// SecurityContext securityContext; - @Inject - RoleRepository roleRepo; + private final RoleRepository roleRepo; - @Inject - PrivilegeRepository privilegeRepo; + private final ConnectionRepository connectionRepo; - @Inject - ConnectionRepository connectionRepo; + private final ApplicationRepository applicationRepo; - @Inject - ApplicationRepository applicationRepo; + private final AuthUtils authUtil; - @Inject - AccessRuleRepository accessruleRepo; - - @Inject - AuthUtils authUtil; - - private MailService mailService = new MailService(); + private MailService mailService; - public UserService() { + @Autowired + public UserController(RoleRepository roleRepo, ConnectionRepository connectionRepo, ApplicationRepository applicationRepo, AuthUtils authUtil) { super(User.class); + this.roleRepo = roleRepo; + this.connectionRepo = connectionRepo; + this.applicationRepo = applicationRepo; + this.authUtil = authUtil; } @ApiOperation(value = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") @GET @RolesAllowed({ADMIN, SUPER_ADMIN}) @Path("/{userId}") - public Response getUserById( - @ApiParam(required = true, value="The UUID of the user to fetch information about") + public ResponseEntity getUserById( + @ApiParam(required = true, value = "The UUID of the user to fetch information about") @PathParam("userId") String userId) { - return getEntityById(userId,userRepo); + return getEntityById(userId, userRepo); } @ApiOperation(value = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") @GET @RolesAllowed({ADMIN, SUPER_ADMIN}) @Path("") - public Response getUserAll() { + public ResponseEntity getUserAll() { return getEntityAll(userRepo); } @@ -103,44 +95,46 @@ public Response getUserAll() { @RolesAllowed({ADMIN}) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response addUser( + public ResponseEntity addUser( @ApiParam(required = true, value = "A list of user in JSON format") - List users){ - User currentUser = (User)securityContext.getUserPrincipal(); - if (currentUser == null || currentUser.getUuid() == null){ + List users) { + User currentUser = (User) securityContext.getUserPrincipal(); + if (currentUser == null || currentUser.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - + checkAssociation(users); boolean allowAdd = true; - for(User user : users) { - logger.debug("Adding User " + user); - if (!allowUpdateSuperAdminRole(currentUser, user, null)){ + for (User user : users) { + logger.debug("Adding User " + user); + if (!allowUpdateSuperAdminRole(currentUser, user, null)) { allowAdd = false; break; } - if(user.getEmail() == null) { - try { - HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); - List emailKeys = metadata.keySet().stream().filter((key)->{return key.toLowerCase().contains("email");}).collect(Collectors.toList()); - if(emailKeys.size()>0) { - user.setEmail(metadata.get(emailKeys.get(0))); - } - } catch (IOException e) { - e.printStackTrace(); - } - } + if (user.getEmail() == null) { + try { + HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); + List emailKeys = metadata.keySet().stream().filter((key) -> { + return key.toLowerCase().contains("email"); + }).collect(Collectors.toList()); + if (emailKeys.size() > 0) { + user.setEmail(metadata.get(emailKeys.get(0))); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } - if (allowAdd){ - Response updateResponse = addEntity(users, userRepo); + if (allowAdd) { + Response updateResponse = addEntity(users, userRepo); sendUserUpdateEmailsFromResponse(updateResponse); return updateResponse; } else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles ["+ currentUser.getRoleString() + "] - is not allowed to grant " + logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); } @@ -152,9 +146,9 @@ public Response addUser( @RolesAllowed({ADMIN}) @Consumes(MediaType.APPLICATION_JSON) @Path("/") - public Response updateUser(List users){ - User currentUser = (User)securityContext.getUserPrincipal(); - if (currentUser == null || currentUser.getUuid() == null){ + public ResponseEntity updateUser(List users) { + User currentUser = (User) securityContext.getUserPrincipal(); + if (currentUser == null || currentUser.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } @@ -165,7 +159,7 @@ public Response updateUser(List users){ for (User user : users) { User originalUser = userRepo.getById(user.getUuid()); - if (allowUpdateSuperAdminRole(currentUser, user, originalUser)){ + if (allowUpdateSuperAdminRole(currentUser, user, originalUser)) { continue; } else { allowUpdate = false; @@ -173,54 +167,53 @@ public Response updateUser(List users){ } } - if (allowUpdate){ + if (allowUpdate) { Response updateResponse = updateEntity(users, userRepo); sendUserUpdateEmailsFromResponse(updateResponse); return updateResponse; - } - else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles ["+ currentUser.getRoleString() + "] - is not allowed to grant or remove " + } else { + logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant or remove " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); } } private void sendUserUpdateEmailsFromResponse(Response updateResponse) { - logger.debug("Sending email"); - try { - Object entity = updateResponse.getEntity(); - if(entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { - PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent)entity; - List addedUsers = (List) okResponse.getContent(); - String message = okResponse.getMessage(); - for(User user : addedUsers) { - try { - mailService.sendUsersAccessEmail(user); - } catch (MessagingException e) { - logger.error("Failed to send email! " + e.getLocalizedMessage()); - logger.debug("Exception Trace: ", e); - okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); - } - } - } - } catch (Exception e) { - logger.error("Failed to send email - unhandled exception: ", e); - } - logger.debug("finished email sending method"); - } - - /** + logger.debug("Sending email"); + try { + Object entity = updateResponse.getEntity(); + if (entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { + PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent) entity; + List addedUsers = (List) okResponse.getContent(); + String message = okResponse.getMessage(); + for (User user : addedUsers) { + try { + mailService.sendUsersAccessEmail(user); + } catch (MessagingException e) { + logger.error("Failed to send email! " + e.getLocalizedMessage()); + logger.debug("Exception Trace: ", e); + okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); + } + } + } + } catch (Exception e) { + logger.error("Failed to send email - unhandled exception: ", e); + } + logger.debug("finished email sending method"); + } + + /** * This check is to prevent non-super-admin user to create/remove a super admin role * against a user(include themselves). Only super admin user could perform such actions. * *

- * if operations not related to super admin role updates, this will return true. + * if operations not related to super admin role updates, this will return true. *

- * + *

* The logic here is checking the state of the super admin role in the input and output users, * if the state is changed, check if the user is a super admin to determine if the user could perform the action. * - * @param currentUser the user trying to perform the action + * @param currentUser the user trying to perform the action * @param inputUser * @param originalUser there could be no original user when adding a new user * @return @@ -228,11 +221,11 @@ private void sendUserUpdateEmailsFromResponse(Response updateResponse) { private boolean allowUpdateSuperAdminRole( @NotNull User currentUser, @NotNull User inputUser, - User originalUser){ + User originalUser) { // if current user is a super admin, this check will return true for (Role role : currentUser.getRoles()) { - for (Privilege privilege : role.getPrivileges()){ + for (Privilege privilege : role.getPrivileges()) { if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { return true; } @@ -242,19 +235,19 @@ private boolean allowUpdateSuperAdminRole( boolean inputUserHasSuperAdmin = false; boolean originalUserHasSuperAdmin = false; - for (Role role : inputUser.getRoles()){ - for (Privilege privilege : role.getPrivileges()){ - if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)){ + for (Role role : inputUser.getRoles()) { + for (Privilege privilege : role.getPrivileges()) { + if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { inputUserHasSuperAdmin = true; break; } } } - if (originalUser != null){ - for (Role role : originalUser.getRoles()){ - for (Privilege privilege : role.getPrivileges()){ - if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)){ + if (originalUser != null) { + for (Role role : originalUser.getRoles()) { + for (Privilege privilege : role.getPrivileges()) { + if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { originalUserHasSuperAdmin = true; break; } @@ -283,18 +276,18 @@ private boolean allowUpdateSuperAdminRole( @Transactional @GET @Path("/me") - public Response getCurrentUser( - @Context HttpHeaders httpHeaders, + public ResponseEntity getCurrentUser( + @RequestHeader("Authorization") String authorizationHeader, @ApiParam(required = false, value = "Attribute that represents if a long term token will attach to the response") - @QueryParam("hasToken") Boolean hasToken){ + @QueryParam("hasToken") Boolean hasToken) { User user = (User) securityContext.getUserPrincipal(); - if (user == null || user.getUuid() == null){ + if (user == null || user.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } user = userRepo.getById(user.getUuid()); - if (user == null){ + if (user == null) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } @@ -309,13 +302,13 @@ public Response getCurrentUser( // We are expecting the queryScope string as plain string. If it is a JSON, we could change the // code to use JsonUtils.mergeTemplateMap(Map, Map) Set privileges = user.getTotalPrivilege(); - if (privileges != null && !privileges.isEmpty()){ + if (privileges != null && !privileges.isEmpty()) { Set scopes = new TreeSet<>(); - privileges.stream().filter(privilege -> privilege.getQueryScope() !=null).forEach(privilege -> { + privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { try { Arrays.stream(objectMapper.readValue(privilege.getQueryScope(), String[].class)) - .filter(x -> x!=null) - .forEach(scopeList-> scopes.addAll(Arrays.asList(scopeList))); + .filter(x -> x != null) + .forEach(scopeList -> scopes.addAll(Arrays.asList(scopeList))); } catch (IOException e) { logger.error("Parsing issue for privilege " + privilege.getUuid() + " queryScope", e); } @@ -323,12 +316,12 @@ public Response getCurrentUser( userForDisplay.setQueryScopes(scopes); } - if (hasToken!=null){ + if (hasToken != null) { - if (user.getToken() != null && !user.getToken().isEmpty()){ + if (user.getToken() != null && !user.getToken().isEmpty()) { userForDisplay.setToken(user.getToken()); } else { - user.setToken(generateUserLongTermToken(httpHeaders)); + user.setToken(generateUserLongTermToken(authorizationHeader)); userRepo.merge(user); userForDisplay.setToken(user.getToken()); } @@ -341,30 +334,30 @@ public Response getCurrentUser( @Transactional @GET @Path("/me/queryTemplate/{applicationId}") - public Response getQueryTemplate( + public ResponseEntity getQueryTemplate( @ApiParam(value = "Application Id for the returning queryTemplate") - @PathParam("applicationId") String applicationId){ + @PathParam("applicationId") String applicationId) { - if (applicationId == null || applicationId.trim().isEmpty()){ + if (applicationId == null || applicationId.trim().isEmpty()) { logger.error("getQueryTemplate() input application UUID is null or empty."); throw new ProtocolException("Input application UUID is incorrect."); } User user = (User) securityContext.getUserPrincipal(); - if (user == null || user.getUuid() == null){ + if (user == null || user.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } user = userRepo.getById(user.getUuid()); - if (user == null){ + if (user == null) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } Application application = applicationRepo.getById(UUID.fromString(applicationId)); - if (application == null){ + if (application == null) { logger.error("getQueryTemplate() cannot find corresponding application by UUID: " + applicationId); throw new ProtocolException("Cannot find application by input UUID: " + applicationId); } @@ -378,7 +371,7 @@ public Response getQueryTemplate( @Transactional @GET @Path("/me/queryTemplate") - public Response getQueryTemplate(){ + public ResponseEntity getQueryTemplate() { return getQueryTemplate(JAXRSConfiguration.defaultApplicationUUID); } @@ -386,16 +379,16 @@ public Response getQueryTemplate(){ private String mergeTemplate(User user, Application application) { String resultJSON = null; Map mergedTemplateMap = null; - for (Privilege privilege : user.getPrivilegesByApplication(application)){ + for (Privilege privilege : user.getPrivilegesByApplication(application)) { String template = privilege.getQueryTemplate(); - logger.debug("mergeTemplate() processing template:"+template); - if (template == null || template.trim().isEmpty()){ + logger.debug("mergeTemplate() processing template:" + template); + if (template == null || template.trim().isEmpty()) { continue; } Map templateMap = null; try { templateMap = objectMapper.readValue(template, Map.class); - } catch (IOException ex){ + } catch (IOException ex) { logger.error("mergeTemplate() cannot convert stored queryTemplate using Jackson, the queryTemplate is: " + template); throw new ApplicationException("Inner application error, please contact admin."); } @@ -404,7 +397,7 @@ private String mergeTemplate(User user, Application application) { continue; } - if (mergedTemplateMap == null){ + if (mergedTemplateMap == null) { mergedTemplateMap = templateMap; continue; } @@ -437,18 +430,18 @@ private String mergeTemplate(User user, Application application) { @Transactional @GET @Path("/me/refresh_long_term_token") - public Response refreshUserToken( - @Context HttpHeaders httpHeaders, + public ResponseEntity refreshUserToken( + @RequestHeader HttpHeaders httpHeaders, @ApiParam(required = false, value = "A flag represents if the long term token will be returned or not") - @QueryParam("hasToken") Boolean hasToken){ + @QueryParam("hasToken") Boolean hasToken) { User user = (User) securityContext.getUserPrincipal(); - if (user == null || user.getUuid() == null){ + if (user == null || user.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } user = userRepo.getById(user.getUuid()); - if (user == null){ + if (user == null) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } @@ -465,31 +458,31 @@ public Response refreshUserToken( * Logic here is, retrieve the subject of the user from httpHeader. Then generate a long term one * with LONG_TERM_TOKEN_PREFIX| in front of the subject to be able to distinguish with regular ones, since * long term token only generated for accessing certain things to, in some degrees, decrease the insecurity. - * @param httpHeaders - * @return - * @throws ProtocolException + * + * @param authorizationHeader the authorization header + * @return the long term token + * @throws IllegalArgumentException if the authorization header is not presented */ - private String generateUserLongTermToken(HttpHeaders httpHeaders) throws ProtocolException{ - Jws jws; - try { - jws = AuthUtils.parseToken(clientSecret, - // the original token should be able to grab from header, otherwise, it should be stopped - // at JWTFilter level - httpHeaders.getHeaderString(HttpHeaders.AUTHORIZATION) - .substring(6) - .trim()); - } catch (NotAuthorizedException ex) { - throw new ProtocolException("Cannot parse token in header"); + private String generateUserLongTermToken(String authorizationHeader) { + if (!StringUtils.isNotBlank(authorizationHeader)) { + throw new IllegalArgumentException("Authorization header is not presented."); + } + + Optional token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader); + if (token.isEmpty()) { + throw new IllegalArgumentException("Token is not presented in the authorization header."); } + Jws jws = JWTUtil.parseToken(token.get()); + Claims claims = jws.getBody(); String tokenSubject = claims.getSubject(); - if (tokenSubject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX+"|")) { + if (tokenSubject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX + "|")) { // considering the subject already contains a "|" // to prevent infinitely adding the long term token prefix // we will grab the real subject here - tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length()+1); + tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); } return JWTUtil.createJwtToken(clientSecret, @@ -509,16 +502,15 @@ private String generateUserLongTermToken(HttpHeaders httpHeaders) throws Protoco * @param users * @return */ - private void checkAssociation(List users) throws ProtocolException{ - - for (User user: users){ - if (user.getRoles() != null){ + private void checkAssociation(List users) { + for (User user : users) { + if (user.getRoles() != null) { Set roles = new HashSet<>(); user.getRoles().stream().forEach(t -> roleRepo.addObjectToSet(roles, roleRepo, t)); user.setRoles(roles); } - if (user.getConnection() != null){ + if (user.getConnection() != null) { Connection connection = connectionRepo.getUniqueResultByColumn("id", user.getConnection().getId()); user.setConnection(connection); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java similarity index 77% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index d4ae09cc0..6c05afaf9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -1,13 +1,14 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserMetadataMappingRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.BaseEntityService; -import edu.harvard.hms.dbmi.avillach.auth.service.UserMetadataMappingService; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import org.springframework.http.ResponseEntity; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; @@ -26,9 +27,9 @@ */ @Api @Path("mapping") -public class UserMetadataMappingWebService extends BaseEntityService{ +public class UserMetadataMappingWebController extends BaseEntityService{ - public UserMetadataMappingWebService() { + public UserMetadataMappingWebController() { super(UserMetadataMapping.class); } @@ -46,7 +47,7 @@ public UserMetadataMappingWebService() { @Produces("application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) @Path("{connectionId}") - public Response getMappingsForConnection(@PathParam("connectionId") String connection) { + public ResponseEntity getMappingsForConnection(@PathParam("connectionId") String connection) { return Response.ok(mappingService. getAllMappingsForConnection(connectionRepo .getUniqueResultByColumn("id", connection))) @@ -57,7 +58,7 @@ public Response getMappingsForConnection(@PathParam("connectionId") String conne @GET @Produces("application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) - public Response getAllMappings() { + public ResponseEntity getAllMappings() { return Response.ok(mappingService.getAllMappings()).build(); } @@ -67,7 +68,7 @@ public Response getAllMappings() { @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed({SUPER_ADMIN}) @Path("/") - public Response addMapping( + public ResponseEntityEntity addMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping in JSON format") List mappings) { return mappingService.addMappings(mappings); @@ -78,7 +79,7 @@ public Response addMapping( @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed({SUPER_ADMIN}) @Path("/") - public Response updateMapping( + public ResponseEntity updateMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping with fields to be updated in JSON format") List mappings) { return updateEntity(mappings, mappingRepo); @@ -89,7 +90,7 @@ public Response updateMapping( @DELETE @RolesAllowed({SUPER_ADMIN}) @Path("/{mappingId}") - public Response removeById( + public ResponseEntity removeById( @ApiParam(required = true, value = "A valid UserMetadataMapping Id") @PathParam("mappingId") final String mappingId) { return removeEntityById(mappingId, mappingRepo); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java index 5a6245a26..31fb8584f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java @@ -1,49 +1,49 @@ -package edu.harvard.hms.dbmi.avillach.auth.security; - -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; - -import javax.ws.rs.core.SecurityContext; -import java.security.Principal; - -/** - *

Implements the SecurityContext interface for JWTFilter to use.

- */ -public class AuthSecurityContext implements SecurityContext { - - private User user; - private Application application; - private String scheme; - - public AuthSecurityContext(User user, String scheme) { - this.user = user; - this.scheme = scheme; - } - - public AuthSecurityContext(Application application, String scheme) { - this.application = application; - this.scheme = scheme; - } - - @Override - public Principal getUserPrincipal() { - return this.user == null ? this.application : this.user; - } - - @Override - public boolean isUserInRole(String role) { - if (user.getRoles() != null) - return user.getPrivilegeNameSet().contains(role); - return false; - } - - @Override - public boolean isSecure() { - return "https".equals(this.scheme); - } - - @Override - public String getAuthenticationScheme() { - return SecurityContext.DIGEST_AUTH; - } -} +//package edu.harvard.hms.dbmi.avillach.auth.security; +// +//import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; +//import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +// +//import javax.ws.rs.core.SecurityContext; +//import java.security.Principal; +// +///** +// *

Implements the SecurityContext interface for JWTFilter to use.

+// */ +//public class AuthSecurityContext implements SecurityContext { +// +// private User user; +// private Application application; +// private String scheme; +// +// public AuthSecurityContext(User user, String scheme) { +// this.user = user; +// this.scheme = scheme; +// } +// +// public AuthSecurityContext(Application application, String scheme) { +// this.application = application; +// this.scheme = scheme; +// } +// +// @Override +// public Principal getUserPrincipal() { +// return this.user == null ? this.application : this.user; +// } +// +// @Override +// public boolean isUserInRole(String role) { +// if (user.getRoles() != null) +// return user.getPrivilegeNameSet().contains(role); +// return false; +// } +// +// @Override +// public boolean isSecure() { +// return "https".equals(this.scheme); +// } +// +// @Override +// public String getAuthenticationScheme() { +// return SecurityContext.DIGEST_AUTH; +// } +//} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/JWTFilter.java deleted file mode 100755 index 89e99ce6d..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/JWTFilter.java +++ /dev/null @@ -1,260 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.security; - -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.TOSService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import javax.ws.rs.ext.Provider; -import java.io.IOException; -import java.util.Arrays; -import java.util.Set; -import java.util.UUID; - -/** - * The main gate for PSAMA that filters all incoming requests against PSAMA. - *

Design Logic

- *
    - *
  • All incoming requests pass through this filter.
  • - *
  • To pass this filter, the incoming request needs a valid bearer token in its HTTP Authorization Header - * to represent a valid identity behind the token.
  • - *
  • In some cases, the incoming request doesn't need to hold a token. For example, when the request is to the authentication - * endpoint, swagger.json, or swagger.html.
  • - *
- */ -@Provider -public class JWTFilter implements ContainerRequestFilter { - - @Context - private UriInfo uriInfo; - - Logger logger = LoggerFactory.getLogger(JWTFilter.class); - - @Context - ResourceInfo resourceInfo; - - @Inject - UserRepository userRepo; - - @Inject - ApplicationRepository applicationRepo; - - @Inject - TOSService tosService; - - @Override - public void filter(ContainerRequestContext requestContext) throws IOException { - logger.debug("starting..."); - logger.debug("For path:{} and requested URI: {}", uriInfo.getPath(), uriInfo.getRequestUri()); - - /** - * skip the filter in certain cases - */ - if (uriInfo.getPath().endsWith("authentication") - || uriInfo.getPath().endsWith("/swagger.yaml") - || uriInfo.getPath().endsWith("/swagger.json")) { - return; - } - - try { - String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); - if (authorizationHeader == null || authorizationHeader.isEmpty()) { - throw new NotAuthorizedException("No authorization header found."); - } - String token = authorizationHeader.substring(6).trim(); - logger.debug(" token: {}", token); - - String userForLogging = null; - Jws jws = parseToken(token); - String userId = jws.getBody().get(JAXRSConfiguration.userIdClaim, String.class); - - if(userId.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { - // For profile information, we do indeed allow long term token - // to be a valid token. - if (uriInfo.getPath().startsWith("/user/me")) { - // Get the subject claim, remove the LONG_TERM_TOKEN_PREFIX, and use that String value to - // look up the existing user. - String realClaimsSubject = jws.getBody().getSubject().substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length()+1); - setSecurityContextForUser(requestContext, realClaimsSubject); - } else { - logger.error("the long term token with subject, {}, cannot access to PSAMA.", userId); - throw new NotAuthorizedException("Long term tokens cannot be used to access to PSAMA."); - } - /** - * authenticate if a long term token is presented. - * PSAMA activities should not be able to execute under this token. - * Normal admin should access psama through psamaui, the token will be expired - * in a certain time. - * If super admin want to access the psama through APIs, the token should - * be grabbed from psamaui as well to prevent the token leakage. - */ - - - } else if(userId.startsWith(AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX)) { - logger.debug(" userId starts with PSAMA_APPLICATION_TOKEN_PREFIX"); - - /** - * authenticate as Application, we might need to extract this blob out to an separate function - */ - - if( ! uriInfo.getPath().endsWith("token/inspect")) { - logger.error(userId + " attempted to perform request " + uriInfo.getPath() + " token may be compromised."); - throw new NotAuthorizedException("User is deactivated"); - } - Application authenticatedApplication = applicationRepo.getById(UUID.fromString(userId.split("\\|")[1])); - if (authenticatedApplication == null){ - logger.error("Cannot find an application by userId: " + userId); - throw new NotAuthorizedException("Your token doesn't contain valid identical information, please contact admin."); - } - - if (!authenticatedApplication.getToken().equals(token)) { - logger.error("filter() incoming application token - " + token + - " - is not the same as record, might because the token has been refreshed. Subject: " + userId); - throw new NotAuthorizedException("Your token has been inactivated, please contact admin to grab you the latest one."); - } - - requestContext.setSecurityContext(new AuthSecurityContext(authenticatedApplication, - uriInfo.getRequestUri().getScheme())); - } else { - logger.debug(" userId is not longterm and not psamaapp either."); - /** - * authenticate as User - */ - setSecurityContextForUser(requestContext, jws.getBody().getSubject()); - - } - - } catch (NotAuthorizedException e) { - // the detail of this exception should be logged right before the exception thrown out - // logger.error("User - " + userForLogging + " - is not authorized. " + e.getChallenges()); - // we should show different response based on role - requestContext.abortWith(PICSUREResponse.unauthorizedError("User is not authorized. " + e.getChallenges())); - } catch (ApplicationException e){ - // we should show different response based on role - e.printStackTrace(); - requestContext.abortWith(PICSUREResponse.applicationError(e.getContent())); - } catch (Exception e) { - e.printStackTrace(); - requestContext.abortWith(PICSUREResponse.applicationError(e.getMessage())); - } - logger.debug("finished."); - } - - /** - * Validate the user information, stored in the token, that was passed in the HTTPRequest header. - * - * @param requestContext The request that has the information - * @param token The token to be parsed - * @param jws The claims stored in the token - * @return - */ - private void setSecurityContextForUser(ContainerRequestContext requestContext, String claimsSubject) throws NotAuthorizedException { - logger.debug("starting..."); - String userForLogging; - - // Find the user when checking the claims - User authenticatedUser = userRepo.findBySubject(claimsSubject); - - if (authenticatedUser == null) { - logger.error("Cannot validate user claims, based on information stored in the JWT token."); - throw new NotAuthorizedException("Cannot validate user claims, based on information stored in the JWT token."); - } - // Check whether user is active - if (!authenticatedUser.isActive()) { - logger.warn("User with ID: " + authenticatedUser.getUuid() + " is deactivated."); - throw new NotAuthorizedException("User is deactivated"); - } - - /** - * This TOSService code will hit to the database to retrieve a user once again - */ - if (!uriInfo.getPath().contains("/tos")){ - if (JAXRSConfiguration.tosEnabled.startsWith("true") && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())){ - //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate - requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).entity("User must accept terms of service").build()); - return; - } - } - - // currently only user id will be logged, in the future, it might contain roles and other information, - // like xxxuser|roles|otherInfo - userForLogging = authenticatedUser.getSubject(); - // check authorization of the authenticated user - checkRoles(authenticatedUser, resourceInfo - .getResourceMethod().isAnnotationPresent(RolesAllowed.class) - ? resourceInfo.getResourceMethod().getAnnotation(RolesAllowed.class).value() - : new String[]{}); - - logger.info("User - {} - has just passed all the authentication and authorization layers.", userForLogging); - - requestContext.setSecurityContext(new AuthSecurityContext(authenticatedUser, - uriInfo.getRequestUri().getScheme())); - } - - /** - * check if user contains the input list of roles - * - * @param authenticatedUser - * @param rolesAllowed - * @return - */ - private boolean checkRoles(User authenticatedUser, String[] rolesAllowed) throws NotAuthorizedException{ - - String logMsg = "The roles of the user - id: " + authenticatedUser.getSubject() + " - "; //doesn't match the required restrictions"; - boolean b = true; - if (rolesAllowed.length < 1) { - return true; - } - - - if (authenticatedUser.getRoles() == null) { - logger.error(logMsg + "is null."); - throw new NotAuthorizedException("user doesn't have an assigned role."); - } - - Set privilegeNameSet = authenticatedUser.getPrivilegeNameSet(); - if (privilegeNameSet.isEmpty()){ - logger.error(logMsg + "doesn't have privileges associated."); - throw new NotAuthorizedException("user doesn't have roles or privileges, please contact admin."); - } - - boolean isAuthorized = false; - for (String role : rolesAllowed) { - if(privilegeNameSet.contains(role.trim())) { - isAuthorized = true; - break; - } - } - if(!isAuthorized) { - logger.error(logMsg + "doesn't match the required role/privilege restrictions, privileges from user: " - + authenticatedUser.getPrivilegeString() + ", priviliges required: " + Arrays.toString(rolesAllowed)); - throw new NotAuthorizedException("doesn't match the required role restrictions."); - } - return b; - } - - private Jws parseToken(String token) { - Jws jws = AuthUtils.parseToken(JAXRSConfiguration.clientSecret, token); - return jws; - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java new file mode 100644 index 000000000..110755bb5 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java @@ -0,0 +1,39 @@ +package edu.harvard.hms.dbmi.avillach.auth.security; + +import edu.harvard.hms.dbmi.avillach.auth.filter.JWTFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Configuration +public class SecurityConfig { + + private final JWTFilter jwtFilter; + + @Autowired + public SecurityConfig(JWTFilter jwtFilter) { + this.jwtFilter = jwtFilter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests((authz) -> + authz.anyRequest().authenticated() + ) + .addFilter() + .httpBasic(withDefaults()); + + return http.build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return (web) -> web.ignoring().requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json"); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/OauthUserMatchingService.java deleted file mode 100644 index a56946fc8..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/OauthUserMatchingService.java +++ /dev/null @@ -1,134 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; - -import java.util.Arrays; -import java.util.List; - -import javax.inject.Inject; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.jayway.jsonpath.*; - -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; - -/** - *

Matches users created by admins with user profiles created by a 3rd party Oauth provider.

- */ -public class OauthUserMatchingService { - - @Inject - UserRepository userRepo; - - @Inject - UserService userService; - - @Inject - UserMetadataMappingService mappingService; - - @Inject - ConnectionRepository connectionRepo; - - private Logger logger = LoggerFactory.getLogger(OauthUserMatchingService.class); - - private ObjectMapper mapper = new ObjectMapper(); - - /*public String mockAuthAPIUserInfo(String accessToken) { - Map map = Map.of("ldap-connector-access-token", - "{ \"name\": \"Guy,Some\", \"family_name\": \"Guy\", \"given_name\": \"Some\", \"nickname\": \"CH000000000\", \"groups\": [], \"emails\": [ \"foo@childrens.harvard.edu\" ], \"dn\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"distinguishedName\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"organizationUnits\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"email\": \"foo@childrens.harvard.edu\", \"updated_at\": \"2018-10-04T18:28:23.371Z\", \"picture\": \"https://s.gravatar.com/avatar/blablablablablablablablablablablabla?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fsp.png\", \"user_id\": \"ad|ldap-connector|blablablablablablablablablablablabla\", \"identities\": [ { \"user_id\": \"ldap-connector|blablablablablablablablablablablabla\", \"provider\": \"ad\", \"connection\": \"ldap-connector\", \"isSocial\": false } ], \"created_at\": \"2018-01-26T14:06:50.413Z\", \"username\": \"CH0000000\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.32\", \"last_login\": \"2018-10-04T18:28:23.091Z\", \"logins_count\": 399, \"blocked_for\": [], \"guardian_authenticators\": []}", - "github-access-token", " { \"email\": \"blablabla@gmail.com\", \"name\": \"Some Girl\", \"picture\": \"https://avatars3.githubusercontent.com/u/0000000000?v=4\", \"nickname\": \"blablabla\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/blablabla\", \"html_url\": \"https://github.com/blablabla\", \"followers_url\": \"https://api.github.com/users/blablabla/followers\", \"following_url\": \"https://api.github.com/users/blablabla/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/blablabla/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/blablabla/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/blablabla/subscriptions\", \"organizations_url\": \"https://api.github.com/users/blablabla/orgs\", \"repos_url\": \"https://api.github.com/users/blablabla/repos\", \"events_url\": \"https://api.github.com/users/blablabla/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/blablabla/received_events\", \"type\": \"User\", \"site_admin\": false, \"location\": \"Nowhere, USA\", \"hireable\": true, \"public_repos\": 8, \"public_gists\": 0, \"followers\": 3, \"following\": 1, \"updated_at\": \"2018-09-20T18:47:43.703Z\", \"emails\": [ { \"email\": \"blablabla@gmail.com\", \"primary\": true, \"verified\": true, \"visibility\": \"public\" }, { \"email\": \"blablabla@users.noreply.github.com\", \"primary\": false, \"verified\": true, \"visibility\": null } ], \"email_verified\": true, \"user_id\": \"github|0000000\", \"identities\": [ { \"provider\": \"github\", \"user_id\": 000000000, \"connection\": \"github\", \"isSocial\": true } ], \"created_at\": \"2016-10-22T22:38:20.437Z\", \"blog\": \"\", \"node_id\": \"blablabla=\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.198\", \"last_login\": \"2018-09-20T18:47:43.491Z\", \"logins_count\": 71, \"blocked_for\": [], \"guardian_authenticators\": []}", - "nih-gov-prod-access-token"," { \"email\": \"NOBODY\", \"sessionIndex\": \"blablablabla\", \"UserPrincipalName\": \"\", \"Mail\": \"\", \"FirstName\": \"\", \"LastName\": \"\", \"MiddleName\": \"\", \"NEDID\": \"\", \"nameIdAttributes\": { \"value\": \"NOBODY\", \"Format\": \"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\" }, \"authenticationmethod\": \"urn:oasis:names:tc:SAML:2.0:ac:classes:Password\", \"issuer\": \"https://auth.nih.gov/IDP\", \"updated_at\": \"2018-07-23T19:32:51.505Z\", \"name\": \"\", \"picture\": \"https://cdn.auth0.com/avatars/default.png\", \"user_id\": \"samlp|NOBODY\", \"nickname\": \"\", \"identities\": [ { \"user_id\": \"NOBODY\", \"provider\": \"samlp\", \"connection\": \"nih-gov-prod\", \"isSocial\": false } ], \"created_at\": \"2018-04-02T13:10:25.654Z\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.195\", \"last_login\": \"2018-07-23T19:32:51.254Z\", \"logins_count\": 12, \"blocked_for\": [], \"guardian_authenticators\": []}", - "no-mapping-connection-token"," { \"email\": \"foo@bar.com\", \"UserName\": \"foooo\", \"FirstName\": \"foo\", \"LastName\": \"oooo\",\"user_id\": \"samlp|fooBar\", \"identities\": [ { \"user_id\": \"fooBar\", \"provider\": \"samlp\", \"connection\": \"no-mapping-connection\", \"isSocial\": false } ]}", - "invalid-path-token"," { \"email\": \"bar@foo.com\", \"UserName\": \"bahh\", \"user_id\": \"samlp|barFoo\", \"identities\": [ { \"user_id\": \"barFoo\", \"provider\": \"samlp\", \"connection\": \"invalid-path\", \"isSocial\": true } ]}", - "no-user-token"," { \"email\": \"no@user.com\", \"UserName\": \"nooooooo\", \"user_id\": \"samlp|noUser\", \"identities\": [ { \"user_id\": \"noUser\", \"provider\": \"samlp\", \"connection\": \"no-user-connection\", \"isSocial\": false } ]}" - ); - return map.get(accessToken); - }*/ - - - /** - * Retrieve a user profile by access_token and match it to a pre-created user in the database using - * pre-configured matching rules. - * - * @param userInfo UserInfo returned from auth0 - * @return The user that was matched or null if no match was possible. - */ - public User matchTokenToUser(JsonNode userInfo) { - // This retrieves a map of UserInfo as JSON. - try { - String userInfoString = mapper.writeValueAsString(userInfo); - logger.info("Attempting to find match for user with info: " + userInfo); - - //Parse this once so it doesn't get re-parsed every time we read from it - Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.ALWAYS_RETURN_LIST); - Object parsedInfo = conf.jsonProvider().parse(userInfoString); - //Return lists or null so that we don't have to worry about whether it's a single object or an array, or catch errors - List connections = JsonPath.using(conf).parse(parsedInfo).read("$.identities[0].connection"); - String connectionId = connections.get(0); - - Connection connection = connectionRepo.getUniqueResultByColumn("id", connectionId); - - List mappings = mappingService.getAllMappingsForConnection(connection); - - if (mappings == null || mappings.isEmpty()) { - //We don't have any mappings for this connection yet - logger.warn("Unable to find user metadata mappings for connectionId " + connection); - return null; - } - - //We only care about unmatched users - List users = userRepo.listUnmatchedByConnectionId(connection); - if (users == null || users.isEmpty()) { - logger.info("No unmatched users exist with connectionId " + connection); - return null; - } - for (UserMetadataMapping umm : mappings) { - List auth0values = JsonPath.using(conf).parse(parsedInfo).read(umm.getAuth0MetadataJsonPath()); - if (auth0values == null || auth0values.isEmpty()) { - //Well, nothing found, let's move on. - logger.info("Fetched data has no value at " + umm.getAuth0MetadataJsonPath()); - break; - } - String auth0value = auth0values.get(0); - for (User u : users) { - List values = null; - try{ - values = JsonPath.using(conf).parse(u.getGeneralMetadata()).read(umm.getGeneralMetadataJsonPath()); - } catch (JsonPathException e) { - logger.warn("User " + u.getUuid() + " has invalid general metadata: " + u.getGeneralMetadata()); - continue; - } - if (values == null || values.isEmpty()) { - logger.warn("User " + u.getUuid() + " has no value at " + umm.getGeneralMetadataJsonPath()); - continue; - } - String generalValue = values.get(0); - if (auth0value.equalsIgnoreCase(generalValue)) { - //Match found!! - String userId = JsonPath.read(parsedInfo, "$.user_id"); - logger.info("Matching user with user_id " + userId); - u.setAuth0metadata(userInfoString); - u.setMatched(true); - u.setSubject(userId); - userService.updateEntity(Arrays.asList(u), userRepo); - return u; - } - } - } - } catch (JsonProcessingException e ){ - logger.error("Unable to read UserInfo"); - } - //No user found - logger.info("No matching user found"); - return null; - } - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java new file mode 100644 index 000000000..02253b5f4 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java @@ -0,0 +1,68 @@ +package edu.harvard.hms.dbmi.avillach.auth.service; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.List; +import java.util.UUID; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; + +@Service +public class PrivilegeService extends BaseEntityService { + + private final static Logger logger = LoggerFactory.getLogger(PrivilegeService.class.getName()); + + private final PrivilegeRepository privilegeRepository; + + @Autowired + protected PrivilegeService(Class type, PrivilegeRepository privilegeRepository) { + super(type); + this.privilegeRepository = privilegeRepository; + } + + @Transactional + public ResponseEntity deletePrivilegeByPrivilegeId(String privilegeId) { + Privilege privilege = this.privilegeRepository.getById(UUID.fromString(privilegeId)); + + // Get security context with spring security context + SecurityContext securityContext = SecurityContextHolder.getContext(); + // Get the principal name from the security context + String principalName = securityContext.getAuthentication().getName(); + + if (ADMIN.equals(privilege.getName())) { + logger.info("User: " + principalName + + ", is trying to remove the system admin privilege: " + ADMIN); + return PICSUREResponse.protocolError("System Admin privilege cannot be removed - uuid: " + privilege.getUuid().toString() + + ", name: " + privilege.getName()); + } + + return removeEntityById(privilegeId, this.privilegeRepository); + } + + public ResponseEntity updateEntity(List privileges) { + return updateEntity(privileges, this.privilegeRepository); + } + + public ResponseEntity addEntity(List privileges) { + return addEntity(privileges, this.privilegeRepository); + } + + public ResponseEntity getEntityAll() { + return getEntityAll(this.privilegeRepository); + } + + public ResponseEntity getEntityById(String privilegeId) { + return getEntityById(privilegeId, this.privilegeRepository); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/UserMetadataMappingService.java deleted file mode 100644 index 224c38737..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/UserMetadataMappingService.java +++ /dev/null @@ -1,57 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; - -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserMetadataMappingRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.ws.rs.core.Response; -import java.util.List; - -/** - *

Provides business logic for UserMetadataMapping endpoint.

- */ -public class UserMetadataMappingService extends BaseEntityService{ - - Logger logger = LoggerFactory.getLogger(UserMetadataMappingService.class); - - @Inject - UserMetadataMappingRepository userMetadataMappingRepo; - - @Inject - ConnectionRepository connectionRepo; - - public UserMetadataMappingService() { - super(UserMetadataMapping.class); - } - - public List getAllMappingsForConnection(Connection connection) { - return userMetadataMappingRepo.findByConnection(connection); - } - - public Response addMappings(List mappings){ - String errorMessage = "The following connectionIds do not exist:\n"; - boolean error = false; - for (UserMetadataMapping umm : mappings){ - Connection c = connectionRepo.findConnectionById(umm.getConnection().getId()); - if (c == null){ - error = true; - errorMessage += umm.getConnection().getId() + "\n"; - } else { - umm.setConnection(c); - } - } - if (error){ - return Response.ok(errorMessage).build(); - } - return addEntity(mappings, userMetadataMappingRepo); - } - - public List getAllMappings() { - return userMetadataMappingRepo.list(); - } - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java deleted file mode 100644 index 7404ee118..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java +++ /dev/null @@ -1,158 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.auth; - -import java.util.*; - -import javax.inject.Inject; -import javax.mail.MessagingException; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.http.Header; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.message.BasicHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; - -import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.exception.ProtocolException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; -import edu.harvard.hms.dbmi.avillach.auth.service.MailService; -import edu.harvard.hms.dbmi.avillach.auth.service.OauthUserMatchingService; -import edu.harvard.hms.dbmi.avillach.auth.service.TOSService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; - -/** - * This class provides authentication functionality. This implements an authenticationService interface - * in the future to support different modes of authentication. - * - *

Thoughts of design

- * The main purpose of this class is returns a token that includes information of the roles of users. - */ -public class AuthenticationService { - - private Logger logger = LoggerFactory.getLogger(AuthenticationService.class); - - @Inject - OauthUserMatchingService matchingService; - - @Inject - UserRepository userRepository; - - @Inject - RoleRepository roleRepo; - - @Inject - TOSService tosService; - - @Inject - MailService mailService; - - @Inject - UserService userService; - - @Inject - AuthUtils authUtil; - - private static final int AUTH_RETRY_LIMIT = 3; - - public Response getToken(Map authRequest){ - String accessToken = authRequest.get("access_token"); - String redirectURI = authRequest.get("redirectURI"); - - if (accessToken == null || redirectURI == null || accessToken.isEmpty() || redirectURI.isEmpty()) - throw new ProtocolException("Missing accessToken or redirectURI in request body."); - - JsonNode userInfo = retrieveUserInfo(accessToken); - JsonNode userIdNode = userInfo.get("user_id"); - if (userIdNode == null){ - logger.error("getToken() cannot find user_id by retrieveUserInfo(), return json response: " + userInfo.toString()); - throw new ApplicationException("cannot get sufficient user information. Please contact admin."); - } - String userId = userIdNode.asText(); - - logger.info("Successfully retrieved userId, " + userId + - ", from the provided code and redirectURI"); - - String connectionId; - try { - connectionId = userInfo.get("identities").get(0).get("connection").asText(); - } catch (Exception e){ - logger.error("getToken() cannot find connection_id by retrieveUserInfo(), return json response: " + userInfo.toString()); - throw new ApplicationException("cannot get sufficient user information. Please contact admin."); - } - - //Do we have this user already? - User user = userRepository.findBySubjectAndConnection(userId, connectionId); - if (user == null){ - //Try to match - user = matchingService.matchTokenToUser(userInfo); - if (user == null){ - if (JAXRSConfiguration.deniedEmailEnabled.startsWith("true")) { - try { - mailService.sendDeniedAccessEmail(userInfo); - } catch (MessagingException e) { - logger.warn("Failed to send user access denied email: ", e); - } - } - throw new NotAuthorizedException("No user matching user_id " + userId + " present in database"); - } - } - - HashMap claims = new HashMap(); - claims.put("sub", userId); - claims.put("name", user.getName()); - claims.put("email", user.getEmail()); - HashMap responseMap = authUtil.getUserProfileResponse(claims); - - logger.info("LOGIN SUCCESS ___ " + user.getEmail() + ":" + user.getUuid().toString() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); - - return PICSUREResponse.success(responseMap); - } - - private JsonNode retrieveUserInfo(String accessToken){ - String auth0UserInfoURI = JAXRSConfiguration.auth0host + "/userinfo"; - Header[] headers = { - new BasicHeader("Content-Type", MediaType.APPLICATION_JSON), - new BasicHeader("Authorization", "Bearer " + accessToken) - }; - JsonNode auth0Response = null; - RequestConfig requestConfig = createRequestConfigWithCustomTimeout(2000); - - for(int i = 1; i <= AUTH_RETRY_LIMIT && auth0Response == null; i++) { - try { - auth0Response = HttpClientUtil.simpleGetWithConfig( - auth0UserInfoURI, - JAXRSConfiguration.client, - JAXRSConfiguration.objectMapper, - requestConfig, - headers - ); - } catch (ApplicationException e) { - if(i < AUTH_RETRY_LIMIT ) { - logger.warn("Failed to authenticate. Retrying"); - } else { - logger.error("Failed to authenticate. Giving up!"); - throw e; - } - } - } - return auth0Response; - } - - private RequestConfig createRequestConfigWithCustomTimeout(int timeoutMs) { - return RequestConfig.custom() - .setConnectionRequestTimeout(timeoutMs) - .setConnectTimeout(timeoutMs) - .setSocketTimeout(timeoutMs) - .build(); - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java.orig b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java.orig deleted file mode 100644 index 045a774c8..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthenticationService.java.orig +++ /dev/null @@ -1,143 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.auth; - -import com.fasterxml.jackson.databind.JsonNode; -import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.exception.ProtocolException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -<<<<<<< HEAD -import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; -import edu.harvard.hms.dbmi.avillach.auth.service.Auth0UserMatchingService; -import edu.harvard.hms.dbmi.avillach.auth.service.MailService; -import edu.harvard.hms.dbmi.avillach.auth.service.TermsOfServiceService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; -======= -import edu.harvard.hms.dbmi.avillach.auth.service.OauthUserMatchingService; -import edu.harvard.hms.dbmi.avillach.auth.service.MailService; -import edu.harvard.hms.dbmi.avillach.auth.service.TOSService; -import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; ->>>>>>> master -import org.apache.http.Header; -import org.apache.http.message.BasicHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * This class is designed to provide authentication functionality. This implements an authenticationService interface - * in the future to support different authentication mode. - * - *

Thoughts of design

- * The main purpose of this class is returns a token that includes information of the roles of users. - */ -public class AuthenticationService { - private Logger logger = LoggerFactory.getLogger(AuthenticationService.class); - - @Inject - OauthUserMatchingService matchingService; - - @Inject - UserRepository userRepository; - - @Inject -<<<<<<< HEAD - RoleRepository roleRepo; - - @Inject - TermsOfServiceService tosService; -======= - TOSService tosService; ->>>>>>> master - - @Inject - MailService mailService; - - @Inject - UserService userService; - - @Inject - AuthUtils authUtil; - - public Response getToken(Map authRequest){ - String accessToken = authRequest.get("access_token"); - String redirectURI = authRequest.get("redirectURI"); - - if (accessToken == null || redirectURI == null || accessToken.isEmpty() || redirectURI.isEmpty()) - throw new ProtocolException("Missing accessToken or redirectURI in request body."); - - JsonNode userInfo = retrieveUserInfo(accessToken); - JsonNode userIdNode = userInfo.get("user_id"); - if (userIdNode == null){ - logger.error("getToken() cannot find user_id by retrieveUserInfo(), return json response: " + userInfo.toString()); - throw new ApplicationException("cannot get sufficient user information. Please contact admin."); - } - String userId = userIdNode.asText(); - - logger.info("Successfully retrieved userId, " + userId + - ", from the provided code and redirectURI"); - - String connectionId; - try { - connectionId = userInfo.get("identities").get(0).get("connection").asText(); - } catch (Exception e){ - logger.error("getToken() cannot find connection_id by retrieveUserInfo(), return json response: " + userInfo.toString()); - throw new ApplicationException("cannot get sufficient user information. Please contact admin."); - } - - //Do we have this user already? - User user = userRepository.findBySubjectAndConnection(userId, connectionId); - if (user == null){ - //Try to match - user = matchingService.matchTokenToUser(userInfo); - if (user == null){ - if (JAXRSConfiguration.deniedEmailEnabled.startsWith("true")) { - mailService.sendDeniedAccessEmail(userInfo); - } - throw new NotAuthorizedException("No user matching user_id " + userId + " present in database"); - } - } - - HashMap claims = new HashMap(); - claims.put("sub", userId); - claims.put("name", user.getName()); - claims.put("email", user.getEmail()); - HashMap responseMap = authUtil.getUserProfileResponse(claims); - - return PICSUREResponse.success(responseMap); - } - - private JsonNode retrieveUserInfo(String accessToken){ - String auth0UserInfoURI = JAXRSConfiguration.auth0host + "/userinfo"; - List
headers = new ArrayList<>(); - headers.add(new BasicHeader("Content-Type", MediaType.APPLICATION_JSON)); - headers.add(new BasicHeader("Authorization", "Bearer " + accessToken)); - return HttpClientUtil.simpleGet(auth0UserInfoURI, - JAXRSConfiguration.client, - JAXRSConfiguration.objectMapper, - headers.toArray(new Header[headers.size()])); - } - - private Map generateClaims(JsonNode userInfo, String... fields){ - Map claims = new HashMap<>(); - - for (String field : fields) { - JsonNode node = userInfo.get(field); - if (node != null) - claims.put(field, node.asText()); - } - - return claims; - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/package-info.java deleted file mode 100644 index ee52f378f..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package provides services for authentication and authorization. - */ -package edu.harvard.hms.dbmi.avillach.auth.service.auth; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java new file mode 100644 index 000000000..56deb6d0c --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -0,0 +1,142 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.hibernate.PropertyNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.*; + +@Service +public class ApplicationService extends BaseEntityService { + + private final static Logger logger = LoggerFactory.getLogger(ApplicationService.class); + private final ApplicationRepository applicationRepo; + private final PrivilegeRepository privilegeRepo; + + @Value("${application.client.secret}") + private String CLIENT_SECRET; + + @Autowired + protected ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo) { + super(Application.class); + this.applicationRepo = applicationRepo; + this.privilegeRepo = privilegeRepo; + } + + /** + * Retrieves an entity by its ID. + * + * @param applicationId the ID of the entity to retrieve + * @return a ResponseEntity representing the result of the operation + */ + public ResponseEntity getEntityById(String applicationId) { + return getEntityById(applicationId, applicationRepo); + } + + public ResponseEntity getEntityAll() { + return getEntityAll(applicationRepo); + } + + @Transactional + public ResponseEntity addNewApplications(List applications) { + checkAssociation(applications); + List appEntities = addOrUpdate(applications, true, applicationRepo); + for (Application application : appEntities) { + try { + application.setToken( + generateApplicationToken(application) + ); + } catch (Exception e) { + logger.error("", e); + } + } + + return updateEntity(appEntities, applicationRepo); + } + + @Transactional + public ResponseEntity deleteApplicationById(String applicationId) { + Application application = applicationRepo.getById(UUID.fromString(applicationId)); + if (application == null) { + logger.error("deleteApplicationById() cannot find the application by applicationId: " + applicationId); + throw new IllegalArgumentException("Cannot find application by the given applicationId: " + applicationId); + } + + return removeEntityById(applicationId, applicationRepo); + } + + public ResponseEntity updateApplications(List applications) { + checkAssociation(applications); + return updateEntity(applications, applicationRepo); + } + + public ResponseEntity refreshApplicationToken(String applicationId) { + Application application = applicationRepo.getById(UUID.fromString(applicationId)); + if (application == null) { + logger.error("refreshApplicationToken() cannot find the application by applicationId: " + applicationId); + throw new IllegalArgumentException("Cannot find application by the given applicationId: " + applicationId); + } + + String newApplicationToken = generateApplicationToken(application); + try { + application.setToken( + newApplicationToken + ); + + applicationRepo.merge(application); + } catch (Exception e) { + logger.error("", e); + } + + return PICSUREResponse.success(Map.of("token", newApplicationToken)); + } + + private void checkAssociation(List applications) { //TODO: We need to refactor this into a service class + for (Application application : applications) { + if (application.getPrivileges() != null) { + Set privileges = new HashSet<>(); + application.getPrivileges().forEach(p -> { + Privilege privilege = privilegeRepo.getById(p.getUuid()); + if (privilege != null) { + privilege.setApplication(application); + privileges.add(privilege); + } else { + logger.error("Didn't find privilege by uuid: " + p.getUuid()); + } + }); + application.setPrivileges(privileges); + + } + } + } + + public String generateApplicationToken(Application application) { // TODO: Refactor this into a new service class + if (application == null || application.getUuid() == null) { + logger.error("generateApplicationToken() application is null or uuid is missing to generate the application token"); + throw new PropertyNotFoundException("Cannot generate application token, please contact admin"); + } + + return JWTUtil.createJwtToken( + this.CLIENT_SECRET, null, null, + new HashMap<>( + Map.of( + "user_id", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getName() + ) + ), + AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getUuid().toString(), 365L * 1000 * 60 * 60 * 24); + } + +} + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java new file mode 100644 index 000000000..f2a2dbebc --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -0,0 +1,150 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import java.util.*; + +import javax.mail.MessagingException; + +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import org.apache.http.Header; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.message.BasicHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +import edu.harvard.dbmi.avillach.util.HttpClientUtil; +import edu.harvard.dbmi.avillach.util.exception.ApplicationException; +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +/** + * This class provides authentication functionality. This implements an authenticationService interface + * in the future to support different modes of authentication. + * + *

Thoughts of design

+ * The main purpose of this class is returns a token that includes information of the roles of users. + */ + +@Service +public class AuthenticationService { + + private final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); + + private final OauthUserMatchingService matchingService; + + private final UserRepository userRepository; + + private final MailService mailService; + + private final UserService userService; + private static final int AUTH_RETRY_LIMIT = 3; + + @Autowired + public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, MailService mailService, UserService userService) { + this.matchingService = matchingService; + this.userRepository = userRepository; + this.mailService = mailService; + this.userService = userService; + } + + public ResponseEntity getToken(Map authRequest) { + String accessToken = authRequest.get("access_token"); + String redirectURI = authRequest.get("redirectURI"); + + if (accessToken == null || redirectURI == null || accessToken.isEmpty() || redirectURI.isEmpty()) { + throw new IllegalArgumentException("Missing accessToken or redirectURI in request body."); + } + + JsonNode userInfo = retrieveUserInfo(accessToken); + JsonNode userIdNode = userInfo.get("user_id"); + if (userIdNode == null) { + logger.error("getToken() cannot find user_id by retrieveUserInfo(), return json response: " + userInfo.toString()); + throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); + } + String userId = userIdNode.asText(); + + logger.info("Successfully retrieved userId, " + userId + + ", from the provided code and redirectURI"); + + String connectionId; + try { + connectionId = userInfo.get("identities").get(0).get("connection").asText(); + } catch (Exception e) { + logger.error("getToken() cannot find connection_id by retrieveUserInfo(), return json response: " + userInfo.toString()); + throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); + } + + //Do we have this user already? + User user = userRepository.findBySubjectAndConnection(userId, connectionId); + if (user == null) { + //Try to match + user = matchingService.matchTokenToUser(userInfo); + if (user == null) { + if (JAXRSConfiguration.deniedEmailEnabled.startsWith("true")) { + try { + mailService.sendDeniedAccessEmail(userInfo); + } catch (MessagingException e) { + logger.warn("Failed to send user access denied email: ", e); + } + } + throw new NotAuthorizedException("No user matching user_id " + userId + " present in database"); + } + } + + HashMap claims = new HashMap(); + claims.put("sub", userId); + claims.put("name", user.getName()); + claims.put("email", user.getEmail()); + HashMap responseMap = userService.getUserProfileResponse(claims); + + logger.info("LOGIN SUCCESS ___ " + user.getEmail() + ":" + user.getUuid().toString() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); + + return PICSUREResponse.success(responseMap); + } + + private JsonNode retrieveUserInfo(String accessToken) { + String auth0UserInfoURI = JAXRSConfiguration.auth0host + "/userinfo"; + Header[] headers = { + new BasicHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE), + new BasicHeader("Authorization", "Bearer " + accessToken) + }; + JsonNode auth0Response = null; + RequestConfig requestConfig = createRequestConfigWithCustomTimeout(); + + for (int i = 1; i <= AUTH_RETRY_LIMIT && auth0Response == null; i++) { + try { + auth0Response = HttpClientUtil.simpleGetWithConfig( + auth0UserInfoURI, + JAXRSConfiguration.client, + JAXRSConfiguration.objectMapper, + requestConfig, + headers + ); + } catch (Exception e) { + if (i < AUTH_RETRY_LIMIT) { + logger.warn("Failed to authenticate. Retrying"); + } else { + logger.error("Failed to authenticate. Giving up!"); + throw e; + } + } + } + return auth0Response; + } + + private RequestConfig createRequestConfigWithCustomTimeout() { + int timeoutMs = 2000; // 2 seconds, default is 3 seconds + return RequestConfig.custom() + .setConnectionRequestTimeout(timeoutMs) + .setConnectTimeout(timeoutMs) + .setSocketTimeout(timeoutMs) + .build(); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthorizationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java similarity index 95% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthorizationService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java index fee36726b..683979226 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/AuthorizationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java @@ -1,15 +1,17 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.auth; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.PathNotFoundException; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.rest.TokenController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; @@ -20,7 +22,7 @@ * what endpoint they are trying to hit and the content of the request body (in HTTP POST method). *

Thoughts on design:

* The core technology used here is jsonpath. - * In the {@link edu.harvard.hms.dbmi.avillach.auth.rest.TokenService#inspectToken(Map)} class, other registered applications + * In the {@link TokenController#inspectToken(Map)} class, other registered applications * can hit the tokenIntrospection endpoint with a token they want PSAMA to introspect along * with the URL the token holder is trying to hit and what data this token holder is trying to send. After * checking if the token is valid or not, the authorization check in this class will start. @@ -33,8 +35,9 @@ *

* */ +@Service public class AuthorizationService { - private Logger logger = LoggerFactory.getLogger(AuthorizationService.class); + private final Logger logger = LoggerFactory.getLogger(AuthorizationService.class); /** * Checking based on AccessRule in Privilege @@ -61,7 +64,7 @@ public class AuthorizationService { * @param requestBody * @return * - * @see edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege + * @see Privilege * @see AccessRule */ public boolean isAuthorized(Application application , Object requestBody, User user){ @@ -76,14 +79,6 @@ public boolean isAuthorized(Application application , Object requestBody, User u return true; } - // Object parsedRequestBody = null; - // Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.ALWAYS_RETURN_LIST); - // try { - // parsedRequestBody = conf.jsonProvider().parse(JAXRSConfiguration.objectMapper.writeValueAsString(requestBody)); - // } catch (JsonProcessingException e) { - // return true; - // } - // start to process the jsonpath checking String formattedQuery = null; @@ -339,7 +334,7 @@ protected boolean evaluateAccessRule(Object parsedRequestBody, AccessRule access // 2. if getGateAnyRelation is false, all gates passed // 3. if getGateAnyRelation is true, one of the gate passed if (gates != null && !gates.isEmpty()) { - if (accessRule.getGateAnyRelation() == null || accessRule.getGateAnyRelation() == false) { + if (accessRule.getGateAnyRelation() == null || !accessRule.getGateAnyRelation()) { // All gates are AND relationship // means one fails all fail @@ -374,12 +369,12 @@ protected boolean evaluateAccessRule(Object parsedRequestBody, AccessRule access if (gatesPassed) { logger.debug("evaluateAccessRule() gates passed"); - if (extractAndCheckRule(accessRule, parsedRequestBody) == false) + if (!extractAndCheckRule(accessRule, parsedRequestBody)) return false; else { if (accessRule.getSubAccessRule() != null) { for (AccessRule subAccessRule : accessRule.getSubAccessRule()) { - if (extractAndCheckRule(subAccessRule, parsedRequestBody) == false) + if (!extractAndCheckRule(subAccessRule, parsedRequestBody)) return false; } } @@ -426,6 +421,8 @@ private boolean extractAndCheckRule(AccessRule accessRule, Object parsedRequestB return false; } + + // TODO: Look into this more // AccessRule type IS_EMPTY is very special, needs to be checked in front of any others // in type IS_EMPTY, it doens't matter if the value is null or anything if (accessRuleType == AccessRule.TypeNaming.IS_EMPTY @@ -479,6 +476,7 @@ private boolean evaluateNode(Object requestBodyValue, AccessRule accessRule){ * 12 = "\demographics\AGE\" */ + // TODO: Clean this up. We can use java 21 pattern matching to make this more readable if (requestBodyValue instanceof String){ return decisionMaker(accessRule, (String)requestBodyValue); } else if (requestBodyValue instanceof Collection) { @@ -643,7 +641,7 @@ private boolean _decisionMaker(AccessRule accessRule, String requestBodyValue, S logger.debug(requestBodyValue); logger.debug(value); - + // TODO: Clean this up. We can use java 21 pattern matching to make this more readable switch (accessRule.getType()){ case AccessRule.TypeNaming.NOT_CONTAINS: if (!requestBodyValue.contains(value)) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/BaseEntityService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java similarity index 85% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/BaseEntityService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java index d258597fb..4daaef8f4 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/BaseEntityService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java @@ -1,17 +1,15 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; import edu.harvard.dbmi.avillach.data.entity.BaseEntity; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; import javax.validation.constraints.NotNull; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; @@ -24,14 +22,14 @@ */ public abstract class BaseEntityService { - private Logger logger; + private final Logger logger; protected final Class type; - private String auditLogName; + private final String auditLogName; - @Context - SecurityContext securityContext; +// @Context +// SecurityContext securityContext; protected BaseEntityService(Class type){ this.type = type; @@ -39,7 +37,7 @@ protected BaseEntityService(Class type){ logger = LoggerFactory.getLogger(type); } - public Response getEntityById(String id, BaseRepository baseRepository){ + public ResponseEntity getEntityById(String id, BaseRepository baseRepository){ logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) + " Looking for " + type.getSimpleName() + " by ID: " + id + "..."); @@ -53,7 +51,7 @@ public Response getEntityById(String id, BaseRepository baseRepository){ return PICSUREResponse.success(t); } - public Response getEntityAll(BaseRepository baseRepository){ + public ResponseEntity getEntityAll(BaseRepository baseRepository){ logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) + " Getting all " + type.getSimpleName() + "s..."); @@ -69,7 +67,7 @@ public Response getEntityAll(BaseRepository baseRepository){ return PICSUREResponse.success(ts); } - public Response addEntity(List entities, BaseRepository baseRepository){ + public ResponseEntity addEntity(List entities, BaseRepository baseRepository){ String username = JAXRSConfiguration.getPrincipalName(securityContext); if (entities == null || entities.isEmpty()) return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + @@ -98,7 +96,7 @@ public Response addEntity(List entities, BaseRepository baseRepository){ "s are added.", addedEntities); } - public Response updateEntity(List entities, BaseRepository baseRepository){ + public ResponseEntity updateEntity(List entities, BaseRepository baseRepository){ if (entities == null || entities.isEmpty()) return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + " to be updated."); @@ -220,21 +218,6 @@ private boolean updateAllAttributes(T detachedT, BaseRepository baseRep detachedT.getClass().getMethod(setter, field.getType()) .invoke(retrievedT, value); } - /** - * This PropertyDescriptor is in java.beans package, which is the kind of - * standard way of doing getter and setter, but there is one thing - * this method is checking the return type of getter setter, if setter - * return the Class object (which is pretty useful and normal these days), - * this method will throw an IntrospectionException..., - * but! I don't think using PropertyDescriptor to look for getter setter has - * the best performance though... - */ -// PropertyDescriptor pd = new PropertyDescriptor(fieldName, detachedT.getClass()); -// Object value = pd.getReadMethod() -// .invoke(detachedT); -// if (value != null){ -// pd.getWriteMethod().invoke(retrievedT, value); -// } } } catch (IllegalArgumentException | ReflectiveOperationException ex /* | IntrospectionException ex */){ ex.printStackTrace(); @@ -246,7 +229,7 @@ private boolean updateAllAttributes(T detachedT, BaseRepository baseRep return true; } - public Response removeEntityById(String id, BaseRepository baseRepository) { + public ResponseEntity removeEntityById(String id, BaseRepository baseRepository) { String username = JAXRSConfiguration.getPrincipalName(securityContext); logger.info("User: " + username + " is trying to REMOVE an entity: " diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java new file mode 100644 index 000000000..1d666a635 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java @@ -0,0 +1,51 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ConnectionWebService extends BaseEntityService { + + private final ConnectionRepository connectionRepo; + + @Autowired + protected ConnectionWebService(Class type, ConnectionRepository connectionRepo) { + super(type); + this.connectionRepo = connectionRepo; + } + + public ResponseEntity addEntity(List connections){ + for (Connection c : connections){ + if (c.getSubPrefix() == null || c.getRequiredFields() == null || c.getLabel() == null || c.getId() == null){ + return PICSUREResponse.protocolError("Id, Label, Subprefix, and RequiredFields cannot be null"); + } + Connection conn = connectionRepo.findConnectionById(c.getId()); + if (conn != null){ + return PICSUREResponse.protocolError("Id must be unique, a connection with id " + c.getId() + " already exists in the database"); + } + } + return addEntity(connections, connectionRepo); // TODO: This should be moved to an actual service class. We shouldn't need to pass a repo to the service class + } + + public ResponseEntity getEntityById(String connectionId) { + return getEntityById(connectionId, connectionRepo); + } + + public ResponseEntity getEntityAll() { + return getEntityAll(connectionRepo); + } + + public ResponseEntity updateEntity(List connections) { + return updateEntity(connections, connectionRepo); + } + + public ResponseEntity removeEntityById(String connectionId) { + return removeEntityById(connectionId, connectionRepo); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java similarity index 89% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java index bb01bf1c3..b3ee6a06c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/auth/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java @@ -1,75 +1,62 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.auth; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; import com.fasterxml.jackson.databind.JsonNode; - import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.AccessRuleRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthUtils; - +import edu.harvard.hms.dbmi.avillach.auth.entity.*; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.*; import org.apache.http.Header; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.ws.rs.NotAuthorizedException; -import javax.ws.rs.core.Response; - -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.fence_consent_group_concept_path; -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.fence_harmonized_concept_path; -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.fence_standard_access_rules; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; import java.io.File; import java.io.IOException; import java.util.*; -public class FENCEAuthenticationService { - private Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); +import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.*; - @Inject - UserRepository userRepo; +@Service +public class FENCEAuthenticationService { + private final Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); - @Inject - RoleRepository roleRepo; + private final UserRepository userRepo; - @Inject - ConnectionRepository connectionRepo; + private final RoleRepository roleRepo; - @Inject - AccessRuleRepository accessruleRepo; + private final ConnectionRepository connectionRepo; - @Inject - UserRepository userRole; + private final AccessRuleRepository accessruleRepo; - @Inject - ApplicationRepository applicationRepo; + private final ApplicationRepository applicationRepo; - @Inject - PrivilegeRepository privilegeRepo; + private final PrivilegeRepository privilegeRepo; - @Inject - AuthUtils authUtil; + private final UserService userService; private Application picSureApp; private Connection fenceConnection; private Map fenceMapping; - @PostConstruct + @Autowired + public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRepo, ConnectionRepository connectionRepo, AccessRuleRepository accessruleRepo, ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, UserService userService) { + this.userRepo = userRepo; + this.roleRepo = roleRepo; + this.connectionRepo = connectionRepo; + this.accessruleRepo = accessruleRepo; + this.applicationRepo = applicationRepo; + this.privilegeRepo = privilegeRepo; + this.userService = userService; + } + + // TODO: Find and equivalent of @PostConstruct in spring +// @PostConstruct public void initializeFenceService() { picSureApp = applicationRepo.getUniqueResultByColumn("name", "PICSURE"); fenceConnection = connectionRepo.getUniqueResultByColumn("label", "FENCE"); @@ -128,7 +115,7 @@ private JsonNode getFENCEAccessToken(String fence_code) { } // Get access_token from FENCE, based on the provided `code` - public Response getFENCEProfile(Map authRequest){ + public ResponseEntity getFENCEProfile(Map authRequest){ logger.debug("getFENCEProfile() starting..."); String fence_code = authRequest.get("code"); @@ -205,7 +192,7 @@ public Response getFENCEProfile(Map authRequest){ claims.put("name", fence_user_profile.get("name")); claims.put("email", current_user.getEmail()); claims.put("sub", current_user.getSubject()); - HashMap responseMap = authUtil.getUserProfileResponse(claims); + HashMap responseMap = userService.getUserProfileResponse(claims); logger.debug("getFENCEProfile() UserProfile response object has been generated"); logger.debug("getFENCEToken() finished"); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java similarity index 94% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java index c1ecde29a..5bf189714 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; import java.io.FileNotFoundException; import java.io.FileReader; @@ -11,8 +11,10 @@ import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; +import edu.harvard.hms.dbmi.avillach.auth.model.AccessEmail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.JsonNode; @@ -22,11 +24,12 @@ import com.github.mustachejava.MustacheFactory; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; /** *

Service class for sending email notifications.

*/ +@Service public class MailService { private static Logger logger = LoggerFactory.getLogger(MailService.class); private static MustacheFactory mf = new DefaultMustacheFactory(); @@ -109,7 +112,7 @@ public void sendDeniedAccessEmail(JsonNode userInfo) throws AddressException, Me /** * Generate email from template and send it. - * @param template Name of the template. + * @param emailTemplate Name of the template. * @param to Recipients * @param subject Subject of the email * @param scope Object that contains attributes for template. e.g.: Map diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java new file mode 100644 index 000000000..b7fa284f1 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -0,0 +1,126 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import java.util.Arrays; +import java.util.List; + +import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.*; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + *

Matches users created by admins with user profiles created by a 3rd party Oauth provider.

+ */ +@Service +public class OauthUserMatchingService { + + private final Logger logger = LoggerFactory.getLogger(OauthUserMatchingService.class); + + private final UserRepository userRepo; + + private final UserController userController; + + private final UserMetadataMappingService mappingService; + + private final ConnectionRepository connectionRepo; + + private final ObjectMapper mapper = new ObjectMapper(); + + @Autowired + public OauthUserMatchingService(UserRepository userRepo, UserController userController, UserMetadataMappingService mappingService, ConnectionRepository connectionRepo) { + this.userRepo = userRepo; + this.userController = userController; + this.mappingService = mappingService; + this.connectionRepo = connectionRepo; + } + + /** + * Retrieve a user profile by access_token and match it to a pre-created user in the database using + * pre-configured matching rules. + * + * @param userInfo UserInfo returned from auth0 + * @return The user that was matched or null if no match was possible. + */ + public User matchTokenToUser(JsonNode userInfo) { + // This retrieves a map of UserInfo as JSON. + try { + String userInfoString = mapper.writeValueAsString(userInfo); + logger.info("Attempting to find match for user with info: " + userInfo); + + //Parse this once so it doesn't get re-parsed every time we read from it + Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.ALWAYS_RETURN_LIST); + Object parsedInfo = conf.jsonProvider().parse(userInfoString); + //Return lists or null so that we don't have to worry about whether it's a single object or an array, or catch errors + List connections = JsonPath.using(conf).parse(parsedInfo).read("$.identities[0].connection"); + String connectionId = connections.get(0); + + Connection connection = connectionRepo.getUniqueResultByColumn("id", connectionId); + + List mappings = mappingService.getAllMappingsForConnection(connection); + + if (mappings == null || mappings.isEmpty()) { + //We don't have any mappings for this connection yet + logger.warn("Unable to find user metadata mappings for connectionId " + connection); + return null; + } + + //We only care about unmatched users + List users = userRepo.listUnmatchedByConnectionId(connection); + if (users == null || users.isEmpty()) { + logger.info("No unmatched users exist with connectionId " + connection); + return null; + } + for (UserMetadataMapping umm : mappings) { + List auth0values = JsonPath.using(conf).parse(parsedInfo).read(umm.getAuth0MetadataJsonPath()); + if (auth0values == null || auth0values.isEmpty()) { + //Well, nothing found, let's move on. + logger.info("Fetched data has no value at " + umm.getAuth0MetadataJsonPath()); + break; + } + String auth0value = auth0values.get(0); + for (User u : users) { + List values = null; + try{ + values = JsonPath.using(conf).parse(u.getGeneralMetadata()).read(umm.getGeneralMetadataJsonPath()); + } catch (JsonPathException e) { + logger.warn("User " + u.getUuid() + " has invalid general metadata: " + u.getGeneralMetadata()); + continue; + } + if (values == null || values.isEmpty()) { + logger.warn("User " + u.getUuid() + " has no value at " + umm.getGeneralMetadataJsonPath()); + continue; + } + String generalValue = values.get(0); + if (auth0value.equalsIgnoreCase(generalValue)) { + //Match found!! + String userId = JsonPath.read(parsedInfo, "$.user_id"); + logger.info("Matching user with user_id " + userId); + u.setAuth0metadata(userInfoString); + u.setMatched(true); + u.setSubject(userId); + userController.updateEntity(Arrays.asList(u), userRepo); + return u; + } + } + } + } catch (JsonProcessingException e ){ + logger.error("Unable to read UserInfo"); + } + //No user found + logger.info("No matching user found"); + return null; + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java similarity index 50% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/TOSService.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 7dd111c68..13139d4e4 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -1,15 +1,18 @@ -package edu.harvard.hms.dbmi.avillach.auth.service; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.TermsOfService; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.TermsOfServiceRepository; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.repository.TermsOfServiceRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfSerivceController; +import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import javax.inject.Inject; import javax.persistence.NoResultException; import java.util.Arrays; import java.util.Date; @@ -18,22 +21,41 @@ /** *

Provides business logic for the TermsOfService endpoint.

> * - * @see edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfServiceEndpoint + * @see TermsOfSerivceController */ +@Service public class TOSService { - Logger logger = LoggerFactory.getLogger(TOSService.class); + private final static Logger logger = LoggerFactory.getLogger(TOSService.class); - @Inject - TermsOfServiceRepository termsOfServiceRepo; + @Value("${application.tos.enabled}") + private boolean isToSEnabled; - @Inject - UserRepository userRepo; + private final TermsOfServiceRepository termsOfServiceRepo; + + private final UserRepository userRepo; + + private final UserController userController; // TODO: This isn't a service its a controller. Why are we doing this? + + @Autowired + public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo, UserController userController) { + this.termsOfServiceRepo = termsOfServiceRepo; + this.userRepo = userRepo; + this.userController = userController; + } - @Inject - UserService userService; public boolean hasUserAcceptedLatest(String userId){ + // If TOS is not enabled, then the user has accepted it + if (!isToSEnabled){ + return true; + } + + // If there is no TOS, then the user has accepted it + if (getLatest() == null) { + return true; + } + logger.info("Checking Terms Of Service acceptance for user with id " + userId); return userRepo.checkAgainstTOSDate(userId); } @@ -63,7 +85,8 @@ public void acceptTermsOfService(String userId){ user.setAcceptedTOS(new Date()); List users = Arrays.asList(user); Date tosDate = termsOfServiceRepo.getLatest().getDateUpdated(); - userService.updateUser(users); + userController.updateUser(users); logger.info("TOS_LOG : User " + (!StringUtils.isEmpty(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata()) + " accepted the Terms of Service dated " + tosDate.toString()); } + } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java new file mode 100644 index 000000000..02df69e76 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -0,0 +1,57 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

Provides business logic for UserMetadataMapping endpoint.

+ */ +@Service +public class UserMetadataMappingService extends BaseEntityService { + + private final UserMetadataMappingRepository userMetadataMappingRepo; + + private final ConnectionRepository connectionRepo; + + @Autowired + public UserMetadataMappingService(UserMetadataMappingRepository userMetadataMappingRepo, ConnectionRepository connectionRepo) { + super(UserMetadataMapping.class); + this.userMetadataMappingRepo = userMetadataMappingRepo; + this.connectionRepo = connectionRepo; + } + + public List getAllMappingsForConnection(Connection connection) { + return userMetadataMappingRepo.findByConnection(connection); + } + + public ResponseEntity addMappings(List mappings) { + String errorMessage = "The following connectionIds do not exist:\n"; + boolean error = false; + for (UserMetadataMapping umm : mappings) { + Connection c = connectionRepo.findConnectionById(umm.getConnection().getId()); + if (c == null) { + error = true; + errorMessage += umm.getConnection().getId() + "\n"; + } else { + umm.setConnection(c); + } + } + if (error) { + return PICSUREResponse.success(errorMessage); + } + return addEntity(mappings, userMetadataMappingRepo); + } + + public List getAllMappings() { + return userMetadataMappingRepo.list(); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java new file mode 100644 index 000000000..4edc5afef --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -0,0 +1,67 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +@Service +public class UserService { + + private final Logger logger = Logger.getLogger(UserService.class.getName()); + + private final TOSService tosService; + + @Autowired + public UserService(TOSService tosService) { + this.tosService = tosService; + } + + public HashMap getUserProfileResponse(Map claims) { + logger.info("getUserProfileResponse() starting..."); + + HashMap responseMap = new HashMap(); + logger.info("getUserProfileResponse() initialized map"); + + logger.info("getUserProfileResponse() using claims:" + claims.toString()); + + String token = JWTUtil.createJwtToken( + JAXRSConfiguration.clientSecret, + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + JAXRSConfiguration.tokenExpirationTime + ); + logger.info("getUserProfileResponse() PSAMA JWT token has been generated. Token:" + token); + responseMap.put("token", token); + + logger.info("getUserProfileResponse() .usedId field is set"); + responseMap.put("userId", claims.get("sub").toString()); + + logger.info("getUserProfileResponse() .email field is set"); + responseMap.put("email", claims.get("email").toString()); + + logger.info("getUserProfileResponse() acceptedTOS is set"); + + boolean acceptedTOS = tosService.hasUserAcceptedLatest(claims.get("sub").toString()); + + responseMap.put("acceptedTOS", "" + acceptedTOS); + + logger.info("getUserProfileResponse() expirationDate is set"); + Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); + responseMap.put("expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString()); + + logger.info("getUserProfileResponse() finished"); + return responseMap; + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/package-info.java new file mode 100644 index 000000000..9a626b7e0 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/package-info.java @@ -0,0 +1 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java deleted file mode 100644 index 3e74e87a5..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthUtils.java +++ /dev/null @@ -1,127 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.utils; - -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.service.TOSService; -import io.jsonwebtoken.*; -import org.apache.commons.codec.binary.Base64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.validation.constraints.NotNull; -import javax.ws.rs.NotAuthorizedException; -import java.io.UnsupportedEncodingException; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -/** - *

Contains common methods for authentication and authorization.

- */ -public class AuthUtils { - - private static Logger logger = LoggerFactory.getLogger(AuthUtils.class); - - @Inject - TOSService tosService; - - /** - * support both Base64 encrypted and non-Base64 encrypted - * @param clientSecret - * @param token - * @return - */ - public static Jws parseToken(@NotNull String clientSecret, String token) - throws NotAuthorizedException{ - Jws jws; - - try { - jws = Jwts.parser().setSigningKey(clientSecret.getBytes()).parseClaimsJws(token); - } catch (SignatureException e) { - try { - if(JAXRSConfiguration.clientSecretIsBase64.startsWith("true")) { - // handle if client secret is base64 encoded - jws = Jwts.parser().setSigningKey(Base64.decodeBase64(clientSecret - .getBytes("UTF-8"))) - .parseClaimsJws(token); - } else { - // handle if client secret is not base64 encoded - jws = Jwts.parser().setSigningKey(clientSecret - .getBytes("UTF-8")) - .parseClaimsJws(token); - } - } catch (UnsupportedEncodingException ex){ - logger.error("parseToken() clientSecret encoding UTF-8 is not supported. " - + ex.getClass().getSimpleName() + ": " + ex.getMessage()); - throw new NotAuthorizedException("encoding is not supported"); - } catch (JwtException | IllegalArgumentException ex) { - logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); - throw new NotAuthorizedException(ex.getClass().getSimpleName()); - } - } catch (JwtException | IllegalArgumentException e) { - logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); - throw new NotAuthorizedException(e.getClass().getSimpleName()); - } - - if (jws == null) { - logger.error("parseToken() get null for jws body by parsing Token - " + token); - throw new NotAuthorizedException("please contact admin to see the log"); - } - - return jws; - } - - /* - * Generate a HashMap of all the information used in the JSON response back to the UI client, while also - * package the same information inside a valid PSAMA JWT token - * - */ - public HashMap getUserProfileResponse(Map claims) { - logger.debug("getUserProfileResponse() starting..."); - - HashMap responseMap = new HashMap(); - logger.debug("getUserProfileResponse() initialized map"); - - logger.debug("getUserProfileResponse() using claims:"+claims.toString()); - - String token = JWTUtil.createJwtToken( - JAXRSConfiguration.clientSecret, - "whatever", - "edu.harvard.hms.dbmi.psama", - claims, - claims.get("sub").toString(), - JAXRSConfiguration.tokenExpirationTime - ); - logger.debug("getUserProfileResponse() PSAMA JWT token has been generated. Token:"+token); - responseMap.put("token", token); - - logger.debug("getUserProfileResponse() .usedId field is set"); - responseMap.put("userId", claims.get("sub").toString()); - - logger.debug("getUserProfileResponse() .email field is set"); - responseMap.put("email", claims.get("email").toString()); - - logger.debug("getUserProfileResponse() acceptedTOS is set"); - - boolean acceptedTOS = acceptedTOSBySub(claims.get("sub").toString()); - - responseMap.put("acceptedTOS", ""+acceptedTOS); - - logger.debug("getUserProfileResponse() expirationDate is set"); - Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); - responseMap.put("expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString()); - - logger.debug("getUserProfileResponse() finished"); - return responseMap; - } - - public boolean acceptedTOSBySub(String subject) { - return JAXRSConfiguration.tosEnabled.startsWith("true") ? - tosService.getLatest() == null || tosService.hasUserAcceptedLatest(subject) : true; - } - - -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 77ceae132..80f979add 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -1,27 +1,34 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import io.jsonwebtoken.*; +import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; import java.util.Map; +import java.util.Optional; /** *

This class is for generating a JWT token and contains common methods for operations on JWT tokens.

*

For more information on JWT tokens, see https://github.com/hms-dbmi/jwt-creator/blob/master/src/main/java/edu/harvard/hms/dbmi/avillach/jwt/App.java

*/ public class JWTUtil { - private static Logger logger = LoggerFactory.getLogger(JWTUtil.class); + private final static Logger logger = LoggerFactory.getLogger(JWTUtil.class); private static final long defaultTTLMillis = 1000L * 60 * 60 * 24 * 7; + @Value("${application.client.secret}") + private static String CLIENT_SECRET; + /** - * * @param clientSecret * @param id * @param issuer @@ -30,15 +37,17 @@ public class JWTUtil { * @param ttlMillis * @return */ - public static String createJwtToken(String clientSecret, String id, String issuer, Map claims , String subject, long ttlMillis) { + public static String createJwtToken(String clientSecret, String id, String issuer, Map claims, String subject, long ttlMillis) { logger.debug("createJwtToken() starting..."); String jwt_token = null; - if (ttlMillis < 0) + if (ttlMillis < 0) { ttlMillis = defaultTTLMillis; + } - if (ttlMillis == 0) + if (ttlMillis == 0) { ttlMillis = 999L * 1000 * 60 * 60 * 24; + } SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); @@ -48,32 +57,60 @@ public static String createJwtToken(String clientSecret, String id, String issue byte[] apiKeySecretBytes = clientSecret.getBytes(); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); - //Let's set the JWT Claims - JwtBuilder builder = null; + //Builds the JWT and serializes it to a compact, URL-safe string + JwtBuilder builder = Jwts.builder() + .setClaims(claims) + .setId(id) + .setIssuedAt(now) + .setSubject(subject) + .setIssuer(issuer) + .signWith(signatureAlgorithm, signingKey); + + //if it has been specified, let's add the expiration + long expMillis = nowMillis + ttlMillis; + Date exp = new Date(expMillis); + builder.setExpiration(exp); + jwt_token = builder.compact(); + + return jwt_token; + } + + public static Jws parseToken(String token) { + Jws jws = null; + try { - //Builds the JWT and serializes it to a compact, URL-safe string - builder = Jwts.builder() - .setClaims(claims) - .setId(id) - .setIssuedAt(now) - .setSubject(subject) - .setIssuer(issuer) - .signWith(signatureAlgorithm, signingKey); - - //if it has been specified, let's add the expiration - if (ttlMillis >= 0) { - long expMillis = nowMillis + ttlMillis; - Date exp = new Date(expMillis); - builder.setExpiration(exp); + jws = Jwts.parser().setSigningKey(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); + } catch (SignatureException e) { + try { + if (JAXRSConfiguration.clientSecretIsBase64.startsWith("true")) { + // handle if client secret is base64 encoded + jws = Jwts.parser().setSigningKey(Base64.decodeBase64(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8))).parseClaimsJws(token); + } else { + // handle if client secret is not base64 encoded + jws = Jwts.parser().setSigningKey(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); + } + } catch (JwtException | IllegalArgumentException ex) { + logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + throw new NotAuthorizedException(ex.getClass().getSimpleName() + ": " + ex.getMessage()); } + } catch (JwtException | IllegalArgumentException e) { + logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + throw new NotAuthorizedException(e.getClass().getSimpleName() + ": " + e.getMessage()); + } - jwt_token = builder.compact(); - } catch (Exception ex) { - logger.error("createJwtToken() Exception:"+ex.getClass().getSimpleName()); - ex.printStackTrace(); + if (jws == null) { + logger.error("parseToken() get null for jws body by parsing Token - " + token); + throw new NotAuthorizedException("Please contact admin to see the log"); } + return jws; + } - return jwt_token; + public static Optional getTokenFromAuthorizationHeader(String authorizationHeader) { + if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) { + return Optional.empty(); + } + + return Optional.of(authorizationHeader.substring("Bearer".length()).trim()); } } diff --git a/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 000000000..8c7e347ad --- /dev/null +++ b/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,118 @@ +{ + "properties": [ + { + "name": "application.client.id", + "type": "java.lang.String", + "description": "Description for application.client.id." + }, + { + "name": "application.client.secret", + "type": "java.lang.String", + "description": "Description for application.client.secret." + }, + { + "name": "application.client.secret.base64", + "type": "java.lang.String", + "description": "Description for application.client.secret.base64." + }, + { + "name": "application.user.id.claim", + "type": "java.lang.String", + "description": "Description for application.user.id.claim." + }, + { + "name": "application.auth0.host", + "type": "java.lang.String", + "description": "Description for application.auth0.host." + }, + { + "name": "application.tos.enabled", + "type": "java.lang.String", + "description": "Description for application.tos.enabled." + }, + { + "name": "application.default.application.uuid", + "type": "java.lang.String", + "description": "Description for application.default.application.uuid." + }, + { + "name": "application.system.name", + "type": "java.lang.String", + "description": "Description for application.system.name." + }, + { + "name": "application.template.path", + "type": "java.lang.String", + "description": "Description for application.template.path." + }, + { + "name": "application.access.grant.email.subject", + "type": "java.lang.String", + "description": "Description for application.access.grant.email.subject." + }, + { + "name": "application.user.activation.reply.to", + "type": "java.lang.String", + "description": "Description for application.user.activation.reply.to." + }, + { + "name": "application.admin.users", + "type": "java.lang.String", + "description": "Description for application.admin.users." + }, + { + "name": "application.denied.email.enabled", + "type": "java.lang.String", + "description": "Description for application.denied.email.enabled." + }, + { + "name": "application.idp.provider", + "type": "java.lang.String", + "description": "Description for application.idp.provider." + }, + { + "name": "application.idp.provider.uri", + "type": "java.lang.String", + "description": "Description for application.idp.provider.uri." + }, + { + "name": "application.fence.client.id", + "type": "java.lang.String", + "description": "Description for application.fence.client.id." + }, + { + "name": "application.fence.client.secret", + "type": "java.lang.String", + "description": "Description for application.fence.client.secret." + }, + { + "name": "application.fence.redirect.url", + "type": "java.lang.String", + "description": "Description for application.fence.redirect.url." + }, + { + "name": "application.fence.consent.group.concept.path", + "type": "java.lang.String", + "description": "Description for application.fence.consent.group.concept.path." + }, + { + "name": "application.fence.standard.access.rules", + "type": "java.lang.String", + "description": "Description for application.fence.standard.access.rules." + }, + { + "name": "application.fence.harmonized.concept.path", + "type": "java.lang.String", + "description": "Description for application.fence.harmonized.concept.path." + }, + { + "name": "application.token.expiration.time", + "type": "java.lang.String", + "description": "Description for application.token.expiration.time." + }, + { + "name": "application.long.term.token.expiration.time", + "type": "java.lang.String", + "description": "Description for application.long.term.token.expiration.time." + } + ] } \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties new file mode 100644 index 000000000..28d01886d --- /dev/null +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -0,0 +1,50 @@ +# Application server port and context path +server.port=8090 +server.servlet.context-path=/auth + +spring.datasource.url=jdbc:your_database_url +spring.datasource.username=your_db_username +spring.datasource.password=your_db_password +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + +# JPA/Hibernate properties +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.YourDialect + +# Mail session configuration (Assuming Gmail SMTP for example) +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=your_email@gmail.com +spring.mail.password=your_email_password +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true + +# Custom application properties +application.client.id=your_client_id +application.client.secret=your_client_secret +application.client.secret.base64=your_base64_flag +application.user.id.claim=sub +application.auth0.host=your_auth0_host +application.tos.enabled=true +application.default.application.uuid=your_default_application_uuid +application.system.name=your_system_name +application.template.path=your_template_path +application.access.grant.email.subject=your_email_subject +application.user.activation.reply.to=your_reply_to_address +application.admin.users=your_admin_users +application.denied.email.enabled=your_denied_email_enabled_flag + +# IDP Provider configurations +application.idp.provider=your_idp_provider +application.idp.provider.uri=your_idp_provider_uri +application.fence.client.id=your_fence_client_id +application.fence.client.secret=your_fence_client_secret +application.fence.redirect.url=your_fence_redirect_url +application.fence.consent.group.concept.path=your_consent_group_concept_path +application.fence.standard.access.rules=your_standard_access_rules +application.fence.harmonized.concept.path=your_harmonized_concept_path + +# Token Expiration +application.token.expiration.time=3600000 # 1 hour in milliseconds +application.long.term.token.expiration.time=2592000000 # 30 days in milliseconds diff --git a/pic-sure-auth-services/src/main/webapp/META-INF/context.xml b/pic-sure-auth-services/src/main/webapp/META-INF/context.xml deleted file mode 100644 index d123dd3f8..000000000 --- a/pic-sure-auth-services/src/main/webapp/META-INF/context.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/pic-sure-auth-services/src/main/webapp/WEB-INF/beans.xml b/pic-sure-auth-services/src/main/webapp/WEB-INF/beans.xml deleted file mode 100644 index 1c97df0e3..000000000 --- a/pic-sure-auth-services/src/main/webapp/WEB-INF/beans.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java index 4618a11b4..35d46c8e7 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java @@ -1,19 +1,21 @@ package edu.harvard.hms.dbmi.avillach; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.rest.ApplicationService; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; import java.util.UUID; public class ApplicationServiceTest { + @Mock + private ApplicationService applicationService; + @Before public void init() { - JAXRSConfiguration.clientSecret = "test"; } @Test @@ -24,7 +26,7 @@ public void testGenerateToken(){ application.setName("Testing Application"); application.setUrl("https://psama.hms.harvard.edu"); - String token = new ApplicationService().generateApplicationToken(application); + String token = applicationService.generateApplicationToken(application); Assert.assertNotNull("Token is null, given application: " + application.getUuid(), token); Assert.assertTrue("Token is too short",token.length() > 10); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java index f864b2d2f..7a40e5f07 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java @@ -4,13 +4,14 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.data.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserService; -import edu.harvard.hms.dbmi.avillach.auth.service.OauthUserMatchingService; -import edu.harvard.hms.dbmi.avillach.auth.service.UserMetadataMappingService; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.OauthUserMatchingService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -41,10 +42,10 @@ public class Auth0MatchingServiceTest { UserMetadataMappingService mappingService = mock(UserMetadataMappingService.class); @Mock - UserService userService = mock(UserService.class); + UserController userController = mock(UserController.class); @InjectMocks - OauthUserMatchingService cut = new OauthUserMatchingService(); + OauthUserMatchingService cut = new OauthUserMatchingService(userRepo, userController, mappingService, mock(ConnectionRepository.class)); User persistedUser; ObjectMapper mapper = new ObjectMapper(); @@ -64,7 +65,7 @@ public Void answer(InvocationOnMock invocation) { persistedUser = userList.get(0); return null; } - }).when(userService).updateUser(any(List.class)); + }).when(userController).updateUser(any(List.class)); } @Test @@ -170,7 +171,7 @@ private List getAllMappingsForConnectionMock(String connect new UserMetadataMapping().setConnection(new Connection().setId("no-user-connection")).setGeneralMetadataJsonPath("$.email").setAuth0MetadataJsonPath("$.email"), new UserMetadataMapping().setConnection(new Connection().setId("invalid-path")).setGeneralMetadataJsonPath("$.email").setAuth0MetadataJsonPath("$.noPath") - ); + ); return allMappings.stream().filter((UserMetadataMapping mapping) -> { return mapping.getConnection().getId().equalsIgnoreCase(connectionId); }).collect(Collectors.toList()); @@ -179,15 +180,16 @@ private List getAllMappingsForConnectionMock(String connect public JsonNode mockAuthAPIUserInfo(String accessToken) throws IOException { Map map = Map.of("ldap-connector-access-token", "{ \"name\": \"Guy,Some\", \"family_name\": \"Guy\", \"given_name\": \"Some\", \"nickname\": \"CH000000000\", \"groups\": [], \"emails\": [ \"foo@childrens.harvard.edu\" ], \"dn\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"distinguishedName\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"organizationUnits\": \"CN=CH0000000,OU=users,DC=chbdir,DC=org\", \"email\": \"foo@childrens.harvard.edu\", \"updated_at\": \"2018-10-04T18:28:23.371Z\", \"picture\": \"https://s.gravatar.com/avatar/blablablablablablablablablablablabla?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fsp.png\", \"user_id\": \"ad|ldap-connector|blablablablablablablablablablablabla\", \"identities\": [ { \"user_id\": \"ldap-connector|blablablablablablablablablablablabla\", \"provider\": \"ad\", \"connection\": \"ldap-connector\", \"isSocial\": false } ], \"created_at\": \"2018-01-26T14:06:50.413Z\", \"username\": \"CH0000000\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.32\", \"last_login\": \"2018-10-04T18:28:23.091Z\", \"logins_count\": 399, \"blocked_for\": [], \"guardian_authenticators\": []}", - "github-access-token","{ \"email\": \"blablabla@gmail.com\", \"name\": \"Some Girl\", \"picture\": \"https://avatars3.githubusercontent.com/u/0000000000?v=4\", \"nickname\": \"blablabla\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/blablabla\", \"html_url\": \"https://github.com/blablabla\", \"followers_url\": \"https://api.github.com/users/blablabla/followers\", \"following_url\": \"https://api.github.com/users/blablabla/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/blablabla/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/blablabla/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/blablabla/subscriptions\", \"organizations_url\": \"https://api.github.com/users/blablabla/orgs\", \"repos_url\": \"https://api.github.com/users/blablabla/repos\", \"events_url\": \"https://api.github.com/users/blablabla/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/blablabla/received_events\", \"type\": \"User\", \"site_admin\": false, \"location\": \"Nowhere, USA\", \"hireable\": true, \"public_repos\": 8, \"public_gists\": \"0\", \"followers\": 3, \"following\": 1, \"updated_at\": \"2018-09-20T18:47:43.703Z\", \"emails\": [ { \"email\": \"blablabla@gmail.com\", \"primary\": true, \"verified\": true, \"visibility\": \"public\" }, { \"email\": \"blablabla@users.noreply.github.com\", \"primary\": false, \"verified\": true, \"visibility\": null } ], \"email_verified\": true, \"user_id\": \"github|0000000\", \"identities\": [ { \"provider\": \"github\", \"user_id\": \"000000000\", \"connection\": \"github\", \"isSocial\": true } ], \"created_at\": \"2016-10-22T22:38:20.437Z\", \"blog\": \"\", \"node_id\": \"blablabla=\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.198\", \"last_login\": \"2018-09-20T18:47:43.491Z\", \"logins_count\": 71, \"blocked_for\": [], \"guardian_authenticators\": []}", - "nih-gov-prod-access-token","{ \"email\": \"NOBODY\", \"sessionIndex\": \"blablablabla\", \"UserPrincipalName\": \"\", \"Mail\": \"\", \"FirstName\": \"\", \"LastName\": \"\", \"MiddleName\": \"\", \"NEDID\": \"\", \"nameIdAttributes\": { \"value\": \"NOBODY\", \"Format\": \"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\" }, \"authenticationmethod\": \"urn:oasis:names:tc:SAML:2.0:ac:classes:Password\", \"issuer\": \"https://auth.nih.gov/IDP\", \"updated_at\": \"2018-07-23T19:32:51.505Z\", \"name\": \"\", \"picture\": \"https://cdn.auth0.com/avatars/default.png\", \"user_id\": \"samlp|NOBODY\", \"nickname\": \"\", \"identities\": [ { \"user_id\": \"NOBODY\", \"provider\": \"samlp\", \"connection\": \"nih-gov-prod\", \"isSocial\": false } ], \"created_at\": \"2018-04-02T13:10:25.654Z\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.195\", \"last_login\": \"2018-07-23T19:32:51.254Z\", \"logins_count\": 12, \"blocked_for\": [], \"guardian_authenticators\": []}", - "no-mapping-connection-token","{ \"email\": \"foo@bar.com\", \"UserName\": \"foooo\", \"FirstName\": \"foo\", \"LastName\": \"oooo\",\"user_id\": \"samlp|fooBar\", \"identities\": [ { \"user_id\": \"fooBar\", \"provider\": \"samlp\", \"connection\": \"no-mapping-connection\", \"isSocial\": false } ]}", - "invalid-path-token","{ \"email\": \"bar@foo.com\", \"UserName\": \"bahh\", \"user_id\": \"samlp|barFoo\", \"identities\": [ { \"user_id\": \"barFoo\", \"provider\": \"samlp\", \"connection\": \"invalid-path\", \"isSocial\": true } ]}", - "no-user-token","{ \"email\": \"no@user.com\", \"UserName\": \"nooooooo\", \"user_id\": \"samlp|noUser\", \"identities\": [ { \"user_id\": \"noUser\", \"provider\": \"samlp\", \"connection\": \"no-user-connection\", \"isSocial\": false } ]}" + "github-access-token", "{ \"email\": \"blablabla@gmail.com\", \"name\": \"Some Girl\", \"picture\": \"https://avatars3.githubusercontent.com/u/0000000000?v=4\", \"nickname\": \"blablabla\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/blablabla\", \"html_url\": \"https://github.com/blablabla\", \"followers_url\": \"https://api.github.com/users/blablabla/followers\", \"following_url\": \"https://api.github.com/users/blablabla/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/blablabla/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/blablabla/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/blablabla/subscriptions\", \"organizations_url\": \"https://api.github.com/users/blablabla/orgs\", \"repos_url\": \"https://api.github.com/users/blablabla/repos\", \"events_url\": \"https://api.github.com/users/blablabla/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/blablabla/received_events\", \"type\": \"User\", \"site_admin\": false, \"location\": \"Nowhere, USA\", \"hireable\": true, \"public_repos\": 8, \"public_gists\": \"0\", \"followers\": 3, \"following\": 1, \"updated_at\": \"2018-09-20T18:47:43.703Z\", \"emails\": [ { \"email\": \"blablabla@gmail.com\", \"primary\": true, \"verified\": true, \"visibility\": \"public\" }, { \"email\": \"blablabla@users.noreply.github.com\", \"primary\": false, \"verified\": true, \"visibility\": null } ], \"email_verified\": true, \"user_id\": \"github|0000000\", \"identities\": [ { \"provider\": \"github\", \"user_id\": \"000000000\", \"connection\": \"github\", \"isSocial\": true } ], \"created_at\": \"2016-10-22T22:38:20.437Z\", \"blog\": \"\", \"node_id\": \"blablabla=\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.198\", \"last_login\": \"2018-09-20T18:47:43.491Z\", \"logins_count\": 71, \"blocked_for\": [], \"guardian_authenticators\": []}", + "nih-gov-prod-access-token", "{ \"email\": \"NOBODY\", \"sessionIndex\": \"blablablabla\", \"UserPrincipalName\": \"\", \"Mail\": \"\", \"FirstName\": \"\", \"LastName\": \"\", \"MiddleName\": \"\", \"NEDID\": \"\", \"nameIdAttributes\": { \"value\": \"NOBODY\", \"Format\": \"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\" }, \"authenticationmethod\": \"urn:oasis:names:tc:SAML:2.0:ac:classes:Password\", \"issuer\": \"https://auth.nih.gov/IDP\", \"updated_at\": \"2018-07-23T19:32:51.505Z\", \"name\": \"\", \"picture\": \"https://cdn.auth0.com/avatars/default.png\", \"user_id\": \"samlp|NOBODY\", \"nickname\": \"\", \"identities\": [ { \"user_id\": \"NOBODY\", \"provider\": \"samlp\", \"connection\": \"nih-gov-prod\", \"isSocial\": false } ], \"created_at\": \"2018-04-02T13:10:25.654Z\", \"app_metadata\": { \"roles\": [ \"ROLE_CITI_USER\" ] }, \"last_ip\": \"134.174.140.195\", \"last_login\": \"2018-07-23T19:32:51.254Z\", \"logins_count\": 12, \"blocked_for\": [], \"guardian_authenticators\": []}", + "no-mapping-connection-token", "{ \"email\": \"foo@bar.com\", \"UserName\": \"foooo\", \"FirstName\": \"foo\", \"LastName\": \"oooo\",\"user_id\": \"samlp|fooBar\", \"identities\": [ { \"user_id\": \"fooBar\", \"provider\": \"samlp\", \"connection\": \"no-mapping-connection\", \"isSocial\": false } ]}", + "invalid-path-token", "{ \"email\": \"bar@foo.com\", \"UserName\": \"bahh\", \"user_id\": \"samlp|barFoo\", \"identities\": [ { \"user_id\": \"barFoo\", \"provider\": \"samlp\", \"connection\": \"invalid-path\", \"isSocial\": true } ]}", + "no-user-token", "{ \"email\": \"no@user.com\", \"UserName\": \"nooooooo\", \"user_id\": \"samlp|noUser\", \"identities\": [ { \"user_id\": \"noUser\", \"provider\": \"samlp\", \"connection\": \"no-user-connection\", \"isSocial\": false } ]}" ); - String result = map.get(accessToken); + String result = map.get(accessToken); Map jsonMap = mapper.readValue(result, - new TypeReference>(){}); + new TypeReference>() { + }); return mapper.valueToTree(jsonMap); } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTest.java index 04dca8618..1896f9066 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTest.java @@ -1,8 +1,8 @@ package edu.harvard.hms.dbmi.avillach; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.AuthorizationService; +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTestByUseCases.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTestByUseCases.java index 98b21d43e..8e392b4cc 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTestByUseCases.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/AuthorizationServiceTestByUseCases.java @@ -1,8 +1,8 @@ package edu.harvard.hms.dbmi.avillach; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.hms.dbmi.avillach.auth.data.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.service.auth.AuthorizationService; +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; From e2a49bbb6a6e6d059ac06f8028f24f90301b3f2d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 25 Mar 2024 16:29:34 -0400 Subject: [PATCH 008/222] WIP: Continuing to migrate to Java 21, Spring 6, and Tomcat I am continuing to migrate the psama application over to java 21, Spring 6, and an embedded tomcat. As I do this I have been utilizing the time to refactor controller, configuration, etc. In order to improve the overall code quality of the application and to allow for better application testing. By separating our application layers we can more readily and reliably test our service layer. --- .../auth => bak}/JAXRSConfiguration.java | 748 +++++++++--------- .../avillach/auth/enums/SecurityRoles.java | 74 ++ .../avillach/auth/model/TokenInspection.java | 48 ++ .../auth/rest/AccessRuleController.java | 65 +- .../avillach/auth/rest/RoleController.java | 106 +-- .../auth/rest/TermsOfSerivceController.java | 68 +- .../avillach/auth/rest/TokenController.java | 289 +------ .../avillach/auth/rest/UserController.java | 258 +----- .../avillach/auth/service/RoleService.java | 96 +++ .../auth/service/impl/AccessRuleService.java | 58 ++ .../service/impl/AuthenticationService.java | 1 - .../auth/service/impl/BaseEntityService.java | 2 - .../auth/service/impl/TokenService.java | 292 +++++++ .../auth/service/impl/UserService.java | 251 +++++- .../hms/dbmi/avillach/auth/utils/JWTUtil.java | 1 - .../dbmi/avillach/auth/utils/JsonUtils.java | 10 - 16 files changed, 1300 insertions(+), 1067 deletions(-) rename pic-sure-auth-services/{src/main/java/edu/harvard/hms/dbmi/avillach/auth => bak}/JAXRSConfiguration.java (97%) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/TokenInspection.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java b/pic-sure-auth-services/bak/JAXRSConfiguration.java similarity index 97% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java rename to pic-sure-auth-services/bak/JAXRSConfiguration.java index a4845dd95..5e6eecd29 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/JAXRSConfiguration.java +++ b/pic-sure-auth-services/bak/JAXRSConfiguration.java @@ -1,374 +1,374 @@ -package edu.harvard.hms.dbmi.avillach.auth; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.TokenService; -import io.swagger.jaxrs.config.BeanConfig; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import javax.ejb.Startup; -import javax.inject.Inject; -import javax.mail.Session; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.ws.rs.ApplicationPath; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.SecurityContext; -import java.util.*; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; - -/** - *

When you deploy the PSAMA application WAR file to a new server, this class is called to supply basic configuration information.

- */ -@Startup -@ApplicationPath("auth") -public class JAXRSConfiguration extends Application { - - private Logger logger = LoggerFactory.getLogger(JAXRSConfiguration.class); - - @Resource(mappedName = "java:global/client_id") - public static String clientId; - @Resource(mappedName = "java:global/client_secret") - public static String clientSecret; - @Resource(mappedName = "java:global/clientSecretIsBase64") - public static String clientSecretIsBase64; - - @Resource(mappedName = "java:global/user_id_claim") - public static String userIdClaim; - - @Resource(mappedName = "java:global/auth0host") - public static String auth0host; - - @Resource(mappedName = "java:global/tosEnabled") - public static String tosEnabled; - - /** - * The default application UUID assumed for all operational contexts where - * one is not supplied. - */ - @Resource(mappedName = "java:global/defaultApplicationUUID") - public static String defaultApplicationUUID; - - @Resource(mappedName = "java:global/systemName") - public static String systemName; - - @Resource(mappedName = "java:global/templatePath") - public static String templatePath; - - @Resource(mappedName = "java:global/accessGrantEmailSubject") - public static String accessGrantEmailSubject; - - @Resource(mappedName = "java:global/userActivationReplyTo") - public static String userActivationReplyTo; - - @Resource(lookup = "java:jboss/mail/gmail") - public static Session mailSession; - - @Resource(lookup = "java:global/adminUsers") - public static String adminUsers; - - @Resource(lookup = "java:global/deniedEmailEnabled") - public static String deniedEmailEnabled; - - // See checkIDPProvider method for setting these variables - public static String idp_provider; - public static String idp_provider_uri; - public static String fence_client_id; - public static String fence_client_secret; - public static String fence_redirect_url; - public static String fence_consent_group_concept_path; - public static String fence_standard_access_rules; - public static String fence_harmonized_concept_path; - - public static String defaultAdminRoleName = "PIC-SURE Top Admin"; - - public static long tokenExpirationTime; - // default expiration time is 1 hr - private static long defaultTokenExpirationTime = 1000L * 60 * 60; - - public static long longTermTokenExpirationTime; - // default long term token expiration time is 30 days - private static long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30; - - @Inject - RoleRepository roleRepo; - - @Inject - PrivilegeRepository privilegeRepo; - - @Inject - ConnectionRepository connectionRepo; - - public final static ObjectMapper objectMapper = new ObjectMapper(); - - public static final HttpClient client = HttpClientBuilder.create().useSystemProperties().build(); - - - @PostConstruct - public void init() { - logger.info("Starting auth micro app"); - - /* - create an admin role if there isn't one - */ - logger.info("Start initializing admin role in database"); - initializeDefaultAdminRole(); - logger.info("Finished initializing admin role."); - - logger.info("Start initializing tokens expiration time."); - initializeTokenExpirationTime(); - initializeLongTermTokenExpirationTime(); - logger.info("Finished initializing token expiration time."); - - logger.info("Determine IDP provider"); - checkIDPProvider(); - - mailSession.getProperties().put("mail.smtp.ssl.trust", "smtp.gmail.com"); - - logger.info("Auth micro app has been successfully started"); - - //Set info for the swagger.json - BeanConfig beanConfig = new BeanConfig(); - beanConfig.setVersion("1.0.0"); - beanConfig.setSchemes(new String[] { "https" }); - beanConfig.setDescription("APIs for accessing PIC-SURE-AUTH-MICROAPP - a centralized authentication/authorization micro services"); - beanConfig.setTitle("PIC-SURE-AUTH-MICROAPP"); - beanConfig.setBasePath("/psama"); - beanConfig.setResourcePackage(TokenService.class.getPackage().getName()); - beanConfig.setScan(true); - } - - /* - * Check if the IDP provider is set, and if it is, then determine additional - * settings. - * - * If flag is missing, or empty, the default is Auth0 configuration. - * - * This is currently only works for FENCE integration. - * - */ - public void checkIDPProvider() { - logger.debug("checkIDPProvider() starting...."); - - Context ctx = null; - try { - ctx = new InitialContext(); - } catch (NamingException e) { - e.printStackTrace(); - } - - try { - idp_provider = (String)ctx.lookup("java:global/idp_provider"); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - idp_provider = "default"; - } - logger.info("checkIDPProvider() idp provider is now :"+idp_provider); - - if (idp_provider.equalsIgnoreCase("fence")) { - try { - idp_provider_uri = (String)ctx.lookup("java:global/idp_provider_uri"); - - fence_client_id = (String) ctx.lookup("java:global/fence_client_id"); - fence_client_secret = (String) ctx.lookup("java:global/fence_client_secret"); - fence_redirect_url = (String) ctx.lookup("java:global/fence_redirect_url"); - - fence_consent_group_concept_path = (String) ctx.lookup("java:global/fence_consent_group_concept_path"); - if (fence_consent_group_concept_path == null) { - logger.error("checkIDPProvider() Empty consent group concept path from standalone.xml. Using default!"); - fence_consent_group_concept_path = "\\\\_Consents\\\\Short Study Accession with Consent code\\\\"; - } - - fence_standard_access_rules = (String) ctx.lookup("java:global/fence_standard_access_rules"); - if (fence_standard_access_rules.isEmpty()) { - logger.error("checkIDPProvider() Empty access rules from standalone.xml. Using defaults."); - fence_standard_access_rules = "GATE_ONLY_INFO,GATE_ONLY_QUERY,GATE_ONLY_SEARCH,GATE_FENCE_CONSENT_REQUIRED"; - } - - fence_harmonized_concept_path = (String) ctx.lookup("java:global/fence_harmonized_concept_path"); - if (fence_harmonized_concept_path.isEmpty()) { - logger.error("checkIDPProvider() Empty harmonized concept path. Not in use."); - fence_harmonized_concept_path = ""; - } - logger.debug("checkIDPProvider() idp provider FENCE is configured"); - - // Upsert FENCE connection - Connection c = connectionRepo.getUniqueResultByColumn("label","FENCE"); - if (c != null) { - logger.debug("checkIDPProvider() FENCE connection already exists."); - } else { - logger.debug("checkIDPProvider() Create new FENCE connection"); - c = new Connection(); - c.setLabel("FENCE"); - c.setId("fence"); - c.setSubPrefix("fence|"); - c.setRequiredFields("[{\"label\":\"email\",\"id\":\"email\"}]"); - connectionRepo.persist(c); - logger.debug("checkIDPProvider() New FENCE connetion has been created"); - } - - // For debugging purposes, here is a dump of most of the FENCE variables - logger.info("checkIDPProvider() fence_standard_access_rules "+fence_standard_access_rules); - logger.info("checkIDPProvider() fence_consent_group_concept_path "+fence_consent_group_concept_path); - logger.info("checkIDPProvider() fence_harmonized_concept_path "+fence_harmonized_concept_path); - - } catch (Exception ex) { - logger.error("checkIDPProvider() "+ex.getMessage()); - logger.error("checkIDPProvider() Invalid FENCE IDP Provider Setup. Mandatory fields are missing. "+ - "Check configuration in standalone.xml"); - } - } - logger.debug("checkIDPProvider() finished"); - } - - private void initializeTokenExpirationTime(){ - try { - Context ctx = new InitialContext(); - tokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/tokenExpirationTime")); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - tokenExpirationTime = defaultTokenExpirationTime; - } - - logger.info("Set token expiration time to " + tokenExpirationTime + " milliseconds"); - - } - - private void initializeLongTermTokenExpirationTime(){ - try { - Context ctx = new InitialContext(); - longTermTokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/longTermTokenExpirationTime")); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - longTermTokenExpirationTime = defaultLongTermTokenExpirationTime; - } - - logger.info("Set long term token expiration time to " + longTermTokenExpirationTime + " milliseconds"); - - } - - private void initializeDefaultAdminRole(){ - - // make sure system admin and super admin privileges are added in the database - checkAndAddAdminPrivileges(); - - if (checkIfAdminRoleExists()){ - logger.info("Admin role already exists in database, no need for the creation."); - return; - } - - logger.info("Didn't find any role contains both " + ADMIN + - " and " + SUPER_ADMIN + - " in database, start to create one."); - Privilege systemAdmin = privilegeRepo.getByColumn("name", ADMIN).get(0); - Privilege superAdmin = privilegeRepo.getByColumn("name", SUPER_ADMIN).get(0); - - Role role = new Role(); - List roles = roleRepo.getByColumn("name", defaultAdminRoleName); - boolean isAdminRole = false; - if (roles != null && !roles.isEmpty()) { - logger.info("Found a role with default admin name " + defaultAdminRoleName + ", but without proper privileges associated with"); - role = roles.get(0); - isAdminRole = true; - } - role.setDescription("PIC-SURE Auth Micro App Top admin including Admin and super Admin"); - Set privileges = new HashSet<>(); - privileges.add(systemAdmin); - privileges.add(superAdmin); - role.setPrivileges(privileges); - - if(isAdminRole){ - roleRepo.merge(role); - logger.info("Finished updating the admin role, roleId: " + role.getUuid()); - } else { - role.setName(defaultAdminRoleName); - roleRepo.persist(role); - logger.info("Finished creating an admin role, roleId: " + role.getUuid()); - } - } - - private void checkAndAddAdminPrivileges(){ - logger.info("Checking if system admin and super admin privileges are added"); - List privileges = privilegeRepo.list(); - if (privileges == null) - privileges = new ArrayList<>(); - - Privilege superAdmin = null, systemAdmin = null; - - for (Privilege p : privileges) { - if (superAdmin != null && systemAdmin != null) - break; - - if (SUPER_ADMIN.equals(p.getName())){ - superAdmin = p; - continue; - } - - if (ADMIN.equals(p.getName())){ - systemAdmin = p; - continue; - } - } - - if (superAdmin == null){ - logger.info("Adding super admin"); - superAdmin = new Privilege(); - superAdmin.setName(SUPER_ADMIN); - superAdmin.setDescription("PIC-SURE Auth super admin for managing roles/privileges/application/connections"); - privilegeRepo.persist(superAdmin); - } - - if (systemAdmin == null) { - logger.info("Adding system admin"); - systemAdmin = new Privilege(); - systemAdmin.setName(ADMIN); - systemAdmin.setDescription("PIC-SURE Auth admin for managing users."); - privilegeRepo.persist(systemAdmin); - } - } - - private boolean checkIfAdminRoleExists(){ - logger.info("Checking if admin role already exists in database"); - List roles = roleRepo.list(); - if (roles == null || roles.isEmpty()) { - return false; - } - - boolean systemAdmin = false, superAdmin = false; - - for (Role role : roles) { - Set privileges = role.getPrivileges(); - if (privileges == null || privileges.isEmpty()) - continue; - - for (Privilege privilege : privileges) { - if (ADMIN.equals(privilege.getName())) { - systemAdmin = true; - } else if (SUPER_ADMIN.equals(privilege.getName())) { - superAdmin = true; - } - } - } - - return systemAdmin && superAdmin; - } - - public static String getPrincipalName(SecurityContext securityContext){ - if (securityContext.getUserPrincipal() == null) - return "No security context set, "; - - return securityContext.getUserPrincipal().getName(); - } - -} +package edu.harvard.hms.dbmi.avillach.auth; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.rest.TokenService; +import io.swagger.jaxrs.config.BeanConfig; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.ejb.Startup; +import javax.inject.Inject; +import javax.mail.Session; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.SecurityContext; +import java.util.*; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; + +/** + *

When you deploy the PSAMA application WAR file to a new server, this class is called to supply basic configuration information.

+ */ +@Startup +@ApplicationPath("auth") +public class JAXRSConfiguration extends Application { + + private Logger logger = LoggerFactory.getLogger(JAXRSConfiguration.class); + + @Resource(mappedName = "java:global/client_id") + public static String clientId; + @Resource(mappedName = "java:global/client_secret") + public static String clientSecret; + @Resource(mappedName = "java:global/clientSecretIsBase64") + public static String clientSecretIsBase64; + + @Resource(mappedName = "java:global/user_id_claim") + public static String userIdClaim; + + @Resource(mappedName = "java:global/auth0host") + public static String auth0host; + + @Resource(mappedName = "java:global/tosEnabled") + public static String tosEnabled; + + /** + * The default application UUID assumed for all operational contexts where + * one is not supplied. + */ + @Resource(mappedName = "java:global/defaultApplicationUUID") + public static String defaultApplicationUUID; + + @Resource(mappedName = "java:global/systemName") + public static String systemName; + + @Resource(mappedName = "java:global/templatePath") + public static String templatePath; + + @Resource(mappedName = "java:global/accessGrantEmailSubject") + public static String accessGrantEmailSubject; + + @Resource(mappedName = "java:global/userActivationReplyTo") + public static String userActivationReplyTo; + + @Resource(lookup = "java:jboss/mail/gmail") + public static Session mailSession; + + @Resource(lookup = "java:global/adminUsers") + public static String adminUsers; + + @Resource(lookup = "java:global/deniedEmailEnabled") + public static String deniedEmailEnabled; + + // See checkIDPProvider method for setting these variables + public static String idp_provider; + public static String idp_provider_uri; + public static String fence_client_id; + public static String fence_client_secret; + public static String fence_redirect_url; + public static String fence_consent_group_concept_path; + public static String fence_standard_access_rules; + public static String fence_harmonized_concept_path; + + public static String defaultAdminRoleName = "PIC-SURE Top Admin"; + + public static long tokenExpirationTime; + // default expiration time is 1 hr + private static long defaultTokenExpirationTime = 1000L * 60 * 60; + + public static long longTermTokenExpirationTime; + // default long term token expiration time is 30 days + private static long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30; + + @Inject + RoleRepository roleRepo; + + @Inject + PrivilegeRepository privilegeRepo; + + @Inject + ConnectionRepository connectionRepo; + + public final static ObjectMapper objectMapper = new ObjectMapper(); + + public static final HttpClient client = HttpClientBuilder.create().useSystemProperties().build(); + + + @PostConstruct + public void init() { + logger.info("Starting auth micro app"); + + /* + create an admin role if there isn't one + */ + logger.info("Start initializing admin role in database"); + initializeDefaultAdminRole(); + logger.info("Finished initializing admin role."); + + logger.info("Start initializing tokens expiration time."); + initializeTokenExpirationTime(); + initializeLongTermTokenExpirationTime(); + logger.info("Finished initializing token expiration time."); + + logger.info("Determine IDP provider"); + checkIDPProvider(); + + mailSession.getProperties().put("mail.smtp.ssl.trust", "smtp.gmail.com"); + + logger.info("Auth micro app has been successfully started"); + + //Set info for the swagger.json + BeanConfig beanConfig = new BeanConfig(); + beanConfig.setVersion("1.0.0"); + beanConfig.setSchemes(new String[] { "https" }); + beanConfig.setDescription("APIs for accessing PIC-SURE-AUTH-MICROAPP - a centralized authentication/authorization micro services"); + beanConfig.setTitle("PIC-SURE-AUTH-MICROAPP"); + beanConfig.setBasePath("/psama"); + beanConfig.setResourcePackage(TokenService.class.getPackage().getName()); + beanConfig.setScan(true); + } + + /* + * Check if the IDP provider is set, and if it is, then determine additional + * settings. + * + * If flag is missing, or empty, the default is Auth0 configuration. + * + * This is currently only works for FENCE integration. + * + */ + public void checkIDPProvider() { + logger.debug("checkIDPProvider() starting...."); + + Context ctx = null; + try { + ctx = new InitialContext(); + } catch (NamingException e) { + e.printStackTrace(); + } + + try { + idp_provider = (String)ctx.lookup("java:global/idp_provider"); + } catch (NamingException | ClassCastException | NumberFormatException ex){ + idp_provider = "default"; + } + logger.info("checkIDPProvider() idp provider is now :"+idp_provider); + + if (idp_provider.equalsIgnoreCase("fence")) { + try { + idp_provider_uri = (String)ctx.lookup("java:global/idp_provider_uri"); + + fence_client_id = (String) ctx.lookup("java:global/fence_client_id"); + fence_client_secret = (String) ctx.lookup("java:global/fence_client_secret"); + fence_redirect_url = (String) ctx.lookup("java:global/fence_redirect_url"); + + fence_consent_group_concept_path = (String) ctx.lookup("java:global/fence_consent_group_concept_path"); + if (fence_consent_group_concept_path == null) { + logger.error("checkIDPProvider() Empty consent group concept path from standalone.xml. Using default!"); + fence_consent_group_concept_path = "\\\\_Consents\\\\Short Study Accession with Consent code\\\\"; + } + + fence_standard_access_rules = (String) ctx.lookup("java:global/fence_standard_access_rules"); + if (fence_standard_access_rules.isEmpty()) { + logger.error("checkIDPProvider() Empty access rules from standalone.xml. Using defaults."); + fence_standard_access_rules = "GATE_ONLY_INFO,GATE_ONLY_QUERY,GATE_ONLY_SEARCH,GATE_FENCE_CONSENT_REQUIRED"; + } + + fence_harmonized_concept_path = (String) ctx.lookup("java:global/fence_harmonized_concept_path"); + if (fence_harmonized_concept_path.isEmpty()) { + logger.error("checkIDPProvider() Empty harmonized concept path. Not in use."); + fence_harmonized_concept_path = ""; + } + logger.debug("checkIDPProvider() idp provider FENCE is configured"); + + // Upsert FENCE connection + Connection c = connectionRepo.getUniqueResultByColumn("label","FENCE"); + if (c != null) { + logger.debug("checkIDPProvider() FENCE connection already exists."); + } else { + logger.debug("checkIDPProvider() Create new FENCE connection"); + c = new Connection(); + c.setLabel("FENCE"); + c.setId("fence"); + c.setSubPrefix("fence|"); + c.setRequiredFields("[{\"label\":\"email\",\"id\":\"email\"}]"); + connectionRepo.persist(c); + logger.debug("checkIDPProvider() New FENCE connetion has been created"); + } + + // For debugging purposes, here is a dump of most of the FENCE variables + logger.info("checkIDPProvider() fence_standard_access_rules "+fence_standard_access_rules); + logger.info("checkIDPProvider() fence_consent_group_concept_path "+fence_consent_group_concept_path); + logger.info("checkIDPProvider() fence_harmonized_concept_path "+fence_harmonized_concept_path); + + } catch (Exception ex) { + logger.error("checkIDPProvider() "+ex.getMessage()); + logger.error("checkIDPProvider() Invalid FENCE IDP Provider Setup. Mandatory fields are missing. "+ + "Check configuration in standalone.xml"); + } + } + logger.debug("checkIDPProvider() finished"); + } + + private void initializeTokenExpirationTime(){ + try { + Context ctx = new InitialContext(); + tokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/tokenExpirationTime")); + } catch (NamingException | ClassCastException | NumberFormatException ex){ + tokenExpirationTime = defaultTokenExpirationTime; + } + + logger.info("Set token expiration time to " + tokenExpirationTime + " milliseconds"); + + } + + private void initializeLongTermTokenExpirationTime(){ + try { + Context ctx = new InitialContext(); + longTermTokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/longTermTokenExpirationTime")); + } catch (NamingException | ClassCastException | NumberFormatException ex){ + longTermTokenExpirationTime = defaultLongTermTokenExpirationTime; + } + + logger.info("Set long term token expiration time to " + longTermTokenExpirationTime + " milliseconds"); + + } + + private void initializeDefaultAdminRole(){ + + // make sure system admin and super admin privileges are added in the database + checkAndAddAdminPrivileges(); + + if (checkIfAdminRoleExists()){ + logger.info("Admin role already exists in database, no need for the creation."); + return; + } + + logger.info("Didn't find any role contains both " + ADMIN + + " and " + SUPER_ADMIN + + " in database, start to create one."); + Privilege systemAdmin = privilegeRepo.getByColumn("name", ADMIN).get(0); + Privilege superAdmin = privilegeRepo.getByColumn("name", SUPER_ADMIN).get(0); + + Role role = new Role(); + List roles = roleRepo.getByColumn("name", defaultAdminRoleName); + boolean isAdminRole = false; + if (roles != null && !roles.isEmpty()) { + logger.info("Found a role with default admin name " + defaultAdminRoleName + ", but without proper privileges associated with"); + role = roles.get(0); + isAdminRole = true; + } + role.setDescription("PIC-SURE Auth Micro App Top admin including Admin and super Admin"); + Set privileges = new HashSet<>(); + privileges.add(systemAdmin); + privileges.add(superAdmin); + role.setPrivileges(privileges); + + if(isAdminRole){ + roleRepo.merge(role); + logger.info("Finished updating the admin role, roleId: " + role.getUuid()); + } else { + role.setName(defaultAdminRoleName); + roleRepo.persist(role); + logger.info("Finished creating an admin role, roleId: " + role.getUuid()); + } + } + + private void checkAndAddAdminPrivileges(){ + logger.info("Checking if system admin and super admin privileges are added"); + List privileges = privilegeRepo.list(); + if (privileges == null) + privileges = new ArrayList<>(); + + Privilege superAdmin = null, systemAdmin = null; + + for (Privilege p : privileges) { + if (superAdmin != null && systemAdmin != null) + break; + + if (SUPER_ADMIN.equals(p.getName())){ + superAdmin = p; + continue; + } + + if (ADMIN.equals(p.getName())){ + systemAdmin = p; + continue; + } + } + + if (superAdmin == null){ + logger.info("Adding super admin"); + superAdmin = new Privilege(); + superAdmin.setName(SUPER_ADMIN); + superAdmin.setDescription("PIC-SURE Auth super admin for managing roles/privileges/application/connections"); + privilegeRepo.persist(superAdmin); + } + + if (systemAdmin == null) { + logger.info("Adding system admin"); + systemAdmin = new Privilege(); + systemAdmin.setName(ADMIN); + systemAdmin.setDescription("PIC-SURE Auth admin for managing users."); + privilegeRepo.persist(systemAdmin); + } + } + + private boolean checkIfAdminRoleExists(){ + logger.info("Checking if admin role already exists in database"); + List roles = roleRepo.list(); + if (roles == null || roles.isEmpty()) { + return false; + } + + boolean systemAdmin = false, superAdmin = false; + + for (Role role : roles) { + Set privileges = role.getPrivileges(); + if (privileges == null || privileges.isEmpty()) + continue; + + for (Privilege privilege : privileges) { + if (ADMIN.equals(privilege.getName())) { + systemAdmin = true; + } else if (SUPER_ADMIN.equals(privilege.getName())) { + superAdmin = true; + } + } + } + + return systemAdmin && superAdmin; + } + + public static String getPrincipalName(SecurityContext securityContext){ + if (securityContext.getUserPrincipal() == null) + return "No security context set, "; + + return securityContext.getUserPrincipal().getName(); + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java new file mode 100644 index 000000000..d6e4a89cf --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java @@ -0,0 +1,74 @@ +package edu.harvard.hms.dbmi.avillach.auth.enums; + +import java.util.List; +import java.util.Set; + +public enum SecurityRoles { + + ADMIN("ADMIN"), + SUPER_ADMIN("SUPER_ADMIN"), + PIC_SURE_TOP_ADMIN("PIC-SURE Top Admin"); + + private final String role; + + SecurityRoles(String role) { + this.role = role; + } + + /** + * Check if a role is contained in a set of roles + * + * @param roles User roles + * @param role Role to check + * @return True if the role is contained in the set of roles + */ + public static boolean contains(Set roles, String role) { + for (String r : roles) { + if (r.equals(role)) { + return true; + } + } + return false; + } + + public String getRole() { + return role; + } + + public static SecurityRoles getRole(String role) { + for (SecurityRoles securityRole : SecurityRoles.values()) { + if (securityRole.getRole().equals(role)) { + return securityRole; + } + } + return null; + } + + public static boolean contains(String role) { + for (SecurityRoles securityRole : SecurityRoles.values()) { + if (securityRole.getRole().equals(role)) { + return true; + } + } + return false; + } + + public static boolean contains(SecurityRoles role) { + for (SecurityRoles securityRole : SecurityRoles.values()) { + if (securityRole.equals(role)) { + return true; + } + } + return false; + } + + public static boolean contains(List roles) { + for (String role : roles) { + if (!contains(role)) { + return false; + } + } + return true; + } + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/TokenInspection.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/TokenInspection.java new file mode 100644 index 000000000..cebf2e8ba --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/TokenInspection.java @@ -0,0 +1,48 @@ +package edu.harvard.hms.dbmi.avillach.auth.model; + +import io.jsonwebtoken.Claims; + +import java.util.HashMap; +import java.util.Map; + +/** + * inner used token introspection class with active:false included + */ +public class TokenInspection { + + Map responseMap = new HashMap<>(); + String message = null; + + public TokenInspection() { + responseMap.put("active", false); + } + + public TokenInspection(String message) { + responseMap.put("active", false); + this.message = message; + } + + public Map getResponseMap() { + return responseMap; + } + + public void setResponseMap(Map responseMap) { + this.responseMap = responseMap; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public void addField(String key, Object value) { + responseMap.put(key, value); + } + + public void addAllFields(Claims body) { + responseMap.putAll(body); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index 314e03b57..b49915b87 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -1,16 +1,18 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; -import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.*; -import javax.transaction.Transactional; import java.util.List; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -24,17 +26,13 @@ */ @Controller("/accessRule") -public class AccessRuleController extends BaseEntityService { +public class AccessRuleController { - private final AccessRuleRepository accessRuleRepo; - -// @Context -// private SecurityContext securityContext; + private final AccessRuleService accessRuleService; @Autowired - public AccessRuleController(AccessRuleRepository accessRuleRepo) { - super(AccessRule.class); - this.accessRuleRepo = accessRuleRepo; + public AccessRuleController(AccessRuleService accessRuleService) { + this.accessRuleService = accessRuleService; } @ApiOperation(value = "GET information of one AccessRule with the UUID, requires ADMIN or SUPER_ADMIN role") @@ -42,68 +40,47 @@ public AccessRuleController(AccessRuleRepository accessRuleRepo) { @GetMapping(value = "/{accessRuleId}") public ResponseEntity getAccessRuleById( @ApiParam(value="The UUID of the accessRule to fetch information about") - @PathParam("accessRuleId") String accessRuleId) { - return getEntityById(accessRuleId,accessRuleRepo); + @PathVariable("accessRuleId") String accessRuleId) { + return this.accessRuleService.getEntityById(accessRuleId); } @ApiOperation(value = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") @Secured({ADMIN, SUPER_ADMIN}) @GetMapping("") public ResponseEntity getAccessRuleAll() { - return getEntityAll(accessRuleRepo); + return this.accessRuleService.getEntityAll(); } @ApiOperation(value = "POST a list of AccessRules, requires SUPER_ADMIN role") - @POST @RolesAllowed(SUPER_ADMIN) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity addAccessRule( @ApiParam(required = true, value = "A list of AccessRule in JSON format") List accessRules){ - accessRules.stream().forEach(accessRule -> { - if (accessRule.getEvaluateOnlyByGates() == null) - accessRule.setEvaluateOnlyByGates(false); - - if (accessRule.getCheckMapKeyOnly() == null) - accessRule.setCheckMapKeyOnly(false); - - if (accessRule.getCheckMapNode() == null) - accessRule.setCheckMapNode(false); - - if (accessRule.getGateAnyRelation() == null) - accessRule.setGateAnyRelation(false); - }); - return addEntity(accessRules, accessRuleRepo); + return this.accessRuleService.addEntity(accessRules); } @ApiOperation(value = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") - @PUT @RolesAllowed(SUPER_ADMIN) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PutMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity updateAccessRule( @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") List accessRules){ - return updateEntity(accessRules, accessRuleRepo); + return this.accessRuleService.updateEntity(accessRules); } @ApiOperation(value = "DELETE an AccessRule by Id only if the accessRule is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE @RolesAllowed(SUPER_ADMIN) - @Path("/{accessRuleId}") + @DeleteMapping(path = "/{accessRuleId}") public ResponseEntity removeById( @ApiParam(required = true, value = "A valid accessRule Id") - @PathParam("accessRuleId") final String accessRuleId) { - return removeEntityById(accessRuleId, accessRuleRepo); + @PathVariable("accessRuleId") final String accessRuleId) { + return this.accessRuleService.removeEntityById(accessRuleId); } @ApiOperation(value = "GET all types listed for the rule in accessRule that could be used, requires SUPER_ADMIN role") - @GET @RolesAllowed(SUPER_ADMIN) - @Path("/allTypes") - @Produces(MediaType.APPLICATION_JSON) + @GetMapping(path = "/allTypes", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getAllTypes(){ return PICSUREResponse.success(AccessRule.TypeNaming.getTypeNameMap()); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index ae8378cfa..906bc0278 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -1,30 +1,17 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import jakarta.annotation.security.RolesAllowed; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; -import java.util.HashSet; import java.util.List; -import java.util.Set; -import java.util.UUID; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -34,103 +21,60 @@ *
Note: Users with admin level access can view roles, but only super admin users can modify them.

*/ @Api -@Path("/role") -public class RoleController extends BaseEntityService { +@Controller +@RequestMapping(value = "/role") +public class RoleController { - Logger logger = LoggerFactory.getLogger(RoleController.class); + private final RoleService roleService; - @Context - SecurityContext securityContext; - - @Inject - RoleRepository roleRepo; - - @Inject - PrivilegeRepository privilegeRepo; - - public RoleController() { - super(Role.class); + @Autowired + public RoleController(RoleService roleService) { + this.roleService = roleService; } @ApiOperation(value = "GET information of one Role with the UUID, requires ADMIN or SUPER_ADMIN role") - @GET - @Path("/{roleId}") @RolesAllowed({ADMIN, SUPER_ADMIN}) + @GetMapping(produces = "application/json", path = "/{roleId}") public ResponseEntity getRoleById( @ApiParam(value="The UUID of the Role to fetch information about") - @PathParam("roleId") String roleId) { - return getEntityById(roleId,roleRepo); + @PathVariable("roleId") String roleId) { + return this.roleService.getEntityById(roleId); } @ApiOperation(value = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") - @GET - @Path("") + @GetMapping(produces = "application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) public ResponseEntity getRoleAll() { - return getEntityAll(roleRepo); + return this.roleService.getEntityAll(); } @ApiOperation(value = "POST a list of Roles, requires SUPER_ADMIN role") - @Transactional - @POST @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PostMapping(produces = "application/json") public ResponseEntity addRole( @ApiParam(required = true, value = "A list of Roles in JSON format") List roles){ - checkPrivilegeAssociation(roles); - return addEntity(roles, roleRepo); + return this.roleService.addEntity(roles); } @ApiOperation(value = "Update a list of Roles, will only update the fields listed, requires SUPER_ADMIN role") - @Transactional - @PUT @RolesAllowed({SUPER_ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PutMapping(produces = "application/json") public ResponseEntity updateRole( @ApiParam(required = true, value = "A list of Roles with fields to be updated in JSON format") List roles){ - checkPrivilegeAssociation(roles); - return updateEntity(roles, roleRepo); + return this.roleService.updateEntity(roles); } @ApiOperation(value = "DELETE an Role by Id only if the Role is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE @RolesAllowed({SUPER_ADMIN}) - @Path("/{roleId}") + @DeleteMapping(produces = "application/json", path = "/{roleId}") public ResponseEntity removeById( @ApiParam(required = true, value = "A valid Role Id") - @PathParam("roleId") final String roleId) { - Role role = roleRepo.getById(UUID.fromString(roleId)); - if (JAXRSConfiguration.defaultAdminRoleName.equals(role.getName())){ - logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) - + ", is trying to remove the default system role: " + JAXRSConfiguration.defaultAdminRoleName); - return PICSUREResponse.protocolError("Default System Role cannot be removed - uuid: " + role.getUuid().toString() - + ", name: " + role.getName()); - } - return removeEntityById(roleId, roleRepo); + @PathVariable("roleId") final String roleId) { + return this.roleService.removeEntityById(roleId); } - /** - * check if the privileges under role is in the database or not, - * then retrieve it from database and attach it to role object - * - * @param roles - * @return - */ - private void checkPrivilegeAssociation(List roles){ - for (Role role: roles){ - if (role.getPrivileges() != null) { - Set privileges = new HashSet<>(); - role.getPrivileges().stream().forEach(p -> privilegeRepo.addObjectToSet(privileges, privilegeRepo, p)); - role.setPrivileges(privileges); - } - } - - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java index f4125a8e8..577ecbd7e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java @@ -1,69 +1,65 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; - -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.SecurityContext; +import jakarta.annotation.security.RolesAllowed; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; /** *

Endpoint for creating and updating terms of service entities. Records when a user accepts a term of service.

*/ -@Path("tos") -public class TermsOfSerivceController extends BaseEntityService { +@Controller +@RequestMapping("/tos") +public class TermsOfSerivceController { - public TermsOfSerivceController() { - super(TermsOfService.class); - } + private final TOSService tosService; - @Inject - TOSService tosService; + @Autowired + public TermsOfSerivceController(TOSService tosService) { + this.tosService = tosService; + } @ApiOperation(value = "GET the latest Terms of Service") - @Path("/latest") - @GET - @Produces("text/html") + @GetMapping(path = "/latest", produces = "text/html") public ResponseEntity getLatestTermsOfService(){ - return Response.ok(tosService.getLatest()).build(); + return PICSUREResponse.success(tosService.getLatest()); } @ApiOperation(value = "Update the Terms of Service html body") - @POST @RolesAllowed({AuthNaming.AuthRoleNaming.ADMIN, SUPER_ADMIN}) - @Consumes("text/html") - @Produces("application/json") + @PostMapping(path = "/update", consumes = "text/html", produces = "application/json") public ResponseEntity updateTermsOfService( @ApiParam(required = true, value = "A html page for updating") String html){ - return Response.status(201).entity(tosService.updateTermsOfService(html)).build(); + return PICSUREResponse.success(tosService.updateTermsOfService(html)); } @ApiOperation(value = "GET if current user has acceptted his TOS or not") - @Path("/") - @GET - @Produces("text/plain") - public ResponseEntity hasUserAcceptedTOS(@Context SecurityContext securityContext){ - String userSubject = securityContext.getUserPrincipal().getName(); - return Response.ok(tosService.hasUserAcceptedLatest(userSubject)).build(); + @GetMapping(path = "/", produces = "text/plain") + public ResponseEntity hasUserAcceptedTOS(){ + SecurityContext context = SecurityContextHolder.getContext(); + String userSubject = context.getAuthentication().getName(); + return PICSUREResponse.success(tosService.hasUserAcceptedLatest(userSubject)); } @ApiOperation(value = "Endpoint for current user to accept his terms of service") - @Path("/accept") - @POST - @Produces("application/json") - public ResponseEntity acceptTermsOfService(@Context SecurityContext securityContext){ - String userSubject = securityContext.getUserPrincipal().getName(); + @PostMapping(path = "/accept", produces = "application/json") + public ResponseEntity acceptTermsOfService(){ + SecurityContext context = SecurityContextHolder.getContext(); + String userSubject = context.getAuthentication().getName(); tosService.acceptTermsOfService(userSubject); - return Response.ok().build(); + return PICSUREResponse.success(); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index f3da366a1..fdff23928 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -1,36 +1,18 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; -import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.TokenService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.context.SecurityContext; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; -import java.security.Principal; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.stream.Collectors; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; +import java.util.Map; /** *

Token introspection endpoint called by an application to validate a user's token and permissions by request.

@@ -45,275 +27,26 @@ @Controller("/token") public class TokenController { - private Logger logger = LoggerFactory.getLogger(TokenController.class); - - private final UserRepository userRepo; - - private final AuthorizationService authorizationService; - - @Context - SecurityContext securityContext; + private final TokenService tokenService; @Autowired - public TokenController(UserRepository userRepo, AuthorizationService authorizationService) { - this.userRepo = userRepo; - this.authorizationService = authorizationService; + public TokenController(TokenService tokenService) { + this.tokenService = tokenService; } @ApiOperation(value = "Token introspection endpoint for user to retrieve a valid token") - @PostMapping("/inspect") - @Consumes("application/json") + @PostMapping(path = "/inspect", produces = "application/json") public ResponseEntity inspectToken( @ApiParam(required = true, value = "A JSON object that at least" + " include a user the token for validation") Map inputMap) { - logger.info("TokenInspect starting..."); - TokenInspection tokenInspection = _inspectToken(inputMap); - if (tokenInspection.message != null) - tokenInspection.responseMap.put("message", tokenInspection.message); - - logger.info("Finished token introspection."); - return PICSUREResponse.success(tokenInspection.responseMap); + return this.tokenService.inspectToken(inputMap); } - /** - * This endpoint currently is only for a user token to be refreshed. - * Application token won't work here. - * - * @return - */ @ApiOperation(value = "To refresh current user's token if the user is an active user") - @GET - @Path("/refresh") + @GetMapping(path = "/refresh", produces = "application/json") public ResponseEntity refreshToken(@RequestHeader("Authorization") String authorizationHeader) { - logger.debug("RefreshToken starting..."); - - // still need to check if the user is in the database or not, - // just in case something changes in the middle - Principal principal = securityContext.getUserPrincipal(); - if (!(principal instanceof User)) { - logger.error("refreshToken() Security context didn't have a user stored."); - } - User user = (User) principal; - - if (user.getUuid() == null) { - logger.error("refreshToken() Stored user doesn't have a uuid."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - user = userRepo.getById(user.getUuid()); - if (user == null) { - logger.error("refreshToken() When retrieving current user, it returned null, the user might be removed from database"); - throw new NotAuthorizedException("User doesn't exist anymore"); - } - - if (!user.isActive()) { - logger.error("refreshToken() The user has just been deactivated."); - throw new NotAuthorizedException("User has been deactivated."); - } - - String subject = user.getSubject(); - if (subject == null || subject.isEmpty()) { - logger.error("refreshToken() subject doesn't exist in the user."); - } - - // parse origin token - Jws jws; - try { - String token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader).orElseThrow(() -> new NotAuthorizedException("Token not found")); - jws = parseToken(token); - - } catch (NotAuthorizedException ex) { - return PICSUREResponse.protocolError("Cannot parse original token"); - } - - Claims claims = jws.getBody(); - - // just check if the subject is along with the database record, - // just in case something has changed in middle - if (!subject.equals(claims.getSubject())) { - logger.error("refreshToken() user subject is not the same as the subject of the input token"); - return PICSUREResponse.applicationError("Inner application error, try again or contact admin."); - } - - Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); - String refreshedToken = JWTUtil.createJwtToken(JAXRSConfiguration.clientSecret, - claims.getId(), - claims.getIssuer(), - claims, - subject, - JAXRSConfiguration.tokenExpirationTime); - - logger.debug("Finished RefreshToken and new token has been generated."); - return PICSUREResponse.success(Map.of( - "token", refreshedToken, - "expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString() - )); - } - - - /** - * @param inputMap - * @return - */ - private TokenInspection _inspectToken(Map inputMap) { - logger.debug("_inspectToken, the incoming token map is: {}", inputMap.entrySet() - .stream() - .map(entry -> entry.getKey() + " - " + entry.getValue()) - .collect(Collectors.joining(", "))); - - TokenInspection tokenInspection = new TokenInspection(); - - String token = (String) inputMap.get("token"); - if (token == null || token.isEmpty()) { - logger.error("Token - " + token + " is blank"); - tokenInspection.message = "Token not found"; - return tokenInspection; - } - - // parse the token based on client secret - // don't need to check if jws is null or not, since parse function has already checked - Jws jws; - try { - jws = parseToken(token); - - /* - * token has been verified, now we remove it from inputMap, so further logs will not be able to log - * the token accidentally! - */ - inputMap.remove("token"); - } catch (NotAuthorizedException ex) { - // only when the token is for sure invalid, we can dump it into the log. - logger.error("_inspectToken() the token - " + token + " - is invalid with exception: " + ex.getMessage()); - tokenInspection.message = ex.getMessage(); - return tokenInspection; - } - - - Application application; - - try { - application = (Application) securityContext.getUserPrincipal(); - } catch (ClassCastException ex) { - logger.error(securityContext.getUserPrincipal().getName() - + " - " + securityContext.getUserPrincipal().getClass().getSimpleName() + - " - is trying to use token introspection endpoint" + - ", but it is not an application"); - throw new ApplicationException("The application token does not associate with an application but " - + securityContext.getUserPrincipal().getClass().getSimpleName()); - } - - // application null check should be finished when application token goes through the JWTFilter authentication process, - // here we just double check it to prevent a null application object goes further. - if (application == null) { - logger.error("_inspectToken() There is no application in securityContext, which shall not be."); - throw new ApplicationException("Inner application error, please ask admin to check the log."); - } - - String subject = jws.getBody().getSubject(); - - // get the user based on subject field in token - User user; - - // check if the token is the special LONG_TERM_TOKEN, - // the differences between this special token and normal token is - // one user only has one long_term_token stored in database, - // this token needs to be exactly the same as the database one. - // If the token refreshed, the old one will be invalid. But normal - // token will not invalid the old ones if refreshed. - boolean isLongTermToken = false; - if (subject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { - subject = subject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); - isLongTermToken = true; - } - - user = userRepo.getUniqueResultByColumn("subject", subject); - logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); - if (user == null) { - logger.error("_inspectToken() could not find user with subject " + subject); - tokenInspection.message = "user doesn't exist"; - return tokenInspection; - } - - - //Essentially we want to return jws.getBody() with an additional active: true field - //only under certain circumstances, the token will return active - boolean isAuthorizationPassed = false; - String errorMsg = null; - - // long term token needs to be the same as the token in the database user table, if - // not the token might has been compromised, which will not go through the authorization check - boolean isLongTermTokenCompromised = false; - if (isLongTermToken && !token.equals(user.getToken())) { - // in long_term_token mode, the token needs to be exactly the same as the token in user table - isLongTermTokenCompromised = true; - logger.error("_inspectToken User " + user.getUuid() + "|" + user.getSubject() - + "is sending a long term token that is not matching the record in database user table."); - errorMsg = "Cannot find matched long term token, your token might have been refreshed."; - } - - // we go through the authorization layer check only if we need to in order to improve the performance - // the logic here, if the token associated with a user, we will start the authorization check. - // If the current application has at least one privilege, the user must have one privilege associated to the application - // pass the accessRule check if there is any accessRules associated with. - if (application.getPrivileges() == null || application.getPrivileges().isEmpty()) { - // if no privileges associated - isAuthorizationPassed = true; - //we still want to log this, though. - logger.info("ACCESS_LOG ___ " + user.getUuid() + "," + user.getEmail() + "," + user.getName() + - " ___ has been granted access to execute query ___ " + inputMap.get("request") + " ___ in application ___ " + application.getName() - + " ___ NO APP PRIVILEGES DEFINED"); - } else if (!isLongTermTokenCompromised - && user.getRoles() != null - // The protocol between applications and PSAMA is application will - // attach everything that needs to be verified in request field of inputMap - // besides token. So here we should attach everything in request. - && authorizationService.isAuthorized(application, inputMap.get("request"), user)) { - isAuthorizationPassed = true; - } else { - // if isLongTermTokenCompromised flag is true, - // the error message has already been set previously - if (!isLongTermTokenCompromised) - errorMsg = "User doesn't have enough privileges."; - } - - if (isAuthorizationPassed) { - tokenInspection.responseMap.put("active", true); - ArrayList roles = new ArrayList(); - for (Privilege p : user.getTotalPrivilege()) { - roles.add(p.getName()); - } - tokenInspection.responseMap.put("roles", String.join(",", roles)); - } else { - if (errorMsg != null) - tokenInspection.message = errorMsg; - return tokenInspection; - } - - tokenInspection.responseMap.putAll(jws.getBody()); - - // attach all privileges associated with the application to the responseMap - tokenInspection.responseMap.put("privileges", user.getPrivilegeNameSetByApplication(application)); - - - logger.info("_inspectToken() Successfully inspect and return response map: " - + tokenInspection.responseMap.entrySet() - .stream() - .map(entry -> entry.getKey() + " - " + entry.getValue()) - .collect(Collectors.joining(", "))); - return tokenInspection; - } - - /** - * inner used token introspection class with active:false included - */ - private class TokenInspection { - Map responseMap = new HashMap<>(); - String message = null; - - public TokenInspection() { - responseMap.put("active", false); - } + return this.tokenService.refreshToken(authorizationHeader); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 532079cf1..851120dc1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -1,18 +1,12 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.*; -import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.MailService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import edu.harvard.hms.dbmi.avillach.auth.utils.JsonUtils; @@ -21,22 +15,22 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import jakarta.annotation.security.RolesAllowed; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.*; -import javax.mail.MessagingException; import javax.transaction.Transactional; -import javax.validation.constraints.NotNull; import java.io.IOException; import java.util.*; -import java.util.stream.Collectors; -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.*; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -45,222 +39,52 @@ */ @Api @Controller("/user") -public class UserController extends BaseEntityService { +public class UserController { - Logger logger = LoggerFactory.getLogger(UserController.class); + private final static Logger logger = LoggerFactory.getLogger(UserController.class); -// @Context -// SecurityContext securityContext; + private final UserService userService; - private final RoleRepository roleRepo; - - private final ConnectionRepository connectionRepo; - - private final ApplicationRepository applicationRepo; - - private final AuthUtils authUtil; - - private MailService mailService; + private final TOSService tosService; @Autowired - public UserController(RoleRepository roleRepo, ConnectionRepository connectionRepo, ApplicationRepository applicationRepo, AuthUtils authUtil) { - super(User.class); - this.roleRepo = roleRepo; - this.connectionRepo = connectionRepo; - this.applicationRepo = applicationRepo; - this.authUtil = authUtil; + public UserController(UserService userService, TOSService tosService) { + this.userService = userService; + this.tosService = tosService; } @ApiOperation(value = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") - @GET @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("/{userId}") + @GetMapping(path = "/{userId}", produces = "application/json") public ResponseEntity getUserById( @ApiParam(required = true, value = "The UUID of the user to fetch information about") - @PathParam("userId") String userId) { - return getEntityById(userId, userRepo); + @PathVariable("userId") String userId) { + return this.userService.getEntityById(userId); } @ApiOperation(value = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") - @GET @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("") + @GetMapping(produces = "application/json") public ResponseEntity getUserAll() { - return getEntityAll(userRepo); + return this.userService.getEntityAll(); } @ApiOperation(value = "POST a list of users, requires ADMIN role") - @Transactional - @POST + @Transactional // TODO: Move this to the service layer @RolesAllowed({ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PostMapping(produces = "application/json") public ResponseEntity addUser( @ApiParam(required = true, value = "A list of user in JSON format") List users) { - User currentUser = (User) securityContext.getUserPrincipal(); - if (currentUser == null || currentUser.getUuid() == null) { - logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - checkAssociation(users); - - boolean allowAdd = true; - for (User user : users) { - logger.debug("Adding User " + user); - if (!allowUpdateSuperAdminRole(currentUser, user, null)) { - allowAdd = false; - break; - } - - if (user.getEmail() == null) { - try { - HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); - List emailKeys = metadata.keySet().stream().filter((key) -> { - return key.toLowerCase().contains("email"); - }).collect(Collectors.toList()); - if (emailKeys.size() > 0) { - user.setEmail(metadata.get(emailKeys.get(0))); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } + return this.userService.addUsers(users); - if (allowAdd) { - Response updateResponse = addEntity(users, userRepo); - sendUserUpdateEmailsFromResponse(updateResponse); - return updateResponse; - } else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " - + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); - throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); - } } @ApiOperation(value = "Update a list of users, will only update the fields listed, requires ADMIN role") - @Transactional - @PUT @RolesAllowed({ADMIN}) - @Consumes(MediaType.APPLICATION_JSON) - @Path("/") + @PutMapping(produces = "application/json") public ResponseEntity updateUser(List users) { - User currentUser = (User) securityContext.getUserPrincipal(); - if (currentUser == null || currentUser.getUuid() == null) { - logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - checkAssociation(users); - - boolean allowUpdate = true; - for (User user : users) { - - User originalUser = userRepo.getById(user.getUuid()); - if (allowUpdateSuperAdminRole(currentUser, user, originalUser)) { - continue; - } else { - allowUpdate = false; - break; - } - } - - if (allowUpdate) { - Response updateResponse = updateEntity(users, userRepo); - sendUserUpdateEmailsFromResponse(updateResponse); - return updateResponse; - } else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant or remove " - + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); - throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); - } - } - - private void sendUserUpdateEmailsFromResponse(Response updateResponse) { - logger.debug("Sending email"); - try { - Object entity = updateResponse.getEntity(); - if (entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { - PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent) entity; - List addedUsers = (List) okResponse.getContent(); - String message = okResponse.getMessage(); - for (User user : addedUsers) { - try { - mailService.sendUsersAccessEmail(user); - } catch (MessagingException e) { - logger.error("Failed to send email! " + e.getLocalizedMessage()); - logger.debug("Exception Trace: ", e); - okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); - } - } - } - } catch (Exception e) { - logger.error("Failed to send email - unhandled exception: ", e); - } - logger.debug("finished email sending method"); - } - - /** - * This check is to prevent non-super-admin user to create/remove a super admin role - * against a user(include themselves). Only super admin user could perform such actions. - * - *

- * if operations not related to super admin role updates, this will return true. - *

- *

- * The logic here is checking the state of the super admin role in the input and output users, - * if the state is changed, check if the user is a super admin to determine if the user could perform the action. - * - * @param currentUser the user trying to perform the action - * @param inputUser - * @param originalUser there could be no original user when adding a new user - * @return - */ - private boolean allowUpdateSuperAdminRole( - @NotNull User currentUser, - @NotNull User inputUser, - User originalUser) { - - // if current user is a super admin, this check will return true - for (Role role : currentUser.getRoles()) { - for (Privilege privilege : role.getPrivileges()) { - if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { - return true; - } - } - } - - boolean inputUserHasSuperAdmin = false; - boolean originalUserHasSuperAdmin = false; - - for (Role role : inputUser.getRoles()) { - for (Privilege privilege : role.getPrivileges()) { - if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { - inputUserHasSuperAdmin = true; - break; - } - } - } - - if (originalUser != null) { - for (Role role : originalUser.getRoles()) { - for (Privilege privilege : role.getPrivileges()) { - if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { - originalUserHasSuperAdmin = true; - break; - } - } - } - - // when they equals, nothing has changed, a non super admin user could perform the action - return inputUserHasSuperAdmin == originalUserHasSuperAdmin; - } else { - // if inputUser has super admin, it should return false - return !inputUserHasSuperAdmin; - } - + return this.userService.updateUser(users); } /** @@ -268,19 +92,18 @@ private boolean allowUpdateSuperAdminRole( * every time a user hit this endpoint /me with the query parameter ?hasToken presented, * it will refresh the long term token. * - * @param httpHeaders * @param hasToken * @return */ @ApiOperation(value = "Retrieve information of current user") - @Transactional - @GET - @Path("/me") + @Transactional // TODO: Move this to the service layer + @GetMapping(produces = "application/json", path = "/me") public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, @ApiParam(required = false, value = "Attribute that represents if a long term token will attach to the response") - @QueryParam("hasToken") Boolean hasToken) { - User user = (User) securityContext.getUserPrincipal(); + @RequestParam("hasToken") Boolean hasToken) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + User user = (User) securityContext.getAuthentication().getPrincipal(); if (user == null || user.getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); @@ -296,7 +119,7 @@ public ResponseEntity getCurrentUser( .setEmail(user.getEmail()) .setPrivileges(user.getPrivilegeNameSet()) .setUuid(user.getUuid().toString()) - .setAcceptedTOS(authUtil.acceptedTOSBySub(user.getSubject())); + .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.getSubject())); // currently, the queryScopes are simple combination of queryScope string together as a set. // We are expecting the queryScope string as plain string. If it is a JSON, we could change the @@ -494,28 +317,7 @@ private String generateUserLongTermToken(String authorizationHeader) { } - /** - * check all referenced field if they are already in database. If - * they are in database, then retrieve it by id, and attach it to - * user object. - * - * @param users - * @return - */ - private void checkAssociation(List users) { - for (User user : users) { - if (user.getRoles() != null) { - Set roles = new HashSet<>(); - user.getRoles().stream().forEach(t -> roleRepo.addObjectToSet(roles, roleRepo, t)); - user.setRoles(roles); - } - if (user.getConnection() != null) { - Connection connection = connectionRepo.getUniqueResultByColumn("id", user.getConnection().getId()); - user.setConnection(connection); - } - } - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java new file mode 100644 index 000000000..a531e0f6f --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -0,0 +1,96 @@ +package edu.harvard.hms.dbmi.avillach.auth.service; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +@Service +public class RoleService extends BaseEntityService { + + private final static Logger logger = Logger.getLogger(RoleService.class.getName()); + private final RoleRepository roleRepository; + + private final PrivilegeRepository privilegeRepo; + + @Autowired + protected RoleService(Class type, RoleRepository roleRepository, PrivilegeRepository privilegeRepo) { + super(type); + this.roleRepository = roleRepository; + this.privilegeRepo = privilegeRepo; + } + + public ResponseEntity getEntityById(String roleId) { + return getEntityById(roleId, roleRepository); + } + + + public ResponseEntity getEntityAll() { + return getEntityAll(roleRepository); + } + + @Transactional + public ResponseEntity addEntity(List roles) { + checkPrivilegeAssociation(roles); + return addEntity(roles, roleRepository); + } + + /** + * check if the privileges under role is in the database or not, + * then retrieve it from database and attach it to role object + * + * @param roles list of roles + */ + private void checkPrivilegeAssociation(List roles) throws RuntimeException { + for (Role role: roles){ + if (role.getPrivileges() != null) { + Set privileges = new HashSet<>(); // TODO: Determine how we can fix this issue. The javax code does not work with java 21 in this case. + role.getPrivileges().stream().forEach(p -> privilegeRepo.addObjectToSet(privileges, privilegeRepo, p)); + role.setPrivileges(privileges); + } + } + + } + + @Transactional + public ResponseEntity updateEntity(List roles) { + checkPrivilegeAssociation(roles); + return updateEntity(roles, roleRepository); + } + + @Transactional + public ResponseEntity removeEntityById(String roleId) { + Role role = roleRepository.getById(UUID.fromString(roleId)); + + // Get principal roles from security context + SecurityContext context = SecurityContextHolder.getContext(); + Set roles = context.getAuthentication().getAuthorities().stream() + .map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + + + if (SecurityRoles.contains(roles, SecurityRoles.PIC_SURE_TOP_ADMIN.getRole())){ + logger.info("User has PIC-SURE Top Admin role, can remove any role"); + return PICSUREResponse.protocolError("Default System Role cannot be removed - uuid: " + role.getUuid().toString() + + ", name: " + role.getName()); + } + return removeEntityById(roleId, roleRepository); + } +} + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java new file mode 100644 index 000000000..625fc574b --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java @@ -0,0 +1,58 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; + +import javax.transaction.Transactional; +import java.util.List; + +@Service +public class AccessRuleService extends BaseEntityService{ + + private final AccessRuleRepository accessRuleRepo; + + @Autowired + protected AccessRuleService(Class type, AccessRuleRepository accessRuleRepo) { + super(type); + this.accessRuleRepo = accessRuleRepo; + } + + + public ResponseEntity getEntityById(String accessRuleId) { + return getEntityById(accessRuleId, accessRuleRepo); + } + + public ResponseEntity getEntityAll() { + return getEntityAll(accessRuleRepo); + } + + public ResponseEntity addEntity(List accessRules) { + accessRules.forEach(accessRule -> { + if (accessRule.getEvaluateOnlyByGates() == null) + accessRule.setEvaluateOnlyByGates(false); + + if (accessRule.getCheckMapKeyOnly() == null) + accessRule.setCheckMapKeyOnly(false); + + if (accessRule.getCheckMapNode() == null) + accessRule.setCheckMapNode(false); + + if (accessRule.getGateAnyRelation() == null) + accessRule.setGateAnyRelation(false); + }); + + return addEntity(accessRules, accessRuleRepo); + } + + public ResponseEntity updateEntity(List accessRules) { + return updateEntity(accessRules, accessRuleRepo); + } + + @Transactional + public ResponseEntity removeEntityById(String accessRuleId) { + return removeEntityById(accessRuleId, accessRuleRepo); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index f2a2dbebc..7e60a5971 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -15,7 +15,6 @@ import com.fasterxml.jackson.databind.JsonNode; import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java index 4daaef8f4..d9f10d0ef 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java @@ -28,8 +28,6 @@ public abstract class BaseEntityService { private final String auditLogName; -// @Context -// SecurityContext securityContext; protected BaseEntityService(Class type){ this.type = type; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java new file mode 100644 index 000000000..3efaa981c --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -0,0 +1,292 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.model.TokenInspection; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.stream.Collectors; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; + +@Service +public class TokenService { + + private final static Logger logger = LoggerFactory.getLogger(TokenService.class); + + private final AuthorizationService authorizationService; + + private final UserRepository userRepository; + + private final long tokenExpirationTime; + private final String clientSecret; + + private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour TODO: Move to a global configuration or enum? + + @Autowired + public TokenService(AuthorizationService authorizationService, UserRepository userRepository, + @Value("${application.client.secret}") String clientSecret, + @Value("${application.token.expiration.time}") long tokenExpirationTime) { + this.authorizationService = authorizationService; + this.userRepository = userRepository; + this.clientSecret = clientSecret; + this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; + } + + public ResponseEntity inspectToken(Map inputMap) { + logger.info("TokenInspect starting..."); + TokenInspection tokenInspection; + try { + tokenInspection = _inspectToken(inputMap); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + if (tokenInspection.getMessage() != null) { + tokenInspection.addField("message", tokenInspection.getMessage()); + } + + logger.info("Finished token introspection."); + return PICSUREResponse.success(tokenInspection.getResponseMap()); + } + + private TokenInspection _inspectToken(Map inputMap) throws IllegalAccessException { + logger.debug("_inspectToken, the incoming token map is: {}", inputMap.entrySet() + .stream() + .map(entry -> entry.getKey() + " - " + entry.getValue()) + .collect(Collectors.joining(", "))); + + TokenInspection tokenInspection = new TokenInspection(); + String token = (String) inputMap.get("token"); + if (token == null || token.isEmpty()) { + logger.error("Token - " + token + " is blank"); + tokenInspection.setMessage("Token not found"); + return tokenInspection; + } + + // parse the token based on client secret + // don't need to check if jws is null or not, since parse function has already checked + Jws jws; + try { + jws = JWTUtil.parseToken(token); + + /* + * token has been verified, now we remove it from inputMap, so further logs will not be able to log + * the token accidentally! + */ + inputMap.remove("token"); + } catch (NotAuthorizedException ex) { + // only when the token is for sure invalid, we can dump it into the log. + logger.error("_inspectToken() the token - " + token + " - is invalid with exception: " + ex.getMessage()); + tokenInspection.setMessage(ex.getMessage()); + return tokenInspection; + } + + + Application application; + try { + application = (Application) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + } catch (ClassCastException ex) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + String principalName = securityContext.getAuthentication().getName(); + logger.error(principalName + + " - " + principalName + + " - is trying to use token introspection endpoint" + + ", but it is not an application"); + throw new IllegalAccessException("The application token does not associate with an application but " + + principalName); + } + + // application null check should be finished when application token goes through the JWTFilter authentication process, + // here we just double check it to prevent a null application object goes further. + if (application == null) { + logger.error("_inspectToken() There is no application in securityContext, which shall not be."); + throw new NullPointerException("Inner application error, please ask admin to check the log."); + } + + String subject = jws.getBody().getSubject(); + + // get the user based on subject field in token + User user; + + // check if the token is the special LONG_TERM_TOKEN, + // the differences between this special token and normal token is + // one user only has one long_term_token stored in database, + // this token needs to be exactly the same as the database one. + // If the token refreshed, the old one will be invalid. But normal + // token will not invalid the old ones if refreshed. + boolean isLongTermToken = false; + if (subject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { + subject = subject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); + isLongTermToken = true; + } + + user = this.userRepository.getUniqueResultByColumn("subject", subject); + logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); + if (user == null) { + logger.error("_inspectToken() could not find user with subject " + subject); + tokenInspection.setMessage("user doesn't exist"); + return tokenInspection; + } + + + //Essentially we want to return jws.getBody() with an additional active: true field + //only under certain circumstances, the token will return active + boolean isAuthorizationPassed = false; + String errorMsg = null; + + // long term token needs to be the same as the token in the database user table, if + // not the token might has been compromised, which will not go through the authorization check + boolean isLongTermTokenCompromised = false; + if (isLongTermToken && !token.equals(user.getToken())) { + // in long_term_token mode, the token needs to be exactly the same as the token in user table + isLongTermTokenCompromised = true; + logger.error("_inspectToken User " + user.getUuid() + "|" + user.getSubject() + + "is sending a long term token that is not matching the record in database user table."); + errorMsg = "Cannot find matched long term token, your token might have been refreshed."; + } + + // we go through the authorization layer check only if we need to in order to improve the performance + // the logic here, if the token associated with a user, we will start the authorization check. + // If the current application has at least one privilege, the user must have one privilege associated to the application + // pass the accessRule check if there is any accessRules associated with. + if (application.getPrivileges() == null || application.getPrivileges().isEmpty()) { + // if no privileges associated + isAuthorizationPassed = true; + //we still want to log this, though. + logger.info("ACCESS_LOG ___ " + user.getUuid() + "," + user.getEmail() + "," + user.getName() + + " ___ has been granted access to execute query ___ " + inputMap.get("request") + " ___ in application ___ " + application.getName() + + " ___ NO APP PRIVILEGES DEFINED"); + } else if (!isLongTermTokenCompromised + && user.getRoles() != null + // The protocol between applications and PSAMA is application will + // attach everything that needs to be verified in request field of inputMap + // besides token. So here we should attach everything in request. + && authorizationService.isAuthorized(application, inputMap.get("request"), user)) { + isAuthorizationPassed = true; + } else { + // if isLongTermTokenCompromised flag is true, + // the error message has already been set previously + if (!isLongTermTokenCompromised) + errorMsg = "User doesn't have enough privileges."; + } + + if (isAuthorizationPassed) { + tokenInspection.addField("active", true); + ArrayList roles = new ArrayList(); + for (Privilege p : user.getTotalPrivilege()) { + roles.add(p.getName()); + } + tokenInspection.addField("roles", String.join(",", roles)); + } else { + tokenInspection.setMessage(errorMsg); + return tokenInspection; + } + + tokenInspection.addAllFields(jws.getBody()); + + // attach all privileges associated with the application to the responseMap + tokenInspection.addField("privileges", user.getPrivilegeNameSetByApplication(application)); + + + logger.info("_inspectToken() Successfully inspect and return response map: " + + tokenInspection.getResponseMap().entrySet() + .stream() + .map(entry -> entry.getKey() + " - " + entry.getValue()) + .collect(Collectors.joining(", "))); + return tokenInspection; + } + + public ResponseEntity refreshToken(String authorizationHeader) { + logger.debug("RefreshToken starting..."); + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Object principal = authentication.getPrincipal(); + if (!(principal instanceof User)) { + logger.error("refreshToken() Security context didn't have a user stored."); + } + + if (!(principal instanceof User user)) { + logger.error("refreshToken() Principal is not an instance of User."); + throw new NotAuthorizedException("User not found"); + } + + if (user.getUuid() == null) { + logger.error("refreshToken() Stored user doesn't have a uuid."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + user = this.userRepository.getById(user.getUuid()); + if (user == null) { + logger.error("refreshToken() When retrieving current user, it returned null, the user might be removed from database"); + throw new NotAuthorizedException("User doesn't exist anymore"); + } + + if (!user.isActive()) { + logger.error("refreshToken() The user has just been deactivated."); + throw new NotAuthorizedException("User has been deactivated."); + } + + String subject = user.getSubject(); + if (subject == null || subject.isEmpty()) { + logger.error("refreshToken() subject doesn't exist in the user."); + } + + // parse origin token + Jws jws; + try { + String token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader).orElseThrow(() -> new NotAuthorizedException("Token not found")); + jws = parseToken(token); + + } catch (NotAuthorizedException ex) { + return PICSUREResponse.protocolError("Cannot parse original token"); + } + + Claims claims = jws.getBody(); + + // just check if the subject is along with the database record, + // just in case something has changed in middle + if (StringUtils.isNotBlank(subject) && !subject.equals(claims.getSubject())) { + logger.error("refreshToken() user subject is not the same as the subject of the input token"); + return PICSUREResponse.applicationError("Inner application error, try again or contact admin."); + } + + Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + this.tokenExpirationTime); + String refreshedToken = JWTUtil.createJwtToken(this.clientSecret, + claims.getId(), + claims.getIssuer(), + claims, + subject, + this.tokenExpirationTime); + + logger.debug("Finished RefreshToken and new token has been generated."); + return PICSUREResponse.success(Map.of( + "token", refreshedToken, + "expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString() + )); + } + + +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 4edc5afef..ad667be2d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -1,28 +1,62 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.harvard.dbmi.avillach.data.repository.BaseRepository; +import edu.harvard.dbmi.avillach.util.exception.ProtocolException; +import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import javax.mail.MessagingException; +import javax.transaction.Transactional; +import javax.validation.constraints.NotNull; +import java.io.IOException; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; +import java.util.*; +import java.util.stream.Collectors; @Service -public class UserService { +public class UserService extends BaseEntityService { - private final Logger logger = Logger.getLogger(UserService.class.getName()); + private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); private final TOSService tosService; + private final UserRepository userRepository; + private final ConnectionRepository connectionRepository; + + private final RoleRepository roleRepository; + private final String clientSecret; + + private final long tokenExpirationTime; + private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour TODO: Move to a global configuration or enum? @Autowired - public UserService(TOSService tosService) { + public UserService(TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, + @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime) { + super(User.class); this.tosService = tosService; + this.userRepository = userRepository; + this.connectionRepository = connectionRepository; + this.roleRepository = roleRepository; + this.clientSecret = clientSecret; + this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; } public HashMap getUserProfileResponse(Map claims) { @@ -34,12 +68,12 @@ public HashMap getUserProfileResponse(Map claims logger.info("getUserProfileResponse() using claims:" + claims.toString()); String token = JWTUtil.createJwtToken( - JAXRSConfiguration.clientSecret, + this.clientSecret, "whatever", "edu.harvard.hms.dbmi.psama", claims, claims.get("sub").toString(), - JAXRSConfiguration.tokenExpirationTime + this.tokenExpirationTime ); logger.info("getUserProfileResponse() PSAMA JWT token has been generated. Token:" + token); responseMap.put("token", token); @@ -57,11 +91,204 @@ public HashMap getUserProfileResponse(Map claims responseMap.put("acceptedTOS", "" + acceptedTOS); logger.info("getUserProfileResponse() expirationDate is set"); - Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + JAXRSConfiguration.tokenExpirationTime); + Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + this.tokenExpirationTime); responseMap.put("expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString()); logger.info("getUserProfileResponse() finished"); return responseMap; } + public ResponseEntity getEntityById(String userId) { + return getEntityById(userId, this.userRepository); + } + + public ResponseEntity getEntityAll() { + return getEntityAll(this.userRepository); + } + + public ResponseEntity addEntity(List users) { + return addEntity(users, this.userRepository); + + } + + /** + * This check is to prevent non-super-admin user to create/remove a super admin role + * against a user(include themselves). Only super admin user could perform such actions. + * + *

+ * if operations not related to super admin role updates, this will return true. + *

+ *

+ * The logic here is checking the state of the super admin role in the input and output users, + * if the state is changed, check if the user is a super admin to determine if the user could perform the action. + * + * @param currentUser the user trying to perform the action + * @param inputUser + * @param originalUser there could be no original user when adding a new user + * @return + */ + private boolean allowUpdateSuperAdminRole( + @NotNull User currentUser, + @NotNull User inputUser, + User originalUser) { + + // if current user is a super admin, this check will return true + for (Role role : currentUser.getRoles()) { + for (Privilege privilege : role.getPrivileges()) { + if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { + return true; + } + } + } + + boolean inputUserHasSuperAdmin = false; + boolean originalUserHasSuperAdmin = false; + + for (Role role : inputUser.getRoles()) { + for (Privilege privilege : role.getPrivileges()) { + if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { + inputUserHasSuperAdmin = true; + break; + } + } + } + + if (originalUser != null) { + for (Role role : originalUser.getRoles()) { + for (Privilege privilege : role.getPrivileges()) { + if (privilege.getName().equals(AuthNaming.AuthRoleNaming.SUPER_ADMIN)) { + originalUserHasSuperAdmin = true; + break; + } + } + } + + // when they equals, nothing has changed, a non super admin user could perform the action + return inputUserHasSuperAdmin == originalUserHasSuperAdmin; + } else { + // if inputUser has super admin, it should return false + return !inputUserHasSuperAdmin; + } + + } + + public ResponseEntity addUsers(List users) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + User currentUser = (User) securityContext.getAuthentication().getPrincipal(); + if (currentUser == null || currentUser.getUuid() == null) { + logger.error("Security context didn't have a user stored."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + checkAssociation(users); + boolean allowAdd = true; + for (User user : users) { + logger.debug("Adding User " + user); + if (!allowUpdateSuperAdminRole(currentUser, user, null)) { // TODO: The allowUpdateSuperAdminRole is a private method + allowAdd = false; + break; + } + + if (user.getEmail() == null) { + try { + HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); + List emailKeys = metadata.keySet().stream().filter((key) -> { + return key.toLowerCase().contains("email"); + }).collect(Collectors.toList()); + if (emailKeys.size() > 0) { + user.setEmail(metadata.get(emailKeys.get(0))); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + if (allowAdd) { + ResponseEntity updateResponse = addEntity(users); + sendUserUpdateEmailsFromResponse(updateResponse); + return updateResponse; + } else { + logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " + + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); + throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); + } + } + + /** + * check all referenced field if they are already in database. If + * they are in database, then retrieve it by id, and attach it to + * user object. + * + * @param users A list of users + */ + private void checkAssociation(List users) { + for (User user : users) { + if (user.getRoles() != null) { + Set roles = new HashSet<>(); + user.getRoles().forEach(t -> this.roleRepository.addObjectToSet(roles, this.roleRepository, t)); // TODO: We need to fix the exception that is thrown here + user.setRoles(roles); + } + + if (user.getConnection() != null) { + Connection connection = this.connectionRepository.getUniqueResultByColumn("id", user.getConnection().getId()); + user.setConnection(connection); + } + } + } + + @Transactional // TODO: Can this be moved further down the call hierarchy to improve performance? + public ResponseEntity updateUser(List users) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + User currentUser = (User) securityContext.getAuthentication().getPrincipal(); + if (currentUser == null || currentUser.getUuid() == null) { + logger.error("Security context didn't have a user stored."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + checkAssociation(users); + boolean allowUpdate = true; + for (User user : users) { + + User originalUser = this.userRepository.getById(user.getUuid()); + if (!allowUpdateSuperAdminRole(currentUser, user, originalUser)) { + allowUpdate = false; + break; + } + } + + if (allowUpdate) { + ResponseEntity updateResponse = updateEntity(users, this.userRepository); + sendUserUpdateEmailsFromResponse(updateResponse); + return updateResponse; + } else { + logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant or remove " + + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); + throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); + } + } + + private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) { + logger.debug("Sending email"); + try { + Object entity = updateResponse.getEntity(); // TODO: Determine how to replicate this given the new approach + if (entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { + PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent) entity; + List addedUsers = (List) okResponse.getContent(); + String message = okResponse.getMessage(); + for (User user : addedUsers) { + try { + mailService.sendUsersAccessEmail(user); + } catch (MessagingException e) { + logger.error("Failed to send email! " + e.getLocalizedMessage()); + logger.debug("Exception Trace: ", e); + okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); + } + } + } + } catch (Exception e) { + logger.error("Failed to send email - unhandled exception: ", e); + } + logger.debug("finished email sending method"); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 80f979add..ee7683780 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -9,7 +9,6 @@ import org.springframework.beans.factory.annotation.Value; import javax.crypto.spec.SecretKeySpec; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Date; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java index f8d2fa5d5..a9a6020dc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java @@ -1,21 +1,11 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.type.MapType; import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.*; import javax.validation.constraints.NotNull; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.stream.Collectors; From aa8112e21b217d5db15dcb96e73002e0b784afdb Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 26 Mar 2024 11:03:15 -0400 Subject: [PATCH 009/222] WIP: Refactored UserController and UserMetadataMappingWebController Both classes have been refactored to separate out the controller and service. --- .../avillach/auth/rest/UserController.java | 204 +------------- .../UserMetadataMappingWebController.java | 75 ++--- .../impl/UserMetadataMappingService.java | 14 + .../auth/service/impl/UserService.java | 262 +++++++++++++++--- .../avillach/Auth0MatchingServiceTest.java | 2 +- 5 files changed, 284 insertions(+), 273 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 851120dc1..ae44f612b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import edu.harvard.dbmi.avillach.util.exception.ApplicationException; import edu.harvard.dbmi.avillach.util.exception.ProtocolException; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.entity.*; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; @@ -102,141 +102,22 @@ public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, @ApiParam(required = false, value = "Attribute that represents if a long term token will attach to the response") @RequestParam("hasToken") Boolean hasToken) { - SecurityContext securityContext = SecurityContextHolder.getContext(); - User user = (User) securityContext.getAuthentication().getPrincipal(); - if (user == null || user.getUuid() == null) { - logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - user = userRepo.getById(user.getUuid()); - if (user == null) { - logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - User.UserForDisplay userForDisplay = new User.UserForDisplay() - .setEmail(user.getEmail()) - .setPrivileges(user.getPrivilegeNameSet()) - .setUuid(user.getUuid().toString()) - .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.getSubject())); - - // currently, the queryScopes are simple combination of queryScope string together as a set. - // We are expecting the queryScope string as plain string. If it is a JSON, we could change the - // code to use JsonUtils.mergeTemplateMap(Map, Map) - Set privileges = user.getTotalPrivilege(); - if (privileges != null && !privileges.isEmpty()) { - Set scopes = new TreeSet<>(); - privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { - try { - Arrays.stream(objectMapper.readValue(privilege.getQueryScope(), String[].class)) - .filter(x -> x != null) - .forEach(scopeList -> scopes.addAll(Arrays.asList(scopeList))); - } catch (IOException e) { - logger.error("Parsing issue for privilege " + privilege.getUuid() + " queryScope", e); - } - }); - userForDisplay.setQueryScopes(scopes); - } - - if (hasToken != null) { - - if (user.getToken() != null && !user.getToken().isEmpty()) { - userForDisplay.setToken(user.getToken()); - } else { - user.setToken(generateUserLongTermToken(authorizationHeader)); - userRepo.merge(user); - userForDisplay.setToken(user.getToken()); - } - } - - return PICSUREResponse.success(userForDisplay); + return this.userService.getCurrentUser(authorizationHeader, hasToken); } @ApiOperation(value = "Retrieve the queryTemplate of certain application by given application Id for the currentUser ") - @Transactional - @GET - @Path("/me/queryTemplate/{applicationId}") + @Transactional // TODO: Move this to the service layer + @GetMapping(path = "/me/queryTemplate/{applicationId}", produces = "application/json") public ResponseEntity getQueryTemplate( @ApiParam(value = "Application Id for the returning queryTemplate") - @PathParam("applicationId") String applicationId) { - - if (applicationId == null || applicationId.trim().isEmpty()) { - logger.error("getQueryTemplate() input application UUID is null or empty."); - throw new ProtocolException("Input application UUID is incorrect."); - } - - User user = (User) securityContext.getUserPrincipal(); - if (user == null || user.getUuid() == null) { - logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - user = userRepo.getById(user.getUuid()); - if (user == null) { - logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - Application application = applicationRepo.getById(UUID.fromString(applicationId)); - - if (application == null) { - logger.error("getQueryTemplate() cannot find corresponding application by UUID: " + applicationId); - throw new ProtocolException("Cannot find application by input UUID: " + applicationId); - } - - return PICSUREResponse.success( - Map.of("queryTemplate", mergeTemplate(user, application))); - + @PathVariable("applicationId") String applicationId) { + return this.userService.getQueryTemplate(applicationId); } @ApiOperation(value = "Retrieve the queryTemplate of default application") - @Transactional - @GET - @Path("/me/queryTemplate") + @GetMapping(path = "/me/queryTemplate", produces = "application/json") public ResponseEntity getQueryTemplate() { - return getQueryTemplate(JAXRSConfiguration.defaultApplicationUUID); - } - - - private String mergeTemplate(User user, Application application) { - String resultJSON = null; - Map mergedTemplateMap = null; - for (Privilege privilege : user.getPrivilegesByApplication(application)) { - String template = privilege.getQueryTemplate(); - logger.debug("mergeTemplate() processing template:" + template); - if (template == null || template.trim().isEmpty()) { - continue; - } - Map templateMap = null; - try { - templateMap = objectMapper.readValue(template, Map.class); - } catch (IOException ex) { - logger.error("mergeTemplate() cannot convert stored queryTemplate using Jackson, the queryTemplate is: " + template); - throw new ApplicationException("Inner application error, please contact admin."); - } - - if (templateMap == null) { - continue; - } - - if (mergedTemplateMap == null) { - mergedTemplateMap = templateMap; - continue; - } - - mergedTemplateMap = JsonUtils.mergeTemplateMap(mergedTemplateMap, templateMap); - } - - try { - resultJSON = objectMapper.writeValueAsString(mergedTemplateMap); - } catch (JsonProcessingException ex) { - logger.error("mergeTemplate() cannot convert map to json string. The map mergedTemplate is: " + mergedTemplateMap); - throw new ApplicationException("Inner application error, please contact admin."); - } - - return resultJSON; - + return this.userService.getDefaultQueryTemplate(); } /** @@ -245,76 +126,17 @@ private String mergeTemplate(User user, Application application) { * with the query parameter ?hasToken presented, * it will refresh the long term token. * - * @param httpHeaders - * @param hasToken - * @return + * @param httpHeaders the http headers + * @return the refreshed long term token */ @ApiOperation(value = "refresh the long term tokne of current user") - @Transactional - @GET - @Path("/me/refresh_long_term_token") + @GetMapping(path = "/me/refresh_long_term_token", produces = "application/json") public ResponseEntity refreshUserToken( - @RequestHeader HttpHeaders httpHeaders, - @ApiParam(required = false, value = "A flag represents if the long term token will be returned or not") - @QueryParam("hasToken") Boolean hasToken) { - User user = (User) securityContext.getUserPrincipal(); - if (user == null || user.getUuid() == null) { - logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - user = userRepo.getById(user.getUuid()); - if (user == null) { - logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - - String longTermToken = generateUserLongTermToken(httpHeaders); - user.setToken(longTermToken); - - userRepo.merge(user); - - return PICSUREResponse.success(Map.of("userLongTermToken", longTermToken)); + @RequestHeader HttpHeaders httpHeaders) { + return this.userService.refreshUserToken(httpHeaders); } - /** - * Logic here is, retrieve the subject of the user from httpHeader. Then generate a long term one - * with LONG_TERM_TOKEN_PREFIX| in front of the subject to be able to distinguish with regular ones, since - * long term token only generated for accessing certain things to, in some degrees, decrease the insecurity. - * - * @param authorizationHeader the authorization header - * @return the long term token - * @throws IllegalArgumentException if the authorization header is not presented - */ - private String generateUserLongTermToken(String authorizationHeader) { - if (!StringUtils.isNotBlank(authorizationHeader)) { - throw new IllegalArgumentException("Authorization header is not presented."); - } - - Optional token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader); - if (token.isEmpty()) { - throw new IllegalArgumentException("Token is not presented in the authorization header."); - } - Jws jws = JWTUtil.parseToken(token.get()); - - Claims claims = jws.getBody(); - String tokenSubject = claims.getSubject(); - - if (tokenSubject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX + "|")) { - // considering the subject already contains a "|" - // to prevent infinitely adding the long term token prefix - // we will grab the real subject here - tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); - } - - return JWTUtil.createJwtToken(clientSecret, - claims.getId(), - claims.getIssuer(), - claims, - AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + tokenSubject, - longTermTokenExpirationTime); - } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index 6c05afaf9..b2f692431 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -1,21 +1,17 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import jakarta.annotation.security.RolesAllowed; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; -import javax.annotation.security.RolesAllowed; -import javax.inject.Inject; -import javax.transaction.Transactional; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.util.List; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -26,73 +22,56 @@ *

*/ @Api -@Path("mapping") -public class UserMetadataMappingWebController extends BaseEntityService{ - - public UserMetadataMappingWebController() { - super(UserMetadataMapping.class); - } - - @Inject - UserMetadataMappingService mappingService; +@Controller +@RequestMapping("/mapping") +public class UserMetadataMappingWebController { - @Inject - UserMetadataMappingRepository mappingRepo; + private final UserMetadataMappingService mappingService; - @Inject - ConnectionRepository connectionRepo; + @Autowired + public UserMetadataMappingWebController(UserMetadataMappingService mappingService) { + this.mappingService = mappingService; + } - @ApiOperation(value = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") - @GET - @Produces("application/json") + @ApiOperation(value = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) - @Path("{connectionId}") - public ResponseEntity getMappingsForConnection(@PathParam("connectionId") String connection) { - return Response.ok(mappingService. - getAllMappingsForConnection(connectionRepo - .getUniqueResultByColumn("id", connection))) - .build(); + @GetMapping(path = "{connectionId}", produces = "application/json") + public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { + return this.mappingService.getAllMappingsForConnection(connection); } @ApiOperation(value = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") - @GET - @Produces("application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) + @GetMapping(path = "/", produces = "application/json") public ResponseEntity getAllMappings() { - return Response.ok(mappingService.getAllMappings()).build(); + List allMappings = mappingService.getAllMappings(); + return PICSUREResponse.success(allMappings); } @ApiOperation(value = "POST a list of UserMetadataMappings, requires SUPER_ADMIN role") - @Transactional - @POST - @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed({SUPER_ADMIN}) - @Path("/") - public ResponseEntityEntity addMapping( + @PostMapping(path = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity addMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping in JSON format") List mappings) { return mappingService.addMappings(mappings); } @ApiOperation(value = "Update a list of UserMetadataMappings, will only update the fields listed, requires SUPER_ADMIN role") - @PUT - @Consumes(MediaType.APPLICATION_JSON) @RolesAllowed({SUPER_ADMIN}) - @Path("/") + @PutMapping(path = "/", consumes = "application/json", produces = "application/json") public ResponseEntity updateMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping with fields to be updated in JSON format") List mappings) { - return updateEntity(mappings, mappingRepo); + return this.mappingService.updateEntity(mappings); } @ApiOperation(value = "DELETE an UserMetadataMapping by Id only if the UserMetadataMapping is not associated by others, requires SUPER_ADMIN role") - @Transactional - @DELETE @RolesAllowed({SUPER_ADMIN}) - @Path("/{mappingId}") + @DeleteMapping(path = "/{mappingId}", produces = "application/json") public ResponseEntity removeById( @ApiParam(required = true, value = "A valid UserMetadataMapping Id") - @PathParam("mappingId") final String mappingId) { - return removeEntityById(mappingId, mappingRepo); + @PathVariable("mappingId") final String mappingId) { + return this.mappingService.removeEntityById(mappingId); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index 02df69e76..9d00bc476 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -9,6 +9,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import javax.transaction.Transactional; import java.util.List; /** @@ -32,6 +33,7 @@ public List getAllMappingsForConnection(Connection connecti return userMetadataMappingRepo.findByConnection(connection); } + @Transactional public ResponseEntity addMappings(List mappings) { String errorMessage = "The following connectionIds do not exist:\n"; boolean error = false; @@ -54,4 +56,16 @@ public List getAllMappings() { return userMetadataMappingRepo.list(); } + public ResponseEntity getAllMappingsForConnection(String connection) { + return PICSUREResponse.success(getAllMappingsForConnection(connectionRepo.getUniqueResultByColumn("id", connection))); + } + + public ResponseEntity updateEntity(List mappings) { + return this.updateEntity(mappings, userMetadataMappingRepo); + } + + @Transactional + public ResponseEntity removeEntityById(String mappingId) { + return this.removeEntityById(mappingId, userMetadataMappingRepo); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index ad667be2d..f74f716e5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -1,23 +1,26 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; -import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import edu.harvard.hms.dbmi.avillach.auth.utils.JsonUtils; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -30,33 +33,45 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.*; -import java.util.stream.Collectors; @Service public class UserService extends BaseEntityService { private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); + private final MailService mailService; private final TOSService tosService; private final UserRepository userRepository; private final ConnectionRepository connectionRepository; - + private final ApplicationRepository applicationRepository; private final RoleRepository roleRepository; private final String clientSecret; - private final long tokenExpirationTime; - private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour TODO: Move to a global configuration or enum? + private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour + + public long longTermTokenExpirationTime; + + private final String applicationUUID; + private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired - public UserService(TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, - @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime) { + public UserService(MailService mailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, ApplicationRepository applicationRepository, + @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime, + @Value("${application.default.}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { super(User.class); + this.mailService = mailService; this.tosService = tosService; this.userRepository = userRepository; this.connectionRepository = connectionRepository; this.roleRepository = roleRepository; this.clientSecret = clientSecret; this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; + this.applicationRepository = applicationRepository; + this.applicationUUID = applicationUUID; + + long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30; // + this.longTermTokenExpirationTime = longTermTokenExpirationTime > 0 ? longTermTokenExpirationTime : defaultLongTermTokenExpirationTime; + } public HashMap getUserProfileResponse(Map claims) { @@ -123,9 +138,9 @@ public ResponseEntity addEntity(List users) { * if the state is changed, check if the user is a super admin to determine if the user could perform the action. * * @param currentUser the user trying to perform the action - * @param inputUser + * @param inputUser the user that is going to be updated * @param originalUser there could be no original user when adding a new user - * @return + * @return true if the user could perform the action, false otherwise */ private boolean allowUpdateSuperAdminRole( @NotNull User currentUser, @@ -181,38 +196,30 @@ public ResponseEntity addUsers(List users) { } checkAssociation(users); - boolean allowAdd = true; for (User user : users) { logger.debug("Adding User " + user); if (!allowUpdateSuperAdminRole(currentUser, user, null)) { // TODO: The allowUpdateSuperAdminRole is a private method - allowAdd = false; - break; + logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " + + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); + throw new IllegalArgumentException("Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); } if (user.getEmail() == null) { try { HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); - List emailKeys = metadata.keySet().stream().filter((key) -> { - return key.toLowerCase().contains("email"); - }).collect(Collectors.toList()); - if (emailKeys.size() > 0) { - user.setEmail(metadata.get(emailKeys.get(0))); + List emailKeys = metadata.keySet().stream().filter((key) -> key.toLowerCase().contains("email")).toList(); + if (!emailKeys.isEmpty()) { + user.setEmail(metadata.get(emailKeys.getFirst())); } } catch (IOException e) { - e.printStackTrace(); + logger.error("Failed to parse metadata for email address", e); } } } - if (allowAdd) { - ResponseEntity updateResponse = addEntity(users); - sendUserUpdateEmailsFromResponse(updateResponse); - return updateResponse; - } else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " - + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); - throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); - } + ResponseEntity updateResponse = addEntity(users); + sendUserUpdateEmailsFromResponse(updateResponse); + return updateResponse; } /** @@ -264,14 +271,14 @@ public ResponseEntity updateUser(List users) { } else { logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant or remove " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); - throw new ProtocolException(Response.Status.BAD_REQUEST, "Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); + throw new IllegalArgumentException("Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); } } private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) { logger.debug("Sending email"); try { - Object entity = updateResponse.getEntity(); // TODO: Determine how to replicate this given the new approach + Object entity = updateResponse.getBody(); // TODO: Determine how to replicate this given the new approach if (entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent) entity; List addedUsers = (List) okResponse.getContent(); @@ -291,4 +298,193 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) } logger.debug("finished email sending method"); } + + public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasToken) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + User user = (User) securityContext.getAuthentication().getPrincipal(); + if (user == null || user.getUuid() == null) { + logger.error("Security context didn't have a user stored."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + user = this.userRepository.getById(user.getUuid()); + if (user == null) { + logger.error("When retrieving current user, it returned null"); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + User.UserForDisplay userForDisplay = new User.UserForDisplay() + .setEmail(user.getEmail()) + .setPrivileges(user.getPrivilegeNameSet()) + .setUuid(user.getUuid().toString()) + .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.getSubject())); + + // currently, the queryScopes are simple combination of queryScope string together as a set. + // We are expecting the queryScope string as plain string. If it is a JSON, we could change the + // code to use JsonUtils.mergeTemplateMap(Map, Map) + Set privileges = user.getTotalPrivilege(); + if (privileges != null && !privileges.isEmpty()) { + Set scopes = new TreeSet<>(); + privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { + try { + Arrays.stream(objectMapper.readValue(privilege.getQueryScope(), String[].class)) + .filter(x -> x != null) + .forEach(scopeList -> scopes.addAll(Arrays.asList(scopeList))); + } catch (IOException e) { + logger.error("Parsing issue for privilege " + privilege.getUuid() + " queryScope", e); + } + }); + userForDisplay.setQueryScopes(scopes); + } + + if (hasToken != null) { + + if (user.getToken() != null && !user.getToken().isEmpty()) { + userForDisplay.setToken(user.getToken()); + } else { + user.setToken(generateUserLongTermToken(authorizationHeader)); + this.userRepository.merge(user); + userForDisplay.setToken(user.getToken()); + } + } + + return PICSUREResponse.success(userForDisplay); + } + + public ResponseEntity getQueryTemplate(String applicationId) { + if (applicationId == null || applicationId.trim().isEmpty()) { + logger.error("getQueryTemplate() input application UUID is null or empty."); + throw new IllegalArgumentException("Input application UUID is incorrect."); + } + + SecurityContext securityContext = SecurityContextHolder.getContext(); + User user = (User) securityContext.getAuthentication().getPrincipal(); + if (user == null || user.getUuid() == null) { + logger.error("Security context didn't have a user stored."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + user = this.userRepository.getById(user.getUuid()); + if (user == null) { + logger.error("When retrieving current user, it returned null"); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + Application application = this.applicationRepository.getById(UUID.fromString(applicationId)); + + if (application == null) { + logger.error("getQueryTemplate() cannot find corresponding application by UUID: " + applicationId); + throw new IllegalArgumentException("Cannot find application by input UUID: " + applicationId); + } + + return PICSUREResponse.success( + Map.of("queryTemplate", mergeTemplate(user, application))); + } + + public ResponseEntity getDefaultQueryTemplate() { + return getQueryTemplate(this.applicationUUID); + } + + private String mergeTemplate(User user, Application application) { + String resultJSON; + Map mergedTemplateMap = null; + for (Privilege privilege : user.getPrivilegesByApplication(application)) { + String template = privilege.getQueryTemplate(); + logger.debug("mergeTemplate() processing template:" + template); + if (template == null || template.trim().isEmpty()) { + continue; + } + Map templateMap = null; + try { + templateMap = objectMapper.readValue(template, Map.class); + } catch (IOException ex) { + logger.error("mergeTemplate() cannot convert stored queryTemplate using Jackson, the queryTemplate is: " + template); + throw new IllegalArgumentException("Inner application error, please contact admin."); + } + + if (templateMap == null) { + continue; + } + + if (mergedTemplateMap == null) { + mergedTemplateMap = templateMap; + continue; + } + + mergedTemplateMap = JsonUtils.mergeTemplateMap(mergedTemplateMap, templateMap); + } + + try { + resultJSON = objectMapper.writeValueAsString(mergedTemplateMap); + } catch (JsonProcessingException ex) { + logger.error("mergeTemplate() cannot convert map to json string. The map mergedTemplate is: " + mergedTemplateMap); + throw new IllegalArgumentException("Inner application error, please contact admin."); + } + + return resultJSON; + + } + + @Transactional + public ResponseEntity refreshUserToken(HttpHeaders httpHeaders) { + SecurityContext securityContext = SecurityContextHolder.getContext(); + User user = (User) securityContext.getAuthentication().getPrincipal(); + if (user == null || user.getUuid() == null) { + logger.error("Security context didn't have a user stored."); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + user = this.userRepository.getById(user.getUuid()); + if (user == null) { + logger.error("When retrieving current user, it returned null"); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + String authorizationHeader = httpHeaders.getFirst("Authorization"); + String longTermToken = generateUserLongTermToken(authorizationHeader); + user.setToken(longTermToken); + + this.userRepository.merge(user); + + return PICSUREResponse.success(Map.of("userLongTermToken", longTermToken)); + } + + /** + * Logic here is, retrieve the subject of the user from httpHeader. Then generate a long term one + * with LONG_TERM_TOKEN_PREFIX| in front of the subject to be able to distinguish with regular ones, since + * long term token only generated for accessing certain things to, in some degrees, decrease the insecurity. + * + * @param authorizationHeader the authorization header + * @return the long term token + * @throws IllegalArgumentException if the authorization header is not presented + */ + private String generateUserLongTermToken(String authorizationHeader) { + if (!StringUtils.isNotBlank(authorizationHeader)) { + throw new IllegalArgumentException("Authorization header is not presented."); + } + + Optional token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader); + if (token.isEmpty()) { + throw new IllegalArgumentException("Token is not presented in the authorization header."); + } + + Jws jws = JWTUtil.parseToken(token.get()); + + Claims claims = jws.getBody(); + String tokenSubject = claims.getSubject(); + + if (tokenSubject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX + "|")) { + // considering the subject already contains a "|" + // to prevent infinitely adding the long term token prefix + // we will grab the real subject here + tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); + } + + return JWTUtil.createJwtToken(clientSecret, + claims.getId(), + claims.getIssuer(), + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + tokenSubject, + this.longTermTokenExpirationTime); + } } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java index 7a40e5f07..865a8f01f 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java @@ -57,7 +57,7 @@ public void setUp() throws Auth0Exception { doAnswer(invocation -> (listUnmatchedByConnectionIdMock(invocation.getArgument(0)))). when(userRepo).listUnmatchedByConnectionId(any()); doAnswer(invocation -> (getAllMappingsForConnectionMock(invocation.getArgument(0)))). - when(mappingService).getAllMappingsForConnection(any()); + when(mappingService).getAllMappingsForConnection((Connection) any()); //So we can check that the user is persisted doAnswer(new Answer() { public Void answer(InvocationOnMock invocation) { From 5a8cc186746eacfa8813bfad335681ad3ddba778 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 26 Mar 2024 11:03:28 -0400 Subject: [PATCH 010/222] Refactor name of application.default.uuid in JSON and properties files This commit removes the mention of 'application.default.application.uuid' in the JSON and properties files, and replaces it with 'application.default.uuid'. The refactoring clarifies the parameter's functionality. --- .../additional-spring-configuration-metadata.json | 10 +++++----- .../src/main/resources/application.properties | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 8c7e347ad..80ee8ca2e 100644 --- a/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/pic-sure-auth-services/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -30,11 +30,6 @@ "type": "java.lang.String", "description": "Description for application.tos.enabled." }, - { - "name": "application.default.application.uuid", - "type": "java.lang.String", - "description": "Description for application.default.application.uuid." - }, { "name": "application.system.name", "type": "java.lang.String", @@ -114,5 +109,10 @@ "name": "application.long.term.token.expiration.time", "type": "java.lang.String", "description": "Description for application.long.term.token.expiration.time." + }, + { + "name": "application.default.uuid", + "type": "java.lang.String", + "description": "Description for application.default.uuid." } ] } \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 28d01886d..f82505076 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -27,7 +27,7 @@ application.client.secret.base64=your_base64_flag application.user.id.claim=sub application.auth0.host=your_auth0_host application.tos.enabled=true -application.default.application.uuid=your_default_application_uuid +application.default.uuid=your_default_application_uuid application.system.name=your_system_name application.template.path=your_template_path application.access.grant.email.subject=your_email_subject From fcc15f896d908a51f2446f7f6ee7bfc5e54bb859 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 26 Mar 2024 11:11:21 -0400 Subject: [PATCH 011/222] Update JWTFilter and remove unused imports This commit updates JWTFilter with explicit injection of application properties and removes unused imports in AuthSecurityContext. Also, the JWTFilter has been added to SecurityConfig, enhancing security by ensuring request authenticity. --- .../hms/dbmi/avillach/auth/filter/JWTFilter.java | 15 +++++++++------ .../auth/security/AuthSecurityContext.java | 4 ---- .../avillach/auth/security/SecurityConfig.java | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 00eef0b01..428eceb21 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.filter; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; @@ -52,14 +51,18 @@ public class JWTFilter extends OncePerRequestFilter { private final TOSService tosService; - @Value("${application.user.id.claim}") - private String USER_CLAIM_ID; + private final String userClaimId; + + private final boolean tosEnabled; @Autowired - public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService) { + public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService, + @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled) { this.userRepo = userRepo; this.applicationRepo = applicationRepo; this.tosService = tosService; + this.userClaimId = userClaimId; + this.tosEnabled = tosEnabled; } /** @@ -90,7 +93,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet // Parse the token Jws jws = parseToken(token); // TODO: We shouldn't be implementing a method that should be in the JWTUtils class - String userId = jws.getBody().get(this.USER_CLAIM_ID, String.class); // TODO: Update when we remove the JAXRSConfiguration class + String userId = jws.getBody().get(this.userClaimId, String.class); // TODO: Update when we remove the JAXRSConfiguration class if (userId.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { // For profile information, we do indeed allow long term token @@ -176,7 +179,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe throw new NotAuthorizedException("User is deactivated"); } - if (JAXRSConfiguration.tosEnabled.startsWith("true") && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { + if (this.tosEnabled && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate try { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User must accept terms of service"); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java index 31fb8584f..65fe5cbe0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java @@ -1,9 +1,5 @@ //package edu.harvard.hms.dbmi.avillach.auth.security; // -//import edu.harvard.hms.dbmi.avillach.auth.data.entity.Application; -//import edu.harvard.hms.dbmi.avillach.auth.data.entity.User; -// -//import javax.ws.rs.core.SecurityContext; //import java.security.Principal; // ///** diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java index 110755bb5..3ef9cd348 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java @@ -25,7 +25,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated() ) - .addFilter() + .addFilter(this.jwtFilter) .httpBasic(withDefaults()); return http.build(); From cc440c1435480248f38060af085d30d168b88149 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 27 Mar 2024 16:50:12 -0400 Subject: [PATCH 012/222] Refactor code and add spring-boot-starter-mail dependency This commit mainly removes unused imports across various files, updates some classes with value injection and cleans out unnecessary comments. It also renames the 'MailService' class to 'BasicMailService' and updates the related references accordingly. The pom.xml file has been updated with the 'spring-boot-starter-mail' dependency addition. 'JWTUtil' class has been updated to include spring '@Value' for properties instead of using static variables. --- pic-sure-auth-services/pom.xml | 9 +- .../dbmi/avillach/auth/filter/JWTFilter.java | 2 - .../dbmi/avillach/auth/model/AccessEmail.java | 1 - .../avillach/auth/rest/AuthController.java | 1 - .../avillach/auth/service/MailService.java | 14 + .../service/impl/AuthenticationService.java | 12 +- .../auth/service/impl/BaseEntityService.java | 23 +- .../auth/service/impl/BasicMailService.java | 130 ++++++++++ .../impl/FENCEAuthenticationService.java | 240 ++++++++++-------- .../auth/service/impl/MailService.java | 138 ---------- .../auth/service/impl/UserService.java | 9 +- .../hms/dbmi/avillach/auth/utils/JWTUtil.java | 33 ++- .../dbmi/avillach/auth/utils/JsonUtils.java | 65 +---- 13 files changed, 340 insertions(+), 337 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 8d5f6a954..35eeca7da 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -78,8 +78,15 @@ org.springframework.boot spring-boot-starter-security - 3.2.3 + 3.2.4 + + + org.springframework.boot + spring-boot-starter-mail + 3.2.4 + + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 428eceb21..2c9a590cb 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -152,8 +152,6 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic request.setAttribute("authenticatedApplication", authenticatedApplication); } - // TODO: Implement the ApplicationException thrown in this method - /** * Sets the security context for the given user. * This method is responsible for validating the user claims, checking if the user is active, diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java index 90b19ebd9..35ee19aa5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.model; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index cf891eca5..712c388eb 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthenticationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.FENCEAuthenticationService; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java new file mode 100644 index 000000000..ef6542cc6 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/MailService.java @@ -0,0 +1,14 @@ +package edu.harvard.hms.dbmi.avillach.auth.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.mustachejava.Mustache; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import jakarta.mail.MessagingException; + +public interface MailService { + void sendUsersAccessEmail(User user) throws MessagingException; + + void sendDeniedAccessEmail(JsonNode userInfo) throws MessagingException; + + void sendEmail(Mustache emailTemplate, String to, String subject, Object scope) throws MessagingException; +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 7e60a5971..7effdc603 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -40,16 +40,16 @@ public class AuthenticationService { private final UserRepository userRepository; - private final MailService mailService; + private final BasicMailService basicMailService; private final UserService userService; private static final int AUTH_RETRY_LIMIT = 3; @Autowired - public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, MailService mailService, UserService userService) { + public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, BasicMailService basicMailService, UserService userService) { this.matchingService = matchingService; this.userRepository = userRepository; - this.mailService = mailService; + this.basicMailService = basicMailService; this.userService = userService; } @@ -88,9 +88,11 @@ public ResponseEntity getToken(Map authRequest) { if (user == null) { if (JAXRSConfiguration.deniedEmailEnabled.startsWith("true")) { try { - mailService.sendDeniedAccessEmail(userInfo); - } catch (MessagingException e) { + basicMailService.sendDeniedAccessEmail(userInfo); + } catch (MessagingException e) { // TODO: We need to remove the javax.mail dependency logger.warn("Failed to send user access denied email: ", e); + } catch (jakarta.mail.MessagingException e) { + throw new RuntimeException(e); } } throw new NotAuthorizedException("No user matching user_id " + userId + " present in database"); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java index d9f10d0ef..50607851e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java @@ -2,12 +2,13 @@ import edu.harvard.dbmi.avillach.data.entity.BaseEntity; import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; import javax.validation.constraints.NotNull; import java.lang.reflect.Field; @@ -27,7 +28,7 @@ public abstract class BaseEntityService { protected final Class type; private final String auditLogName; - + protected BaseEntityService(Class type){ this.type = type; @@ -36,7 +37,9 @@ protected BaseEntityService(Class type){ } public ResponseEntity getEntityById(String id, BaseRepository baseRepository){ - logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) + SecurityContext securityContext = SecurityContextHolder.getContext(); + String principalName = securityContext.getAuthentication().getName(); + logger.info("User: " + principalName + " Looking for " + type.getSimpleName() + " by ID: " + id + "..."); @@ -50,7 +53,9 @@ public ResponseEntity getEntityById(String id, BaseRepository baseRepository) } public ResponseEntity getEntityAll(BaseRepository baseRepository){ - logger.info("User: " + JAXRSConfiguration.getPrincipalName(securityContext) + + SecurityContext securityContext = SecurityContextHolder.getContext(); + String principalName = securityContext.getAuthentication().getName(); + logger.info("User: " + principalName + " Getting all " + type.getSimpleName() + "s..."); List ts = null; @@ -66,7 +71,8 @@ public ResponseEntity getEntityAll(BaseRepository baseRepository){ } public ResponseEntity addEntity(List entities, BaseRepository baseRepository){ - String username = JAXRSConfiguration.getPrincipalName(securityContext); + SecurityContext securityContext = SecurityContextHolder.getContext(); + String username = securityContext.getAuthentication().getName(); if (entities == null || entities.isEmpty()) return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + " to be added."); @@ -99,7 +105,8 @@ public ResponseEntity updateEntity(List entities, BaseRepository baseRepos return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + " to be updated."); - String username = JAXRSConfiguration.getPrincipalName(securityContext); + SecurityContext securityContext = SecurityContextHolder.getContext(); + String username = securityContext.getAuthentication().getName(); logger.info("User: " + username + " is trying to update a list of " + type.getSimpleName()); @@ -228,8 +235,8 @@ private boolean updateAllAttributes(T detachedT, BaseRepository baseRep } public ResponseEntity removeEntityById(String id, BaseRepository baseRepository) { - - String username = JAXRSConfiguration.getPrincipalName(securityContext); + SecurityContext securityContext = SecurityContextHolder.getContext(); + String username = securityContext.getAuthentication().getName(); logger.info("User: " + username + " is trying to REMOVE an entity: " + type.getSimpleName() + ", by uuid: " + id); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java new file mode 100644 index 000000000..80039c41e --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java @@ -0,0 +1,130 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.StringWriter; +import java.util.Map; + +import edu.harvard.hms.dbmi.avillach.auth.model.AccessEmail; +import edu.harvard.hms.dbmi.avillach.auth.service.MailService; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import edu.harvard.hms.dbmi.avillach.auth.entity.User; + +/** + *

Service class for sending email notifications.

+ */ +@Service +public class BasicMailService implements MailService { + private static final Logger logger = LoggerFactory.getLogger(BasicMailService.class); + private static final MustacheFactory mf = new DefaultMustacheFactory(); + private final Mustache accessTemplate = compileTemplate("accessEmail.mustache"); + private final Mustache deniedTemplate = compileTemplate("deniedAccessEmail.mustache"); + private final JavaMailSender mailSender; + private final String templatePath; + private final String systemName; + private final String accessGrantEmailSubject; + private final String adminUsers; + + + @Autowired + public BasicMailService(JavaMailSender mailSender, @Value("${application.template.path}") String templatePath, @Value("${application.system.name}") String systemName, + @Value("${application.access.grant.email.subject") String accessGrantEmailSubject, @Value("${application.admin.users}") String adminUsers) { + this.mailSender = mailSender; + this.templatePath = templatePath; + this.systemName = systemName; + this.accessGrantEmailSubject = accessGrantEmailSubject; + this.adminUsers = adminUsers; + } + + /** + * Compile mustache template from templateFile + */ + private Mustache compileTemplate(String templateFile) { + try { + FileReader reader = new FileReader(templatePath + templateFile); + return mf.compile(reader, templateFile); + } catch (FileNotFoundException e) { + logger.warn("email template not found for " + templateFile); + return null; + } + } + + /** + * Send email to user about changes in user Roles + * @param user User object + */ + @Override + public void sendUsersAccessEmail(User user) throws MessagingException { + if(accessTemplate == null) { + logger.debug("No template defined for new user access email, not sending"); + }else if (StringUtils.isEmpty(user.getEmail())) { + logger.error("User " + (user.getSubject() != null ? user.getSubject() : "") + " has no email address."); + } else { + String subject = "Your Access To " + this.systemName; + if (this.accessGrantEmailSubject != null && !this.accessGrantEmailSubject.isEmpty() && !this.accessGrantEmailSubject.equals("none")){ + subject = this.accessGrantEmailSubject; + } + sendEmail(accessTemplate, user.getEmail(),subject, new AccessEmail(user)); + } + } + + /** + * Send email to admin about user being denied access to the system + * @param userInfo User info object returned by authentication provider + */ + @Override + public void sendDeniedAccessEmail(JsonNode userInfo) throws MessagingException { + if(deniedTemplate == null) { + logger.debug("No template for Access Denied email, not sending"); + } else { + logger.info("Sending 'Access Denied' email to " + + this.adminUsers + + ". User: " + + userInfo.get("email") != null ? userInfo.get("email").asText() : userInfo.get("user_id").asText()); + ObjectMapper mapper = new ObjectMapper(); + Map scope = mapper.convertValue(userInfo, Map.class); + scope.put("systemName", this.systemName); + sendEmail(deniedTemplate, this.adminUsers, "User denied access to " + this.systemName, scope); + } + } + + /** + * Generate email from template and send it. + * @param emailTemplate Name of the template. + * @param to Recipients + * @param subject Subject of the email + * @param scope Object that contains attributes for template. e.g.: Map + */ + @Override + public void sendEmail(Mustache emailTemplate, String to, String subject, Object scope) throws MessagingException { + logger.debug("sendEmail(String, String, String, Object) - start"); + if (StringUtils.isEmpty(to) || StringUtils.isEmpty(subject) || scope == null || emailTemplate == null) { + logger.error("One of the required parameters is null. Can't send email."); + return; + } + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message); + helper.setFrom(to); + helper.setSubject(subject); + helper.setText(emailTemplate.execute(new StringWriter(), scope).toString(), true); + mailSender.send(message); + logger.debug("sendEmail() finished"); + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java index b3ee6a06c..2f133aefc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java @@ -1,31 +1,37 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.*; +import jakarta.annotation.PostConstruct; import org.apache.http.Header; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import import java.io.File; import java.io.IOException; import java.util.*; -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.*; - @Service public class FENCEAuthenticationService { private final Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final UserRepository userRepo; private final RoleRepository roleRepo; @@ -44,8 +50,33 @@ public class FENCEAuthenticationService { private Connection fenceConnection; private Map fenceMapping; + // ----------------- FENCE Configuration ----------------- + private final String idp_provider_uri; + private final String fence_client_id; + private final String fence_client_secret; + private final String fence_redirect_url; + private final String fence_consent_group_concept_path; + private final String fence_harmonized_concept_path; + private final String fence_standard_access_rules; + + // ----------------- Template Path ----------------- + private final String templatePath; + + // ----------------- HTTP Client ----------------- + private final RestClient restClient; + @Autowired - public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRepo, ConnectionRepository connectionRepo, AccessRuleRepository accessruleRepo, ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, UserService userService) { + public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRepo, ConnectionRepository connectionRepo, + AccessRuleRepository accessruleRepo, ApplicationRepository applicationRepo, + PrivilegeRepository privilegeRepo, UserService userService, + @Value("${application.idp.provider}") String idpProviderUri, + @Value("${application.fence.client.id") String fenceClientId, + @Value("${application.fence.client.secret}") String fenceClientSecret, + @Value("${application.fence.redirect.url}") String fenceRedirectUrl, + @Value("${application.fence.consent.group.concept.path}") String fenceConsentGroupConceptPath, + @Value("${application.fence.harmonized.concept.path}") String fenceHarmonizedConceptPath, + @Value("${application.fence.standard.access.rules}") String fenceStandardAccessRules, + @Value("${application.template.path}") String templatePath) { this.userRepo = userRepo; this.roleRepo = roleRepo; this.connectionRepo = connectionRepo; @@ -53,14 +84,24 @@ public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRe this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; this.userService = userService; + idp_provider_uri = idpProviderUri; + fence_client_id = fenceClientId; + fence_client_secret = fenceClientSecret; + fence_redirect_url = fenceRedirectUrl; + fence_consent_group_concept_path = fenceConsentGroupConceptPath; + fence_harmonized_concept_path = fenceHarmonizedConceptPath; + fence_standard_access_rules = fenceStandardAccessRules; + this.templatePath = templatePath; + this.restClient = RestClient.builder() + .requestFactory(new HttpComponentsClientHttpRequestFactory()) + .build(); } - // TODO: Find and equivalent of @PostConstruct in spring -// @PostConstruct - public void initializeFenceService() { - picSureApp = applicationRepo.getUniqueResultByColumn("name", "PICSURE"); - fenceConnection = connectionRepo.getUniqueResultByColumn("label", "FENCE"); - fenceMapping = getFENCEMapping(); + @PostConstruct + public void initializeFenceService() { + picSureApp = applicationRepo.getUniqueResultByColumn("name", "PICSURE"); + fenceConnection = connectionRepo.getUniqueResultByColumn("label", "FENCE"); + fenceMapping = getFENCEMapping(); } private JsonNode getFENCEUserProfile(String access_token) { @@ -68,15 +109,15 @@ private JsonNode getFENCEUserProfile(String access_token) { List
headers = new ArrayList<>(); headers.add(new BasicHeader("Authorization", "Bearer " + access_token)); - logger.debug("getFENCEUserProfile() getting user profile from uri:"+JAXRSConfiguration.idp_provider_uri+"/user/user"); + logger.debug("getFENCEUserProfile() getting user profile from uri:" + this.idp_provider_uri + "/user/user"); JsonNode fence_user_profile_response = HttpClientUtil.simpleGet( - JAXRSConfiguration.idp_provider_uri+"/user/user", - JAXRSConfiguration.client, - JAXRSConfiguration.objectMapper, + this.idp_provider_uri + "/user/user", + this.client, + this.objectMapper, headers.toArray(new Header[headers.size()]) ); - logger.debug("getFENCEUserProfile() finished, returning user profile"+fence_user_profile_response.asText()); + logger.debug("getFENCEUserProfile() finished, returning user profile" + fence_user_profile_response.asText()); return fence_user_profile_response; } @@ -85,53 +126,53 @@ private JsonNode getFENCEAccessToken(String fence_code) { List
headers = new ArrayList<>(); Base64.Encoder encoder = Base64.getEncoder(); - String fence_auth_header = JAXRSConfiguration.fence_client_id+":"+JAXRSConfiguration.fence_client_secret; + String fence_auth_header = this.fence_client_id + ":" + this.fence_client_secret; headers.add(new BasicHeader("Authorization", "Basic " + encoder.encodeToString(fence_auth_header.getBytes()))); headers.add(new BasicHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")); // Build the request body, as JSON String query_string = - "grant_type=authorization_code" - + "&code=" + fence_code - + "&redirect_uri=" + JAXRSConfiguration.fence_redirect_url; + "grant_type=authorization_code" + + "&code=" + fence_code + + "&redirect_uri=" + this.fence_redirect_url; - String fence_token_url = JAXRSConfiguration.idp_provider_uri+"/user/oauth2/token"; + String fence_token_url = this.idp_provider_uri + "/user/oauth2/token"; JsonNode resp = null; try { resp = HttpClientUtil.simplePost( fence_token_url, new StringEntity(query_string), - JAXRSConfiguration.client, - JAXRSConfiguration.objectMapper, + this.client, + this.objectMapper, headers.toArray(new Header[headers.size()]) ); } catch (Exception ex) { - logger.error("getFENCEAccessToken() failed to call FENCE token service, "+ex.getMessage()); + logger.error("getFENCEAccessToken() failed to call FENCE token service, " + ex.getMessage()); } - logger.debug("getFENCEAccessToken() finished. "+resp.asText()); + logger.debug("getFENCEAccessToken() finished. " + resp.asText()); return resp; } // Get access_token from FENCE, based on the provided `code` - public ResponseEntity getFENCEProfile(Map authRequest){ + public ResponseEntity getFENCEProfile(Map authRequest) { logger.debug("getFENCEProfile() starting..."); - String fence_code = authRequest.get("code"); + String fence_code = authRequest.get("code"); JsonNode fence_user_profile = null; // Get the Gen3/FENCE user profile. It is a JsonNode object try { logger.debug("getFENCEProfile() query FENCE for user profile with code"); fence_user_profile = getFENCEUserProfile(getFENCEAccessToken(fence_code).get("access_token").asText()); - logger.debug("getFENCEProfile() user profile structure:"+fence_user_profile.asText()); + logger.debug("getFENCEProfile() user profile structure:" + fence_user_profile.asText()); logger.debug("getFENCEProfile() .username:" + fence_user_profile.get("username")); logger.debug("getFENCEProfile() .user_id:" + fence_user_profile.get("user_id")); logger.debug("getFENCEProfile() .email:" + fence_user_profile.get("email")); } catch (Exception ex) { - logger.error("getFENCEToken() could not retrieve the user profile from the auth provider, because "+ex.getMessage(), ex); - throw new NotAuthorizedException("Could not get the user profile "+ - "from the Gen3 authentication provider."+ex.getMessage()); + logger.error("getFENCEToken() could not retrieve the user profile from the auth provider, because " + ex.getMessage(), ex); + throw new NotAuthorizedException("Could not get the user profile " + + "from the Gen3 authentication provider." + ex.getMessage()); } User current_user = null; @@ -140,12 +181,12 @@ public ResponseEntity getFENCEProfile(Map authRequest){ // in the Gen3/FENCE profile current_user = createUserFromFENCEProfile(fence_user_profile); logger.info("getFENCEProfile() saved details for user with e-mail:" - +current_user.getEmail() - +" and subject:" - +current_user.getSubject()); + + current_user.getEmail() + + " and subject:" + + current_user.getSubject()); } catch (Exception ex) { - logger.error("getFENCEToken() Could not persist the user information, because "+ex.getMessage()); + logger.error("getFENCEToken() Could not persist the user information, because " + ex.getMessage()); throw new NotAuthorizedException("The user details could not be persisted. Please contact the administrator."); } @@ -160,39 +201,34 @@ public ResponseEntity getFENCEProfile(Map authRequest){ continue; } - logger.debug("getFENCEProfile() AccessRole:"+access_role_name); + logger.debug("getFENCEProfile() AccessRole:" + access_role_name); String[] parts = access_role_name.split("\\."); String newRoleName; if (parts.length > 1) { - newRoleName = "FENCE_"+parts[0]+"_"+parts[parts.length-1]; + newRoleName = "FENCE_" + parts[0] + "_" + parts[parts.length - 1]; } else { - newRoleName = "FENCE_"+access_role_name; + newRoleName = "FENCE_" + access_role_name; } - logger.info("getFENCEProfile() New PSAMA role name:"+newRoleName); - - if (upsertRole(current_user, newRoleName, "FENCE role "+newRoleName)) { - logger.info("getFENCEProfile() Updated user role. Now it includes `"+newRoleName+"`"); - } else { - logger.error("getFENCEProfile() could not add roles to user's profile"); - } + logger.info("getFENCEProfile() New PSAMA role name:" + newRoleName); - // TODO: In case we need to do something with this part, we can uncomment it. - //JsonNode role_object = fence_user_profile.get("project_access").get(newRoleName); - //It is a an array of strings, like this: ["read-storage","read"] - //logger.debug("getFENCEProfile() object:"+role_object.toString()); + if (upsertRole(current_user, newRoleName, "FENCE role " + newRoleName)) { + logger.info("getFENCEProfile() Updated user role. Now it includes `" + newRoleName + "`"); + } else { + logger.error("getFENCEProfile() could not add roles to user's profile"); + } } try { userRepo.changeRole(current_user, current_user.getRoles()); - logger.debug("upsertRole() updated user, who now has "+current_user.getRoles().size()+" roles."); + logger.debug("upsertRole() updated user, who now has " + current_user.getRoles().size() + " roles."); } catch (Exception ex) { - logger.error("upsertRole() Could not add roles to user, because "+ex.getMessage()); + logger.error("upsertRole() Could not add roles to user, because " + ex.getMessage()); } - HashMap claims = new HashMap(); + HashMap claims = new HashMap(); claims.put("name", fence_user_profile.get("name")); claims.put("email", current_user.getEmail()); claims.put("sub", current_user.getSubject()); - HashMap responseMap = userService.getUserProfileResponse(claims); + HashMap responseMap = userService.getUserProfileResponse(claims); logger.debug("getFENCEProfile() UserProfile response object has been generated"); logger.debug("getFENCEToken() finished"); @@ -210,7 +246,7 @@ private User createUserFromFENCEProfile(JsonNode node) { logger.debug("createUserFromFENCEProfile() starting..."); User new_user = new User(); - new_user.setSubject("fence|"+node.get("user_id").asText()); + new_user.setSubject("fence|" + node.get("user_id").asText()); new_user.setEmail(node.get("email").asText()); new_user.setGeneralMetadata(node.toString()); // This is a hack, but someone has to do it. @@ -232,14 +268,14 @@ private User createUserFromFENCEProfile(JsonNode node) { /** * Insert or Update the User object's list of Roles in the database. * - * @param u The User object the generated Role will be added to - * @param roleName Name of the Role + * @param u The User object the generated Role will be added to + * @param roleName Name of the Role * @param roleDescription Description of the Role * @return boolean Whether the Role was successfully added to the User or not */ - private boolean upsertRole(User u, String roleName, String roleDescription) { + private boolean upsertRole(User u, String roleName, String roleDescription) { boolean status = false; - logger.debug("upsertRole() starting for user subject:"+u.getSubject()); + logger.debug("upsertRole() starting for user subject:" + u.getSubject()); // Get the User's list of Roles. The first time, this will be an empty Set. // This method is called for every Role, and the User's list of Roles will @@ -266,7 +302,7 @@ private boolean upsertRole(User u, String roleName, String roleDescription) { u.getRoles().add(r); status = true; } catch (Exception ex) { - logger.error("upsertRole() Could not inser/update role "+roleName+" to repo, because "+ex.getMessage()); + logger.error("upsertRole() Could not inser/update role " + roleName + " to repo, because " + ex.getMessage()); } @@ -276,7 +312,7 @@ private boolean upsertRole(User u, String roleName, String roleDescription) { private Set upsertPrivilege(User u, Role r) { String roleName = r.getName(); - logger.info("upsertPrivilege() starting, adding privilege to role "+roleName); + logger.info("upsertPrivilege() starting, adding privilege to role " + roleName); String[] parts = roleName.split("_"); String project_name = parts[1]; @@ -285,20 +321,22 @@ private Set upsertPrivilege(User u, Role r) { String concept_path = fenceMapping.get(project_name); // Get privilege and assign it to this role. - String privilegeName = r.getName().replaceFirst("FENCE_*","PRIV_FENCE_"); - logger.info("upsertPrivilege() Looking for privilege, with name : "+privilegeName); + String privilegeName = r.getName().replaceFirst("FENCE_*", "PRIV_FENCE_"); + logger.info("upsertPrivilege() Looking for privilege, with name : " + privilegeName); Set privs = r.getPrivileges(); - if (privs == null) { privs = new HashSet();} + if (privs == null) { + privs = new HashSet(); + } Privilege p = privilegeRepo.getUniqueResultByColumn("name", privilegeName); if (p != null) { - logger.info("upsertPrivilege() Assigning privilege "+p.getName()+" to role "+r.getName()); + logger.info("upsertPrivilege() Assigning privilege " + p.getName() + " to role " + r.getName()); privs.add(p); } else { logger.info("upsertPrivilege() This is a new privilege"); - logger.info("upsertPrivilege() project:"+project_name+" consent_group:"+consent_group+" concept_path:"+concept_path); + logger.info("upsertPrivilege() project:" + project_name + " consent_group:" + consent_group + " concept_path:" + concept_path); // Add new privilege PRIV_FENCE_phs######_c# and PRIV_FENCE_phs######_c#_HARMONIZED privs.add(createNewPrivilege(project_name, consent_group, concept_path, false)); @@ -314,21 +352,21 @@ private Privilege createNewPrivilege(String project_name, String consent_group, // Build Privilege Object try { priv.setApplication(picSureApp); - priv.setName("PRIV_FENCE_"+project_name+"_"+consent_group+(isHarmonized?"_HARMONIZED":"")); - priv.setDescription("FENCE privilege for "+project_name+"/"+consent_group); + priv.setName("PRIV_FENCE_" + project_name + "_" + consent_group + (isHarmonized ? "_HARMONIZED" : "")); + priv.setDescription("FENCE privilege for " + project_name + "/" + consent_group); priv.setQueryScope(queryScopeConceptPath); String consent_concept_path = fence_consent_group_concept_path; // TOOD: Change this to a mustache template String queryTemplateText = "{\"categoryFilters\": {\"" - +consent_concept_path - +"\":\"" - +project_name+"."+consent_group - +"\"}," - +"\"numericFilters\":{},\"requiredFields\":[]," - +"\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," - +"\"expectedResultType\": \"COUNT\"" - +"}"; + + consent_concept_path + + "\":\"" + + project_name + "." + consent_group + + "\"}," + + "\"numericFilters\":{},\"requiredFields\":[]," + + "\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," + + "\"expectedResultType\": \"COUNT\"" + + "}"; priv.setQueryTemplate(queryTemplateText); priv.setQueryScope(queryScopeConceptPath); @@ -337,18 +375,18 @@ private Privilege createNewPrivilege(String project_name, String consent_group, Set accessrules = new HashSet(); accessrules.add(ar); // Add additionanl access rules - for(String arName: fence_standard_access_rules.split(",")) { + for (String arName : fence_standard_access_rules.split(",")) { if (arName.startsWith("AR_")) { - logger.info("Adding AccessRule "+arName+" to privilege "+priv.getName()); - accessrules.add(accessruleRepo.getUniqueResultByColumn("name",arName)); + logger.info("Adding AccessRule " + arName + " to privilege " + priv.getName()); + accessrules.add(accessruleRepo.getUniqueResultByColumn("name", arName)); } } priv.setAccessRules(accessrules); - logger.info("createNewPrivilege() Added "+accessrules.size()+" access_rules to privilege"); + logger.info("createNewPrivilege() Added " + accessrules.size() + " access_rules to privilege"); } privilegeRepo.persist(priv); - logger.info("createNewPrivilege() Added new privilege "+priv.getName()+" to DB"); + logger.info("createNewPrivilege() Added new privilege " + priv.getName() + " to DB"); } catch (Exception ex) { ex.printStackTrace(); logger.error("createNewPrivilege() could not save privilege"); @@ -358,24 +396,24 @@ private Privilege createNewPrivilege(String project_name, String consent_group, private AccessRule upsertAccessRule(String project_name, String consent_group) { logger.debug("upsertAccessRule() starting"); - String ar_name = "AR_"+project_name+"_"+consent_group; + String ar_name = "AR_" + project_name + "_" + consent_group; AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); if (ar != null) { - logger.info("upsertAccessRule() AccessRule "+ar_name+" already exists."); + logger.info("upsertAccessRule() AccessRule " + ar_name + " already exists."); return ar; } - logger.info("upsertAccessRule() Creating new access rule "+ar_name); + logger.info("upsertAccessRule() Creating new access rule " + ar_name); ar = new AccessRule(); ar.setName(ar_name); - ar.setDescription("FENCE AR for "+project_name+"/"+consent_group); + ar.setDescription("FENCE AR for " + project_name + "/" + consent_group); StringBuilder ruleText = new StringBuilder(); ruleText.append("$..categoryFilters.['"); ruleText.append(fence_consent_group_concept_path); ruleText.append("']"); ar.setRule(ruleText.toString()); ar.setType(AccessRule.TypeNaming.ALL_EQUALS); - ar.setValue(project_name+"."+consent_group); + ar.setValue(project_name + "." + consent_group); ar.setCheckMapKeyOnly(false); ar.setCheckMapNode(true); ar.setEvaluateOnlyByGates(false); @@ -386,8 +424,8 @@ private AccessRule upsertAccessRule(String project_name, String consent_group) { for (String accessruleName : fence_standard_access_rules.split("\\,")) { if (accessruleName.startsWith("GATE_")) { logger.info("upsertAccessRule() Assign gate " + accessruleName + - " to access_rule "+ar.getName()); - gates.add(accessruleRepo.getUniqueResultByColumn("name",accessruleName)); + " to access_rule " + ar.getName()); + gates.add(accessruleRepo.getUniqueResultByColumn("name", accessruleName)); } else { continue; } @@ -400,20 +438,20 @@ private AccessRule upsertAccessRule(String project_name, String consent_group) { return ar; } - /* - * Get the mappings of fence privileges to paths - */ - @SuppressWarnings("unchecked") - private Map getFENCEMapping() { - try { - return JAXRSConfiguration.objectMapper.readValue( - new File(String.join(File.separator, - new String[] {JAXRSConfiguration.templatePath ,"fence_mapping.json"})) - , Map.class); - } catch (IOException e) { - logger.error("fence_mapping.json not found at "+JAXRSConfiguration.templatePath); - } - return Map.of(); - } + /* + * Get the mappings of fence privileges to paths + */ + @SuppressWarnings("unchecked") + private Map getFENCEMapping() { + try { + return this.objectMapper.readValue( + new File(String.join(File.separator, + new String[]{this.templatePath, "fence_mapping.json"})) + , Map.class); + } catch (IOException e) { + logger.error("fence_mapping.json not found at " + this.templatePath); + } + return Map.of(); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java deleted file mode 100644 index 5bf189714..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/MailService.java +++ /dev/null @@ -1,138 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.impl; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.StringWriter; -import java.util.Map; -import java.util.Properties; - -import javax.mail.*; -import javax.mail.internet.AddressException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; - -import edu.harvard.hms.dbmi.avillach.auth.model.AccessEmail; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mustachejava.DefaultMustacheFactory; -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; - -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; - -/** - *

Service class for sending email notifications.

- */ -@Service -public class MailService { - private static Logger logger = LoggerFactory.getLogger(MailService.class); - private static MustacheFactory mf = new DefaultMustacheFactory(); - - private Session mailSession; - - private static Mustache accessTemplate = compileTemplate("accessEmail.mustache"); - private static Mustache deniedTemplate = compileTemplate("deniedAccessEmail.mustache"); - - - public static final int SMTP_TIMEOUT_MS = 1000; - - public MailService(){ - //try to read this from the app container configuration - mailSession = JAXRSConfiguration.mailSession; - if(mailSession == null) { - mailSession = Session.getDefaultInstance(System.getProperties()); - } - - // define timeout - wildfly doesn't read this from standalone (not in xml schema) - Properties properties = mailSession.getProperties(); - properties.put("mail.smtp.connectiontimeout", SMTP_TIMEOUT_MS); - } - - /** - * Compile mustache template from templateFile - * - * @throws FileNotFoundException Exception thrown if templateFile is missing due to not being configured - */ - private static Mustache compileTemplate(String templateFile) { - try { - FileReader reader = new FileReader(JAXRSConfiguration.templatePath + templateFile); - return mf.compile(reader, templateFile); - } catch (FileNotFoundException e) { - logger.warn("email template not found for " + templateFile); - return null; - } - } - - /** - * Send email to user about changes in user Roles - * @param user - * @throws MessagingException - * @throws AddressException - */ - public void sendUsersAccessEmail(User user) throws AddressException, MessagingException{ - if(accessTemplate == null) { - logger.debug("No template defined for new user access email, not sending"); - }else if (StringUtils.isEmpty(user.getEmail())) { - logger.error("User " + (user.getSubject() != null ? user.getSubject() : "") + " has no email address."); - } else { - String subject = "Your Access To " + JAXRSConfiguration.systemName; - if (JAXRSConfiguration.accessGrantEmailSubject != null && !JAXRSConfiguration.accessGrantEmailSubject.isEmpty() && !JAXRSConfiguration.accessGrantEmailSubject.equals("none")){ - subject = JAXRSConfiguration.accessGrantEmailSubject; - } - sendEmail(accessTemplate, user.getEmail(),subject, new AccessEmail(user)); - } - } - - /** - * Send email to admin about user being denied access to the system - * @param userInfo User info object returned by authentication provider - * @throws MessagingException - * @throws AddressException - */ - public void sendDeniedAccessEmail(JsonNode userInfo) throws AddressException, MessagingException{ - if(deniedTemplate == null) { - logger.debug("No template for Access Denied email, not sending"); - } else { - logger.info("Sending 'Access Denied' email to " - + JAXRSConfiguration.adminUsers - + ". User: " - + userInfo.get("email") != null ? userInfo.get("email").asText() : userInfo.get("user_id").asText()); - ObjectMapper mapper = new ObjectMapper(); - Map scope = mapper.convertValue(userInfo, Map.class); - scope.put("systemName", JAXRSConfiguration.systemName); - sendEmail(deniedTemplate, JAXRSConfiguration.adminUsers, "User denied access to " + JAXRSConfiguration.systemName, scope); - } - } - - /** - * Generate email from template and send it. - * @param emailTemplate Name of the template. - * @param to Recipients - * @param subject Subject of the email - * @param scope Object that contains attributes for template. e.g.: Map - * @throws AddressException - * @throws MessagingException - */ - private void sendEmail(Mustache emailTemplate, String to, String subject, Object scope) throws AddressException, MessagingException { - logger.debug("sendEmail(String, String, String, Object) - start"); - if (StringUtils.isEmpty(to) || StringUtils.isEmpty(subject) || scope == null || emailTemplate == null) { - logger.error("One of the required parameters is null. Can't send email."); - return; - } - - Message message = new MimeMessage(mailSession); - message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to)); - message.setSubject(subject); - message.setContent(emailTemplate.execute(new StringWriter(), scope).toString(),"text/html"); - - Transport.send(message); - logger.debug("sendEmail() finished"); - - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index f74f716e5..91e1a6388 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; @@ -39,7 +38,7 @@ public class UserService extends BaseEntityService { private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); - private final MailService mailService; + private final BasicMailService basicMailService; private final TOSService tosService; private final UserRepository userRepository; private final ConnectionRepository connectionRepository; @@ -55,11 +54,11 @@ public class UserService extends BaseEntityService { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired - public UserService(MailService mailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, ApplicationRepository applicationRepository, + public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, ApplicationRepository applicationRepository, @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime, @Value("${application.default.}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { super(User.class); - this.mailService = mailService; + this.basicMailService = basicMailService; this.tosService = tosService; this.userRepository = userRepository; this.connectionRepository = connectionRepository; @@ -285,7 +284,7 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) String message = okResponse.getMessage(); for (User user : addedUsers) { try { - mailService.sendUsersAccessEmail(user); + basicMailService.sendUsersAccessEmail(user); } catch (MessagingException e) { logger.error("Failed to send email! " + e.getLocalizedMessage()); logger.debug("Exception Trace: ", e); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index ee7683780..a908d03ab 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -1,12 +1,12 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import io.jsonwebtoken.*; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; @@ -17,24 +17,28 @@ /** *

This class is for generating a JWT token and contains common methods for operations on JWT tokens.

- *

For more information on JWT tokens, see https://github.com/hms-dbmi/jwt-creator/blob/master/src/main/java/edu/harvard/hms/dbmi/avillach/jwt/App.java

+ *

For more information on JWT tokens, see ...

*/ +@Component public class JWTUtil { private final static Logger logger = LoggerFactory.getLogger(JWTUtil.class); private static final long defaultTTLMillis = 1000L * 60 * 60 * 24 * 7; @Value("${application.client.secret}") - private static String CLIENT_SECRET; + private static String clientSecret; + + @Value("${application.client.secret.base64}") + private static boolean clientSecretIsBase64; /** - * @param clientSecret - * @param id - * @param issuer - * @param claims - * @param subject - * @param ttlMillis - * @return + * @param clientSecret - client secret + * @param id - id + * @param issuer - issuer + * @param claims - claims + * @param subject - subject + * @param ttlMillis - time to live in milliseconds + * @return JWT token */ public static String createJwtToken(String clientSecret, String id, String issuer, Map claims, String subject, long ttlMillis) { logger.debug("createJwtToken() starting..."); @@ -54,6 +58,7 @@ public static String createJwtToken(String clientSecret, String id, String issue //We will sign our JWT with our ApiKey secret byte[] apiKeySecretBytes = clientSecret.getBytes(); + Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //Builds the JWT and serializes it to a compact, URL-safe string @@ -78,15 +83,15 @@ public static Jws parseToken(String token) { Jws jws = null; try { - jws = Jwts.parser().setSigningKey(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); + jws = Jwts.parser().setSigningKey(clientSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); } catch (SignatureException e) { try { - if (JAXRSConfiguration.clientSecretIsBase64.startsWith("true")) { + if (clientSecretIsBase64) { // handle if client secret is base64 encoded - jws = Jwts.parser().setSigningKey(Base64.decodeBase64(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8))).parseClaimsJws(token); + jws = Jwts.parser().setSigningKey(Base64.decodeBase64(clientSecret.getBytes(StandardCharsets.UTF_8))).parseClaimsJws(token); } else { // handle if client secret is not base64 encoded - jws = Jwts.parser().setSigningKey(CLIENT_SECRET.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); + jws = Jwts.parser().setSigningKey(clientSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); } } catch (JwtException | IllegalArgumentException ex) { logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java index a9a6020dc..ecbdf0efd 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java @@ -1,7 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -130,69 +128,14 @@ public static Set mergeToNewSet(Object a, Object b){ } - /* - * Taking this method out for now, as we don't do index-based comparisons anymore. This may have been to - * allow merging of variant info filters; I don't think that's necessary for query templates -NC 4/2020 - */ - -// /** -// * will merge the two elements in the same location. -// * meaning baseList(0) will merge with incomingList(0) ... -// * And if two same-location elements are not in the same type, only the element in baseList will be kept -// * -// * @param baseList -// * @param incomingList -// * @return -// */ -// public static List mergeListToList(List baseList, List incomingList){ -// List mergedList = new ArrayList(); -// -// if (incomingList.size()==0) { -// addElementsOfListToMergedList(mergedList, baseList); -// }else if(incomingList.get(0) instanceof String) { -// addElementsOfListToMergedList(mergedList, incomingList); -// addElementsOfListToMergedList(mergedList, baseList); -// } else { -// List sourceList = baseList.size()>incomingList.size()?baseList:incomingList; -// List targetList = baseList.size()<=incomingList.size()?baseList:incomingList; -// for (int i = 0 ; i < sourceList.size() ; i++) { -// Object sourceElement = sourceList.get(i); -// if(targetList.size()<=i) { -// mergedList.add(sourceList.get(i)); -// }else { -// Object targetElement = targetList.get(i); -// if (sourceElement.getClass() == targetElement.getClass() ) { -// if (sourceElement instanceof List){ -// mergedList.add(mergeListToList((List)sourceElement, (List)targetElement)); -// } else if (sourceElement instanceof Map){ -// mergedList.add(mergeTemplateMap((Map)sourceElement, (Map)targetElement)); -// } else { -// logJsonTypeException(sourceElement); -// } -// } else { -// mergedList.add(sourceElement); -// } -// } -// } -// } -// -// return mergedList; -// } -// -// private static void addElementsOfListToMergedList(List mergedList, List baseList) { -// if(baseList!=null&&baseList.size()>0) { -// mergedList.addAll(baseList); -// } -// } - /** * attach or merge a map to a list, the list might contain many elements that are string, list or map that are converted from JSONs. * The logic here is first check if there is any map element that has the same structure based on the method isMapMergeable, * if true, merge, if false, simply attach. * - * @param map - * @param list - * @return + * @param map a map that is converted from a JSON. + * @param collection a list that contains many elements that are string, list or map that are converted from JSONs. + * @return a new list that contains the merged map. */ public static Set mergeMapToSet(Map map, Collection collection){ Set mergedSet = new HashSet(collection.size()); @@ -239,7 +182,7 @@ public static boolean isMapMergeable(Map baseMap, Map incomingMap){ private static void logJsonTypeException(Object value){ logger.error("Incoming JSON Object is a type: " + value.getClass() + ", can only merge String, List and Map!"); - throw new ApplicationException("Inner application error, please contact admin."); + throw new IllegalArgumentException("Inner application error, please contact admin."); } } From b6f8bb426e0af037a3146cce49a6c0901ec7f6d8 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 5 Apr 2024 15:42:14 -0400 Subject: [PATCH 013/222] Remove BaseEntityService and improve user-specific behaviour Removed the BaseEntityService class to streamline services. Updated several methods in the UserService to directly use the UserRepository, enhancing their reliability and readability. The AuthenticationService now also benefits from dependency injection for configuration properties, and ConnectionRepository has been incorporated. --- pic-sure-auth-services/pom.xml | 76 +++-- .../dbmi/avillach/auth/entity/AccessRule.java | 13 +- .../avillach/auth/entity/Application.java | 8 +- .../dbmi/avillach/auth/entity/BaseEntity.java | 53 ++++ .../dbmi/avillach/auth/entity/Connection.java | 5 +- .../dbmi/avillach/auth/entity/Privilege.java | 3 +- .../hms/dbmi/avillach/auth/entity/Role.java | 9 +- .../avillach/auth/entity/TermsOfService.java | 5 +- .../hms/dbmi/avillach/auth/entity/User.java | 7 +- .../auth/entity/UserMetadataMapping.java | 7 +- .../dbmi/avillach/auth/filter/JWTFilter.java | 13 +- .../dbmi/avillach/auth/model/AccessEmail.java | 5 +- .../auth/repository/AccessRuleRepository.java | 9 +- .../repository/ApplicationRepository.java | 13 +- .../auth/repository/ConnectionRepository.java | 26 +- .../auth/repository/PrivilegeRepository.java | 9 +- .../auth/repository/RoleRepository.java | 9 +- .../repository/TermsOfServiceRepository.java | 25 +- .../UserMetadataMappingRepository.java | 12 +- .../auth/repository/UserRepository.java | 177 +++--------- .../auth/rest/AccessRuleController.java | 35 ++- .../auth/rest/ApplicationController.java | 30 +- .../avillach/auth/rest/AuthController.java | 11 +- .../auth/rest/ConnectionWebController.java | 39 ++- .../auth/rest/PrivilegeController.java | 21 +- .../avillach/auth/rest/RoleController.java | 32 ++- .../avillach/auth/rest/UserController.java | 34 +-- .../UserMetadataMappingWebController.java | 59 ++-- .../auth/service/PrivilegeService.java | 42 ++- .../avillach/auth/service/RoleService.java | 73 +++-- .../auth/service/impl/AccessRuleService.java | 27 +- .../auth/service/impl/ApplicationService.java | 63 +++-- .../service/impl/AuthenticationService.java | 75 ++--- .../auth/service/impl/BaseEntityService.java | 266 ------------------ .../auth/service/impl/BasicMailService.java | 2 +- .../service/impl/ConnectionWebService.java | 40 +-- .../impl/FENCEAuthenticationService.java | 175 ++++++------ .../impl/OauthUserMatchingService.java | 17 +- .../auth/service/impl/TOSService.java | 63 +++-- .../auth/service/impl/TokenService.java | 15 +- .../impl/UserMetadataMappingService.java | 40 +-- .../auth/service/impl/UserService.java | 136 +++++---- .../avillach/auth/utils/RestClientUtil.java | 82 ++++++ .../avillach/Auth0MatchingServiceTest.java | 10 +- .../avillach/auth/utils/JsonUtilsTest.java | 44 +-- 45 files changed, 885 insertions(+), 1030 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/BaseEntity.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 35eeca7da..cd1f5dffc 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -16,34 +16,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - edu.harvard.hms.dbmi.avillach - pic-sure-api-data - 2.1.0-SNAPSHOT - - - org.apache.logging.log4j - log4j-core - - - org.slf4j - slf4j-log4j12 - - - org.slf4j - slf4j-api - - - ch.qos.logback - logback-classic - - - org.slf4j - slf4j-jdk14 - - + ch.qos.logback + logback-classic + 1.2.3 + @@ -86,7 +93,12 @@ spring-boot-starter-mail 3.2.4 - + + + org.springframework.boot + spring-boot-starter-data-jpa + 3.2.4 + @@ -181,10 +193,16 @@ swagger-jaxrs2-servlet-initializer 2.0.0 + + org.springframework.boot + spring-boot-starter-validation + RELEASE + compile + - edu.harvard.hms.dbmi.avillach.picsure.auth.microapp - pic-sure-auth-microapp - 1.0-SNAPSHOT + jakarta.transaction + jakarta.transaction-api + 2.0.1 diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java index bbb6a98fa..314a68a50 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java @@ -2,9 +2,17 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; -import javax.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Transient; + import java.lang.reflect.Field; import java.util.HashSet; import java.util.LinkedHashMap; @@ -34,7 +42,6 @@ */ @Entity(name = "access_rule") public class AccessRule extends BaseEntity { - /** * please do not modify the existing values, in case the value has * already saved in the database. But you can add more constant values, or diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 36981cfd3..b07770e6e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -1,9 +1,11 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonInclude; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; - -import javax.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.OneToMany; import java.security.Principal; import java.util.Set; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/BaseEntity.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/BaseEntity.java new file mode 100644 index 000000000..6e681ec3a --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/BaseEntity.java @@ -0,0 +1,53 @@ +package edu.harvard.hms.dbmi.avillach.auth.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.annotations.GenericGenerator; + +import java.util.Objects; +import java.util.UUID; + +@MappedSuperclass +public abstract class BaseEntity { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator( + name = "UUID", + strategy = "org.hibernate.id.UUIDGenerator") + @Column(columnDefinition = "BINARY(16)") + protected UUID uuid; + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + @Override + public int hashCode() { + return Objects.hash(uuid); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof BaseEntity entity)) { + return false; + } + if (this.uuid == null){ + return false; + } + + return this.uuid.equals(entity.uuid); + } + + @Override + public String toString() { + return this.uuid.toString(); + } +} + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java index 4c7e92a50..c4e4db8fc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Connection.java @@ -1,9 +1,8 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; -import javax.persistence.Column; -import javax.persistence.Entity; import java.io.Serializable; /** diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java index 7076d1b9d..4c7988b88 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Privilege.java @@ -3,9 +3,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.Set; import java.util.stream.Collectors; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java index 529e74657..aa0fd91fd 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Role.java @@ -1,9 +1,8 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonInclude; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; +import jakarta.persistence.*; -import javax.persistence.*; import java.util.Set; import java.util.stream.Collectors; @@ -14,15 +13,15 @@ @Entity(name = "role") public class Role extends BaseEntity { - String name; + private String name; - String description; + private String description; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "role_privilege", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "privilege_id")}) - Set privileges; + private Set privileges; public String getName() { return name; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java index f7615b68f..8843cfec6 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/TermsOfService.java @@ -1,9 +1,8 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.PrePersist; -import javax.persistence.Entity; -import javax.persistence.PrePersist; import java.util.Date; /** diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 66765f564..1fc346e98 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -2,11 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; +import jakarta.persistence.*; import org.hibernate.annotations.Type; -import javax.persistence.*; import java.io.Serializable; import java.security.Principal; import java.util.Date; @@ -190,12 +189,12 @@ public String getPrivilegeString(){ if (totalPrivilegeSet == null) return null; - return totalPrivilegeSet.stream().map(p -> p.getName()).collect(Collectors.joining(",")); + return totalPrivilegeSet.stream().map(Privilege::getName).collect(Collectors.joining(",")); } @JsonIgnore public String getRoleString(){ - return (roles==null)?null:roles.stream().map(r -> r.name) + return (roles==null)?null:roles.stream().map(Role::getName) .collect(Collectors.joining(",")); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java index 5e44ffdfb..6d67d2ad6 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/UserMetadataMapping.java @@ -1,8 +1,9 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; - -import javax.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; /** *

Model defining how to map an admin added user to an Oauth user profile.

diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 2c9a590cb..80dd91090 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -23,6 +23,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -92,8 +93,8 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet logger.debug(" token: {}", token); // Parse the token - Jws jws = parseToken(token); // TODO: We shouldn't be implementing a method that should be in the JWTUtils class - String userId = jws.getBody().get(this.userClaimId, String.class); // TODO: Update when we remove the JAXRSConfiguration class + Jws jws = parseToken(token); + String userId = jws.getBody().get(this.userClaimId, String.class); if (userId.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { // For profile information, we do indeed allow long term token @@ -122,14 +123,14 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet } // Authenticate as Application - Application authenticatedApplication = applicationRepo.getById(UUID.fromString(userId.split("\\|")[1])); - if (authenticatedApplication == null) { + Optional authenticatedApplication = applicationRepo.findById(UUID.fromString(userId.split("\\|")[1])); + if (authenticatedApplication.isEmpty()) { logger.error("Cannot find an application by userId: " + userId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); return; } - if (!authenticatedApplication.getToken().equals(token)) { + if (!authenticatedApplication.get().getToken().equals(token)) { logger.error("filter() incoming application token - " + token + " - is not the same as record, might because the token has been refreshed. Subject: " + userId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token has been inactivated, please contact admin to grab you the latest one."); @@ -137,7 +138,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet // This is the application token that is being used to authenticate the user by other applications // Set the security context for the application - setSecurityContextForApplication(request, authenticatedApplication); + setSecurityContextForApplication(request, authenticatedApplication.orElse(null)); } else { logger.debug("UserID: {} is not a long term token and not a PSAMA application token.", userId); // Authenticate as User diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java index 35ee19aa5..31825de94 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/AccessEmail.java @@ -10,16 +10,17 @@ */ public class AccessEmail { - private String systemName = JAXRSConfiguration.systemName; + private String systemName = null; private String documentation = null; private String username; private Set roles; private boolean rolesExists; - public AccessEmail(User u) { + public AccessEmail(User u, String systemName) { this.username = u.getName(); this.roles = u.getRoles(); + this.systemName = systemName; } public String getUsername() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java index 2d9f9ec47..c668dcd57 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/AccessRuleRepository.java @@ -1,7 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.UUID; @@ -12,9 +12,8 @@ * @see AccessRule */ @Repository -public class AccessRuleRepository extends BaseRepository { +public interface AccessRuleRepository extends JpaRepository { + + AccessRule findByName(String name); - protected AccessRuleRepository() { - super(AccessRule.class); - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java index ceb0f2960..86d82894a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java @@ -1,7 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.UUID; @@ -12,12 +12,9 @@ * @see Application */ -// TODO: A repository class is not the right place to annotate with transactional. Verify? -// TODO: Is there a reason why we would scope this to the application? @Repository -public class ApplicationRepository extends BaseRepository { +public interface ApplicationRepository extends JpaRepository { - protected ApplicationRepository() { - super(Application.class); - } -} + Application findByName(String name); + +} \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java index 3369bd3f8..ccd3ab5ef 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java @@ -1,13 +1,9 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import javax.persistence.NoResultException; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import java.util.UUID; /** @@ -16,24 +12,8 @@ * @see Connection */ @Repository -public class ConnectionRepository extends BaseRepository { +public interface ConnectionRepository extends JpaRepository { - protected ConnectionRepository() { - super(Connection.class); - } + Connection findByLabel(String label); - public Connection findConnectionById(String connectionId) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(Connection.class); - Root queryRoot = query.from(Connection.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - try { - return em.createQuery(query - .where( - eq(cb, queryRoot, "id", connectionId))) - .getSingleResult(); - } catch (NoResultException e) { - return null; - } - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java index 7d02a05ff..c7af74330 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/PrivilegeRepository.java @@ -1,7 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.UUID; @@ -12,9 +12,8 @@ */ @Repository -public class PrivilegeRepository extends BaseRepository { +public interface PrivilegeRepository extends JpaRepository { + + Privilege findByName(String name); - protected PrivilegeRepository() { - super(Privilege.class); - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java index d2cb6ae1e..9aa53a9ad 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java @@ -1,7 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.UUID; @@ -12,9 +12,8 @@ */ @Repository -public class RoleRepository extends BaseRepository { +public interface RoleRepository extends JpaRepository { + + Role findByName(String name); - protected RoleRepository() { - super(Role.class); - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java index eb4ef256e..f18914ec5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java @@ -1,12 +1,9 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import java.util.UUID; /** @@ -14,20 +11,12 @@ * @see TermsOfService */ @Repository -public class TermsOfServiceRepository extends BaseRepository { +public interface TermsOfServiceRepository extends JpaRepository { - protected TermsOfServiceRepository() { - super(TermsOfService.class); - } + /** + *

Find the latest TermsOfService by date updated.

+ * @return TermsOfService + */ + TermsOfService findTopByOrderByDateUpdatedDesc(); - public TermsOfService getLatest(){ - CriteriaQuery query = cb().createQuery(TermsOfService.class); - Root queryRoot = query.from(TermsOfService.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .orderBy(cb.desc(queryRoot.get("dateUpdated")))) - .setMaxResults(1) - .getSingleResult(); - } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java index fa8d10cc9..36a457d6d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserMetadataMappingRepository.java @@ -1,8 +1,8 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @@ -14,14 +14,8 @@ */ @Repository -public class UserMetadataMappingRepository extends BaseRepository { +public interface UserMetadataMappingRepository extends JpaRepository { - protected UserMetadataMappingRepository() { - super(UserMetadataMapping.class); - } - - public List findByConnection(Connection connection) { - return getByColumn("connection", connection); - } + List findByConnection(Connection connection); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java index 257939dc3..4fc0c6369 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java @@ -1,25 +1,12 @@ package edu.harvard.hms.dbmi.avillach.auth.repository; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; import edu.harvard.hms.dbmi.avillach.auth.entity.User; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import javax.persistence.NoResultException; -import javax.persistence.NonUniqueResultException; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; -import javax.persistence.criteria.Subquery; -import java.util.Date; import java.util.List; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** *

Provides operations for the User entity to interact with a database.

@@ -27,136 +14,36 @@ */ @Repository -public class UserRepository extends BaseRepository { - - private final static Logger logger = LoggerFactory.getLogger(UserRepository.class); - - protected UserRepository() { - super(User.class); - } - - public User findBySubject(String subject) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - eq(cb, queryRoot, "subject", subject))) - .getSingleResult(); - } - - public User findBySubjectAndConnection(String subject, String connectionId){ - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - try { - return em.createQuery(query - .where( - cb.and( - cb.equal(queryRoot.join("connection") - .get("id"), connectionId), - eq(cb, queryRoot, "subject", subject)))) - .getSingleResult(); - } catch (NoResultException e){ - return null; - } - } - - public List listUnmatchedByConnectionId(Connection connection) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - cb.and( - eq(cb, queryRoot, "connection", connection), - eq(cb, queryRoot, "matched", false)))) - .getResultList(); - } - - /** - * - * @return - */ - public User findOrCreate(User inputUser) { - User user = null; - String subject = inputUser.getSubject(); - try{ - user = findBySubject(subject); - logger.info("findOrCreate(), trying to find user: {subject: " + subject+ - "}, and found a user with uuid: " + user.getUuid() - + ", subject: " + user.getSubject()); - } catch (NoResultException e) { - logger.debug("findOrCreate() subject " + subject + - " could not be found by `entityManager`, going to create a new user."); - user = createUser(inputUser); - }catch(NonUniqueResultException e){ - logger.error("findOrCreate() " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - return user; - } - - private User createUser(User inputUser) { - String subject = inputUser.getSubject(); - logger.debug("createUser() creating user, subject: " + subject + " ......"); - em().persist(inputUser); - - User result = getById(inputUser.getUuid()); - if (result != null) - logger.info("createUser() created user, uuid: " + result.getUuid() - + ", subject: " + subject - + ", role: " + result.getRoleString() - + ", privilege: "+ result.getPrivilegeString()); - - return result; - } - - public User changeRole(User user, Set roles){ - logger.info("Starting changing the role of user: " + user.getUuid() - + ", with subject: " + user.getSubject() + ", to " + roles.stream().map(role -> role.getName()).collect(Collectors.joining(","))); - user.setRoles(roles); - em().merge(user); - User updatedUser = getById(user.getUuid()); - logger.info("User: " + updatedUser.getUuid() + ", with subject: " + - updatedUser.getSubject() + ", now has a new role: " + updatedUser.getRoleString()); - return updatedUser; - } - - @Override - public void persist(User user) { - findOrCreate(user); - } - - public User findByEmail(String email) { - CriteriaQuery query = em.getCriteriaBuilder().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - return em.createQuery(query - .where( - eq(cb, queryRoot, "email", email))) - .getSingleResult(); - } - - public boolean checkAgainstTOSDate(String userId){ - CriteriaQuery query = cb().createQuery(User.class); - Root queryRoot = query.from(User.class); - query.select(queryRoot); - CriteriaBuilder cb = cb(); - - Subquery subquery = query.subquery(Date.class); - Root tosRoot = subquery.from(TermsOfService.class); - subquery.select(cb.greatest(tosRoot.get("dateUpdated"))); - - return !em.createQuery(query - .where( - cb.and( - eq(cb, queryRoot, "subject", userId), - cb.greaterThanOrEqualTo(queryRoot.get("acceptedTOS"), subquery)))) - .getResultList().isEmpty(); - } +public interface UserRepository extends JpaRepository { + + User findBySubject(String subject); + + User findBySubjectAndConnection(String subject, Connection connection); + + List findByConnectionAndMatched(Connection connection, boolean matched); + +// +// public User changeRole(User user, Set roles){ +// logger.info("Starting changing the role of user: " + user.getUuid() +// + ", with subject: " + user.getSubject() + ", to " + roles.stream().map(role -> role.getName()).collect(Collectors.joining(","))); +// user.setRoles(roles); +// em().merge(user); +// User updatedUser = getById(user.getUuid()); +// logger.info("User: " + updatedUser.getUuid() + ", with subject: " + +// updatedUser.getSubject() + ", now has a new role: " + updatedUser.getRoleString()); +// return updatedUser; +// } +// +// @Override +// public void persist(User user) { +// findOrCreate(user); +// } + + /** + *

Find a user by email.

+ * @param email the email to search for + * @return User + */ + User findByEmail(String email); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index b49915b87..bd59944e9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Optional; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -21,7 +22,7 @@ /** *

Endpoint for service handling business logic for access rules.

*

Note: Only users with the super admin role can access this endpoint.

- * + *

* Path: /accessRule */ @@ -39,16 +40,23 @@ public AccessRuleController(AccessRuleService accessRuleService) { @Secured(value = {ADMIN, SUPER_ADMIN}) @GetMapping(value = "/{accessRuleId}") public ResponseEntity getAccessRuleById( - @ApiParam(value="The UUID of the accessRule to fetch information about") + @ApiParam(value = "The UUID of the accessRule to fetch information about") @PathVariable("accessRuleId") String accessRuleId) { - return this.accessRuleService.getEntityById(accessRuleId); + Optional entityById = this.accessRuleService.getAccessRuleById(accessRuleId); + + if (entityById.isEmpty()) { + return PICSUREResponse.error("AccessRule not found", 404); + } + + return PICSUREResponse.success(entityById.get()); } @ApiOperation(value = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") @Secured({ADMIN, SUPER_ADMIN}) @GetMapping("") public ResponseEntity getAccessRuleAll() { - return this.accessRuleService.getEntityAll(); + List allAccessRules = this.accessRuleService.getAllAccessRules(); + return PICSUREResponse.success(allAccessRules); } @ApiOperation(value = "POST a list of AccessRules, requires SUPER_ADMIN role") @@ -56,8 +64,14 @@ public ResponseEntity getAccessRuleAll() { @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity addAccessRule( @ApiParam(required = true, value = "A list of AccessRule in JSON format") - List accessRules){ - return this.accessRuleService.addEntity(accessRules); + List accessRules) { + accessRules = this.accessRuleService.addAccessRule(accessRules); + + if (accessRules.isEmpty()) { + return PICSUREResponse.protocolError("No access rules added", 400); + } + + return PICSUREResponse.success(accessRules); } @ApiOperation(value = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") @@ -65,8 +79,9 @@ public ResponseEntity addAccessRule( @PutMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity updateAccessRule( @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") - List accessRules){ - return this.accessRuleService.updateEntity(accessRules); + List accessRules) { + accessRules = this.accessRuleService.updateAccessRules(accessRules); + return PICSUREResponse.success(accessRules); } @ApiOperation(value = "DELETE an AccessRule by Id only if the accessRule is not associated by others, requires SUPER_ADMIN role") @@ -75,13 +90,13 @@ public ResponseEntity updateAccessRule( public ResponseEntity removeById( @ApiParam(required = true, value = "A valid accessRule Id") @PathVariable("accessRuleId") final String accessRuleId) { - return this.accessRuleService.removeEntityById(accessRuleId); + return PICSUREResponse.success(this.accessRuleService.removeAccessRuleById(accessRuleId)); } @ApiOperation(value = "GET all types listed for the rule in accessRule that could be used, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @GetMapping(path = "/allTypes", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getAllTypes(){ + public ResponseEntity getAllTypes() { return PICSUREResponse.success(AccessRule.TypeNaming.getTypeNameMap()); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index a190ef2b0..4f82a7933 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -1,6 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -11,8 +12,9 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import javax.transaction.Transactional; import java.util.List; +import java.util.Map; +import java.util.Optional; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -38,13 +40,19 @@ public ApplicationController(ApplicationService applicationService) { public ResponseEntity getApplicationById( @ApiParam(required = true, value = "The UUID of the application to fetch information about") @PathVariable("applicationId") String applicationId) { - return applicationService.getEntityById(applicationId); + Optional entityById = applicationService.getApplicationByID(applicationId); + + if (entityById.isEmpty()) { + return PICSUREResponse.protocolError("Application is not found by given Application ID: " + applicationId); + } + + return PICSUREResponse.success(entityById.get()); } @ApiOperation(value = "GET a list of existing Applications, no role restrictions") @GetMapping public ResponseEntity getApplicationAll() { - return applicationService.getEntityAll(); + return PICSUREResponse.success(applicationService.getAllApplications()); } @ApiOperation(value = "POST a list of Applications, requires SUPER_ADMIN role") @@ -53,7 +61,8 @@ public ResponseEntity getApplicationAll() { public ResponseEntity addApplication( @ApiParam(required = true, value = "A list of AccessRule in JSON format") List applications) { - return applicationService.addNewApplications(applications); + applications = applicationService.addNewApplications(applications); + return PICSUREResponse.success(applications); } @ApiOperation(value = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") @@ -62,7 +71,8 @@ public ResponseEntity addApplication( public ResponseEntity updateApplication( @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") List applications) { - return applicationService.updateApplications(applications); + applications = applicationService.updateApplications(applications); + return PICSUREResponse.success(applications); } @ApiOperation(value = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") @@ -71,7 +81,8 @@ public ResponseEntity updateApplication( public ResponseEntity refreshApplicationToken( @ApiParam(required = true, value = "A valid application Id") @PathVariable("applicationId") String applicationId) { - return applicationService.refreshApplicationToken(applicationId); + String newApplicationToken = applicationService.refreshApplicationToken(applicationId); + return PICSUREResponse.success(Map.of("token", newApplicationToken)); } @ApiOperation(value = "DELETE an Application by Id only if the application is not associated by others, requires SUPER_ADMIN role") @@ -80,7 +91,12 @@ public ResponseEntity refreshApplicationToken( public ResponseEntity removeById( @ApiParam(required = true, value = "A valid accessRule Id") @PathVariable("applicationId") final String applicationId) { - return applicationService.deleteApplicationById(applicationId); + try { + List applications = applicationService.deleteApplicationById(applicationId); + return PICSUREResponse.success(applications); + } catch (IllegalArgumentException e) { + return PICSUREResponse.protocolError(e.getMessage()); + } } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index 712c388eb..3a80c7778 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -9,11 +9,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import java.io.IOException; import java.util.Map; @@ -32,18 +34,21 @@ public class AuthController { public final AuthenticationService authenticationService; public final FENCEAuthenticationService fenceAuthenticationService; + private final String idp_provider; + @Autowired - public AuthController(AuthorizationService authorizationService, AuthenticationService authenticationService, FENCEAuthenticationService fenceAuthenticationService) { + public AuthController(AuthorizationService authorizationService, AuthenticationService authenticationService, FENCEAuthenticationService fenceAuthenticationService, @Value("${application.idp.provider}") String idpProvider) { this.authorizationService = authorizationService; this.authenticationService = authenticationService; this.fenceAuthenticationService = fenceAuthenticationService; + this.idp_provider = idpProvider; } @ApiOperation(value = "The authentication endpoint for retrieving a valid user token") @PostMapping(path = "/authentication", consumes = "application/json", produces = "application/json") - public ResponseEntity authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) { + public ResponseEntity authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) throws IOException { logger.debug("authentication() starting..."); - if (JAXRSConfiguration.idp_provider.equalsIgnoreCase("fence")) { + if (this.idp_provider.equalsIgnoreCase("fence")) { logger.debug("authentication() FENCE authentication"); return fenceAuthenticationService.getFENCEProfile(authRequest); } else { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index 923f98556..a6ccf5f36 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -1,6 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ConnectionWebService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -19,7 +20,7 @@ /** *

Endpoint for service handling business logic for connections to PSAMA.
- * Note: Only users with the super admin role can access this endpoint.

+ * Note: Only users with the super admin role can access this endpoint.

*/ @Api @Controller @@ -35,19 +36,25 @@ public ConnectionWebController(ConnectionWebService connectionWebSerivce) { } @ApiOperation(value = "GET information of one Connection with the UUID, requires ADMIN or SUPER_ADMIN role") - @GetMapping(path ="/{connectionId}", produces = "application/json") + @GetMapping(path = "/{connectionId}", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getConnectionById( - @ApiParam(required = true, value="The UUID of the Connection to fetch information about") + @ApiParam(required = true, value = "The UUID of the Connection to fetch information about") @PathVariable("connectionId") String connectionId) { - return connectionWebService.getEntityById(connectionId); + try { + Connection connectionById = connectionWebService.getConnectionById(connectionId); + return ResponseEntity.ok(connectionById); + } catch (IllegalArgumentException e) { + return PICSUREResponse.protocolError(e.getMessage()); + } } @ApiOperation(value = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") - @GetMapping(path ="/", produces = "application/json") + @GetMapping(path = "/", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getAllConnections() { - return connectionWebService.getEntityAll(); + List allConnections = connectionWebService.getAllConnections(); + return ResponseEntity.ok(allConnections); } @ApiOperation(value = "POST a list of Connections, requires SUPER_ADMIN role") @@ -56,8 +63,14 @@ public ResponseEntity getAllConnections() { @PostMapping(produces = "application/json", consumes = "application/json") public ResponseEntity addConnection( @ApiParam(required = true, value = "A list of Connections in JSON format") - List connections){ - return connectionWebService.addEntity(connections); + List connections) { + try { + connections = connectionWebService.addConnection(connections); + } catch (IllegalArgumentException e) { + return PICSUREResponse.protocolError(e.getMessage()); + } + + return PICSUREResponse.success("All connections are added.", connections); } @ApiOperation(value = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") @@ -65,18 +78,20 @@ public ResponseEntity addConnection( @PutMapping(produces = "application/json", consumes = "application/json") public ResponseEntity updateConnection( @ApiParam(required = true, value = "A list of Connection with fields to be updated in JSON format") - List connections){ - return connectionWebService.updateEntity(connections); + List connections) { + List responseEntity = connectionWebService.updateConnections(connections); + return ResponseEntity.ok(responseEntity); } @ApiOperation(value = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") @Transactional @Secured({SUPER_ADMIN}) - @DeleteMapping(path ="/{connectionId}", produces = "application/json") + @DeleteMapping(path = "/{connectionId}", produces = "application/json") public ResponseEntity removeById( @ApiParam(required = true, value = "A valid connection Id") @PathVariable("connectionId") final String connectionId) { - return connectionWebService.removeEntityById(connectionId); + List connections = connectionWebService.removeConnectionById(connectionId); + return ResponseEntity.ok(connections); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index 5f1aa82bf..ec91ccf39 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -1,6 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.PrivilegeService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -37,14 +38,21 @@ public PrivilegeController(PrivilegeService privilegeService) { public ResponseEntity getPrivilegeById( @ApiParam(value="The UUID of the privilege to fetch information about") @PathVariable("privilegeId") String privilegeId) { - return this.privilegeService.getEntityById(privilegeId); + Privilege privilegeById = this.privilegeService.getPrivilegeById(privilegeId); + + if (privilegeById == null) { + return PICSUREResponse.protocolError("Privilege not found"); + } + + return PICSUREResponse.success(privilegeById); } @ApiOperation(value = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/", produces = "application/json") public ResponseEntity getPrivilegeAll() { - return this.privilegeService.getEntityAll(); + List privilegesAll = this.privilegeService.getPrivilegesAll(); + return PICSUREResponse.success(privilegesAll); } @ApiOperation(value = "POST a list of privileges, requires SUPER_ADMIN role") @@ -53,7 +61,8 @@ public ResponseEntity getPrivilegeAll() { public ResponseEntity addPrivilege( @ApiParam(required = true, value = "A list of privileges in JSON format") List privileges){ - return this.privilegeService.addEntity(privileges); + privileges = this.privilegeService.addPrivileges(privileges); + return PICSUREResponse.success(privileges); } @ApiOperation(value = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") @@ -62,7 +71,8 @@ public ResponseEntity addPrivilege( public ResponseEntity updatePrivilege( @ApiParam(required = true, value = "A list of privilege with fields to be updated in JSON format") List privileges){ - return this.privilegeService.updateEntity(privileges); + privileges = this.privilegeService.updatePrivileges(privileges); + return ResponseEntity.ok(privileges); } @ApiOperation(value = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") @@ -71,7 +81,8 @@ public ResponseEntity updatePrivilege( public ResponseEntity removeById( @ApiParam(required = true, value = "A valid privilege Id") @PathVariable("privilegeId") final String privilegeId) { - return this.privilegeService.deletePrivilegeByPrivilegeId(privilegeId); + List privileges = this.privilegeService.deletePrivilegeByPrivilegeId(privilegeId); + return ResponseEntity.ok(privileges); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index 906bc0278..4b59d9ea3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -1,6 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -11,7 +12,9 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; +import java.text.MessageFormat; import java.util.List; +import java.util.Optional; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -36,16 +39,17 @@ public RoleController(RoleService roleService) { @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json", path = "/{roleId}") public ResponseEntity getRoleById( - @ApiParam(value="The UUID of the Role to fetch information about") + @ApiParam(value = "The UUID of the Role to fetch information about") @PathVariable("roleId") String roleId) { - return this.roleService.getEntityById(roleId); + return this.roleService.getRoleById(roleId); } @ApiOperation(value = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") @GetMapping(produces = "application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) public ResponseEntity getRoleAll() { - return this.roleService.getEntityAll(); + List allRoles = this.roleService.getAllRoles(); + return PICSUREResponse.success(allRoles); } @ApiOperation(value = "POST a list of Roles, requires SUPER_ADMIN role") @@ -53,8 +57,9 @@ public ResponseEntity getRoleAll() { @PostMapping(produces = "application/json") public ResponseEntity addRole( @ApiParam(required = true, value = "A list of Roles in JSON format") - List roles){ - return this.roleService.addEntity(roles); + List roles) { + List savedRoles = this.roleService.addRoles(roles); + return PICSUREResponse.success("All roles are added.", savedRoles); } @ApiOperation(value = "Update a list of Roles, will only update the fields listed, requires SUPER_ADMIN role") @@ -62,8 +67,13 @@ public ResponseEntity addRole( @PutMapping(produces = "application/json") public ResponseEntity updateRole( @ApiParam(required = true, value = "A list of Roles with fields to be updated in JSON format") - List roles){ - return this.roleService.updateEntity(roles); + List roles) { + List updatedRoles = this.roleService.updateRoles(roles); + if (updatedRoles.isEmpty()) { + return PICSUREResponse.protocolError("No Role(s) has been updated."); + } + + return PICSUREResponse.success("All Roles are updated.", updatedRoles); } @ApiOperation(value = "DELETE an Role by Id only if the Role is not associated by others, requires SUPER_ADMIN role") @@ -72,9 +82,13 @@ public ResponseEntity updateRole( public ResponseEntity removeById( @ApiParam(required = true, value = "A valid Role Id") @PathVariable("roleId") final String roleId) { - return this.roleService.removeEntityById(roleId); - } + Optional> roles = this.roleService.removeRoleById(roleId); + if (roles.isEmpty()) { + return PICSUREResponse.protocolError("Role not found - uuid: " + roleId); + } + return PICSUREResponse.success(MessageFormat.format("Successfully deleted role by id: {0}, listing rest of the role(s) as below", roleId), roles.get()); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index ae44f612b..a52f8fbb9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -1,34 +1,21 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; -import com.fasterxml.jackson.core.JsonProcessingException; -import edu.harvard.dbmi.avillach.util.exception.ApplicationException; -import edu.harvard.dbmi.avillach.util.exception.ProtocolException; import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; -import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; -import edu.harvard.hms.dbmi.avillach.auth.utils.JsonUtils; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jws; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import jakarta.annotation.security.RolesAllowed; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.transaction.Transactional; -import java.io.IOException; import java.util.*; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -45,12 +32,10 @@ public class UserController { private final UserService userService; - private final TOSService tosService; @Autowired - public UserController(UserService userService, TOSService tosService) { + public UserController(UserService userService) { this.userService = userService; - this.tosService = tosService; } @ApiOperation(value = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") @@ -59,14 +44,16 @@ public UserController(UserService userService, TOSService tosService) { public ResponseEntity getUserById( @ApiParam(required = true, value = "The UUID of the user to fetch information about") @PathVariable("userId") String userId) { - return this.userService.getEntityById(userId); + User userById = this.userService.getUserById(userId); + return ResponseEntity.ok(userById); } @ApiOperation(value = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json") public ResponseEntity getUserAll() { - return this.userService.getEntityAll(); + List entityAll = this.userService.getAllUsers(); + return ResponseEntity.ok(entityAll); } @ApiOperation(value = "POST a list of users, requires ADMIN role") @@ -96,7 +83,6 @@ public ResponseEntity updateUser(List users) { * @return */ @ApiOperation(value = "Retrieve information of current user") - @Transactional // TODO: Move this to the service layer @GetMapping(produces = "application/json", path = "/me") public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, @@ -106,12 +92,18 @@ public ResponseEntity getCurrentUser( } @ApiOperation(value = "Retrieve the queryTemplate of certain application by given application Id for the currentUser ") - @Transactional // TODO: Move this to the service layer @GetMapping(path = "/me/queryTemplate/{applicationId}", produces = "application/json") public ResponseEntity getQueryTemplate( @ApiParam(value = "Application Id for the returning queryTemplate") @PathVariable("applicationId") String applicationId) { - return this.userService.getQueryTemplate(applicationId); + Optional mergedTemplate = this.userService.getQueryTemplate(applicationId); + + if (mergedTemplate.isEmpty()) { + logger.error("getDefaultQueryTemplate() cannot find corresponding application by UUID: {}", applicationId); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + return PICSUREResponse.success(Map.of("queryTemplate", mergedTemplate.orElse(null))); } @ApiOperation(value = "Retrieve the queryTemplate of default application") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index b2f692431..892be8a60 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -26,52 +26,65 @@ @RequestMapping("/mapping") public class UserMetadataMappingWebController { - private final UserMetadataMappingService mappingService; + private final UserMetadataMappingService mappingService; - @Autowired + @Autowired public UserMetadataMappingWebController(UserMetadataMappingService mappingService) { this.mappingService = mappingService; } @ApiOperation(value = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) - @GetMapping(path = "{connectionId}", produces = "application/json") - public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { - return this.mappingService.getAllMappingsForConnection(connection); - } + @GetMapping(path = "{connectionId}", produces = "application/json") + public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { + return this.mappingService.getAllMappingsForConnection(connection); + } @ApiOperation(value = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) - @GetMapping(path = "/", produces = "application/json") - public ResponseEntity getAllMappings() { - List allMappings = mappingService.getAllMappings(); - return PICSUREResponse.success(allMappings); - } + @GetMapping(path = "/", produces = "application/json") + public ResponseEntity getAllMappings() { + List allMappings = mappingService.getAllMappings(); + return PICSUREResponse.success(allMappings); + } @ApiOperation(value = "POST a list of UserMetadataMappings, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PostMapping(path = "/", consumes = "application/json", produces = "application/json") - public ResponseEntity addMapping( + @PostMapping(path = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity addMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping in JSON format") List mappings) { - return mappingService.addMappings(mappings); - } + + try { + List userMetadataMappings = mappingService.addMappings(mappings); + return PICSUREResponse.success(userMetadataMappings); + } catch (IllegalArgumentException e) { + return PICSUREResponse.error(e.getMessage()); + } + } @ApiOperation(value = "Update a list of UserMetadataMappings, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PutMapping(path = "/", consumes = "application/json", produces = "application/json") - public ResponseEntity updateMapping( + @PutMapping(path = "/", consumes = "application/json", produces = "application/json") + public ResponseEntity updateMapping( @ApiParam(required = true, value = "A list of UserMetadataMapping with fields to be updated in JSON format") List mappings) { - return this.mappingService.updateEntity(mappings); - } + List userMetadataMappings = this.mappingService.updateUserMetadataMappings(mappings); + + if (userMetadataMappings == null || userMetadataMappings.isEmpty()) { + return PICSUREResponse.error("No UserMetadataMapping found with the given Ids"); + } + + return PICSUREResponse.success(userMetadataMappings); + } @ApiOperation(value = "DELETE an UserMetadataMapping by Id only if the UserMetadataMapping is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @DeleteMapping(path = "/{mappingId}", produces = "application/json") - public ResponseEntity removeById( + @DeleteMapping(path = "/{mappingId}", produces = "application/json") + public ResponseEntity removeById( @ApiParam(required = true, value = "A valid UserMetadataMapping Id") @PathVariable("mappingId") final String mappingId) { - return this.mappingService.removeEntityById(mappingId); - } + List userMetadataMappings = this.mappingService.removeMetadataMappingByIdAndRetrieveAll(mappingId); + return PICSUREResponse.success(userMetadataMappings); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java index 02253b5f4..50f871a2a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java @@ -1,68 +1,66 @@ package edu.harvard.hms.dbmi.avillach.auth.service; import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.List; +import java.util.Optional; import java.util.UUID; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @Service -public class PrivilegeService extends BaseEntityService { +public class PrivilegeService { private final static Logger logger = LoggerFactory.getLogger(PrivilegeService.class.getName()); private final PrivilegeRepository privilegeRepository; @Autowired - protected PrivilegeService(Class type, PrivilegeRepository privilegeRepository) { - super(type); + protected PrivilegeService(PrivilegeRepository privilegeRepository) { this.privilegeRepository = privilegeRepository; } @Transactional - public ResponseEntity deletePrivilegeByPrivilegeId(String privilegeId) { - Privilege privilege = this.privilegeRepository.getById(UUID.fromString(privilegeId)); + public List deletePrivilegeByPrivilegeId(String privilegeId) { + Optional privilege = this.privilegeRepository.findById(UUID.fromString(privilegeId)); // Get security context with spring security context SecurityContext securityContext = SecurityContextHolder.getContext(); // Get the principal name from the security context String principalName = securityContext.getAuthentication().getName(); - if (ADMIN.equals(privilege.getName())) { - logger.info("User: " + principalName - + ", is trying to remove the system admin privilege: " + ADMIN); - return PICSUREResponse.protocolError("System Admin privilege cannot be removed - uuid: " + privilege.getUuid().toString() - + ", name: " + privilege.getName()); + if (ADMIN.equals(privilege.get().getName())) { + logger.info("User: {}, is trying to remove the system admin privilege: " + ADMIN, principalName); + throw new RuntimeException("System Admin privilege cannot be removed - uuid: " + privilege.get().getUuid().toString() + + ", name: " + privilege.get().getName()); } - return removeEntityById(privilegeId, this.privilegeRepository); + this.privilegeRepository.deleteById(UUID.fromString(privilegeId)); + return this.getPrivilegesAll(); } - public ResponseEntity updateEntity(List privileges) { - return updateEntity(privileges, this.privilegeRepository); + public List updatePrivileges(List privileges) { + this.privilegeRepository.saveAll(privileges); + return this.getPrivilegesAll(); } - public ResponseEntity addEntity(List privileges) { - return addEntity(privileges, this.privilegeRepository); + public List addPrivileges(List privileges) { + return this.privilegeRepository.saveAll(privileges); } - public ResponseEntity getEntityAll() { - return getEntityAll(this.privilegeRepository); + public List getPrivilegesAll() { + return this.privilegeRepository.findAll(); } - public ResponseEntity getEntityById(String privilegeId) { - return getEntityById(privilegeId, this.privilegeRepository); + public Privilege getPrivilegeById(String privilegeId) { + return this.privilegeRepository.findById(UUID.fromString(privilegeId)).orElse(null); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index a531e0f6f..d0c41a9f5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -6,24 +6,20 @@ import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.BaseEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import javax.transaction.Transactional; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.logging.Logger; import java.util.stream.Collectors; @Service -public class RoleService extends BaseEntityService { +public class RoleService { private final static Logger logger = Logger.getLogger(RoleService.class.getName()); private final RoleRepository roleRepository; @@ -31,25 +27,27 @@ public class RoleService extends BaseEntityService { private final PrivilegeRepository privilegeRepo; @Autowired - protected RoleService(Class type, RoleRepository roleRepository, PrivilegeRepository privilegeRepo) { - super(type); + protected RoleService(RoleRepository roleRepository, PrivilegeRepository privilegeRepo) { this.roleRepository = roleRepository; this.privilegeRepo = privilegeRepo; } - public ResponseEntity getEntityById(String roleId) { - return getEntityById(roleId, roleRepository); + public ResponseEntity getRoleById(String roleId) { + Optional optionalRole = roleRepository.findById(UUID.fromString(roleId)); + if (optionalRole.isEmpty()) { + return PICSUREResponse.protocolError("Role is not found by given role ID: " + roleId); + } + return PICSUREResponse.success(optionalRole.get()); } - - public ResponseEntity getEntityAll() { - return getEntityAll(roleRepository); + public List getAllRoles() { + return roleRepository.findAll(); } @Transactional - public ResponseEntity addEntity(List roles) { + public List addRoles(List roles) { checkPrivilegeAssociation(roles); - return addEntity(roles, roleRepository); + return roleRepository.saveAll(roles); } /** @@ -61,8 +59,14 @@ public ResponseEntity addEntity(List roles) { private void checkPrivilegeAssociation(List roles) throws RuntimeException { for (Role role: roles){ if (role.getPrivileges() != null) { - Set privileges = new HashSet<>(); // TODO: Determine how we can fix this issue. The javax code does not work with java 21 in this case. - role.getPrivileges().stream().forEach(p -> privilegeRepo.addObjectToSet(privileges, privilegeRepo, p)); + Set privileges = new HashSet<>(); + for (Privilege p : role.getPrivileges()) { + Optional privilege = privilegeRepo.findById(p.getUuid()); + if (privilege.isEmpty()) { + throw new RuntimeException("Privilege not found - uuid: " + p.getUuid().toString()); + } + privileges.add(privilege.get()); + } role.setPrivileges(privileges); } } @@ -70,27 +74,40 @@ private void checkPrivilegeAssociation(List roles) throws RuntimeException } @Transactional - public ResponseEntity updateEntity(List roles) { + public List updateRoles(List roles) { checkPrivilegeAssociation(roles); - return updateEntity(roles, roleRepository); + return roleRepository.saveAll(roles); } @Transactional - public ResponseEntity removeEntityById(String roleId) { - Role role = roleRepository.getById(UUID.fromString(roleId)); + public Optional> removeRoleById(String roleId) { + Optional optionalRole = roleRepository.findById(UUID.fromString(roleId)); + + if (optionalRole.isEmpty()) { + return Optional.empty(); + } - // Get principal roles from security context SecurityContext context = SecurityContextHolder.getContext(); Set roles = context.getAuthentication().getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); + if (!SecurityRoles.contains(roles, SecurityRoles.PIC_SURE_TOP_ADMIN.getRole())){ + logger.info("User doesn't have PIC-SURE Top Admin role, can't remove any role"); + return Optional.empty(); + } + + roleRepository.deleteById(optionalRole.get().getUuid()); + return Optional.of(roleRepository.findAll()); + } - if (SecurityRoles.contains(roles, SecurityRoles.PIC_SURE_TOP_ADMIN.getRole())){ - logger.info("User has PIC-SURE Top Admin role, can remove any role"); - return PICSUREResponse.protocolError("Default System Role cannot be removed - uuid: " + role.getUuid().toString() - + ", name: " + role.getName()); + public void addObjectToSet(Set roles, Role t) { + // check if the role exists in the database + Role role = roleRepository.findById(t.getUuid()).orElse(null); + if (role == null) { + throw new RuntimeException("Role not found - uuid: " + t.getUuid().toString()); } - return removeEntityById(roleId, roleRepository); + + roles.add(t); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java index 625fc574b..62e079e0f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java @@ -3,33 +3,33 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service -public class AccessRuleService extends BaseEntityService{ +public class AccessRuleService { private final AccessRuleRepository accessRuleRepo; @Autowired protected AccessRuleService(Class type, AccessRuleRepository accessRuleRepo) { - super(type); this.accessRuleRepo = accessRuleRepo; } - public ResponseEntity getEntityById(String accessRuleId) { - return getEntityById(accessRuleId, accessRuleRepo); + public Optional getAccessRuleById(String accessRuleId) { + return accessRuleRepo.findById(UUID.fromString(accessRuleId)); } - public ResponseEntity getEntityAll() { - return getEntityAll(accessRuleRepo); + public List getAllAccessRules() { + return accessRuleRepo.findAll(); } - public ResponseEntity addEntity(List accessRules) { + public List addAccessRule(List accessRules) { accessRules.forEach(accessRule -> { if (accessRule.getEvaluateOnlyByGates() == null) accessRule.setEvaluateOnlyByGates(false); @@ -44,15 +44,16 @@ public ResponseEntity addEntity(List accessRules) { accessRule.setGateAnyRelation(false); }); - return addEntity(accessRules, accessRuleRepo); + return this.accessRuleRepo.saveAll(accessRules); } - public ResponseEntity updateEntity(List accessRules) { - return updateEntity(accessRules, accessRuleRepo); + public List updateAccessRules(List accessRules) { + return this.accessRuleRepo.saveAll(accessRules); } @Transactional - public ResponseEntity removeEntityById(String accessRuleId) { - return removeEntityById(accessRuleId, accessRuleRepo); + public List removeAccessRuleById(String accessRuleId) { + this.accessRuleRepo.deleteById(UUID.fromString(accessRuleId)); + return this.accessRuleRepo.findAll(); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 56deb6d0c..e576d4ff2 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -19,7 +19,7 @@ import java.util.*; @Service -public class ApplicationService extends BaseEntityService { +public class ApplicationService { private final static Logger logger = LoggerFactory.getLogger(ApplicationService.class); private final ApplicationRepository applicationRepo; @@ -30,7 +30,6 @@ public class ApplicationService extends BaseEntityService { @Autowired protected ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo) { - super(Application.class); this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; } @@ -41,18 +40,18 @@ protected ApplicationService(ApplicationRepository applicationRepo, PrivilegeRep * @param applicationId the ID of the entity to retrieve * @return a ResponseEntity representing the result of the operation */ - public ResponseEntity getEntityById(String applicationId) { - return getEntityById(applicationId, applicationRepo); + public Optional getApplicationByID(String applicationId) { + return this.applicationRepo.findById(UUID.fromString(applicationId)); } - public ResponseEntity getEntityAll() { - return getEntityAll(applicationRepo); + public List getAllApplications() { + return this.applicationRepo.findAll(); } @Transactional - public ResponseEntity addNewApplications(List applications) { + public List addNewApplications(List applications) { checkAssociation(applications); - List appEntities = addOrUpdate(applications, true, applicationRepo); + List appEntities = this.applicationRepo.saveAll(applications); for (Application application : appEntities) { try { application.setToken( @@ -63,57 +62,61 @@ public ResponseEntity addNewApplications(List applications) { } } - return updateEntity(appEntities, applicationRepo); + return this.applicationRepo.saveAll(appEntities); } @Transactional - public ResponseEntity deleteApplicationById(String applicationId) { - Application application = applicationRepo.getById(UUID.fromString(applicationId)); - if (application == null) { - logger.error("deleteApplicationById() cannot find the application by applicationId: " + applicationId); + public List deleteApplicationById(String applicationId) { + Optional application = applicationRepo.findById(UUID.fromString(applicationId)); + + if (application.isEmpty()) { + logger.error("deleteApplicationById() cannot find the application by applicationId: {}", applicationId); throw new IllegalArgumentException("Cannot find application by the given applicationId: " + applicationId); } - return removeEntityById(applicationId, applicationRepo); + this.applicationRepo.delete(application.get()); + return this.applicationRepo.findAll(); } - public ResponseEntity updateApplications(List applications) { + public List updateApplications(List applications) { checkAssociation(applications); - return updateEntity(applications, applicationRepo); + return this.applicationRepo.saveAll(applications); } - public ResponseEntity refreshApplicationToken(String applicationId) { - Application application = applicationRepo.getById(UUID.fromString(applicationId)); - if (application == null) { - logger.error("refreshApplicationToken() cannot find the application by applicationId: " + applicationId); + public String refreshApplicationToken(String applicationId) { + Optional application = applicationRepo.findById(UUID.fromString(applicationId)); + + if (application.isEmpty()) { + logger.error("refreshApplicationToken() cannot find the application by applicationId: {}", applicationId); throw new IllegalArgumentException("Cannot find application by the given applicationId: " + applicationId); } - String newApplicationToken = generateApplicationToken(application); + String newApplicationToken = generateApplicationToken(application.orElse(null)); try { - application.setToken( + application.get().setToken( newApplicationToken ); - applicationRepo.merge(application); + this.applicationRepo.save(application.get()); } catch (Exception e) { logger.error("", e); } - return PICSUREResponse.success(Map.of("token", newApplicationToken)); + return newApplicationToken; } - private void checkAssociation(List applications) { //TODO: We need to refactor this into a service class + private void checkAssociation(List applications) { for (Application application : applications) { if (application.getPrivileges() != null) { Set privileges = new HashSet<>(); application.getPrivileges().forEach(p -> { - Privilege privilege = privilegeRepo.getById(p.getUuid()); - if (privilege != null) { + Optional optionalPrivilege = privilegeRepo.findById(p.getUuid()); + if (optionalPrivilege.isPresent()) { + Privilege privilege = optionalPrivilege.get(); privilege.setApplication(application); privileges.add(privilege); } else { - logger.error("Didn't find privilege by uuid: " + p.getUuid()); + logger.error("Didn't find privilege by uuid: {}", p.getUuid()); } }); application.setPrivileges(privileges); @@ -122,10 +125,10 @@ private void checkAssociation(List applications) { //TODO: We need } } - public String generateApplicationToken(Application application) { // TODO: Refactor this into a new service class + public String generateApplicationToken(Application application) { if (application == null || application.getUuid() == null) { logger.error("generateApplicationToken() application is null or uuid is missing to generate the application token"); - throw new PropertyNotFoundException("Cannot generate application token, please contact admin"); + throw new NullPointerException("Cannot generate application token, please contact admin"); } return JWTUtil.createJwtToken( diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 7effdc603..972141952 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -1,24 +1,24 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; +import java.io.IOException; import java.util.*; -import javax.mail.MessagingException; - +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; -import org.apache.http.Header; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.message.BasicHeader; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import edu.harvard.dbmi.avillach.util.HttpClientUtil; -import edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -45,15 +45,26 @@ public class AuthenticationService { private final UserService userService; private static final int AUTH_RETRY_LIMIT = 3; + private final String deniedEmailEnabled; + + private final String auth0host; + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ConnectionRepository connectionRepository; + @Autowired - public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, BasicMailService basicMailService, UserService userService) { + public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, BasicMailService basicMailService, UserService userService, + @Value("${application.denied.email.enabled}") String deniedEmailEnabled, @Value("${application.auth0.host}") String auth0host, ConnectionRepository connectionRepository) { this.matchingService = matchingService; this.userRepository = userRepository; this.basicMailService = basicMailService; this.userService = userService; + this.deniedEmailEnabled = deniedEmailEnabled; + this.auth0host = auth0host; + this.connectionRepository = connectionRepository; } - public ResponseEntity getToken(Map authRequest) { + public ResponseEntity getToken(Map authRequest) throws IOException { String accessToken = authRequest.get("access_token"); String redirectURI = authRequest.get("redirectURI"); @@ -80,17 +91,16 @@ public ResponseEntity getToken(Map authRequest) { throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); } + Connection connection = connectionRepository.findById(UUID.fromString(connectionId)).orElseThrow(() -> new NotAuthorizedException("No connection found for connection_id " + connectionId)); //Do we have this user already? - User user = userRepository.findBySubjectAndConnection(userId, connectionId); + User user = userRepository.findBySubjectAndConnection(userId, connection); if (user == null) { //Try to match user = matchingService.matchTokenToUser(userInfo); if (user == null) { - if (JAXRSConfiguration.deniedEmailEnabled.startsWith("true")) { + if (this.deniedEmailEnabled.startsWith("true")) { try { basicMailService.sendDeniedAccessEmail(userInfo); - } catch (MessagingException e) { // TODO: We need to remove the javax.mail dependency - logger.warn("Failed to send user access denied email: ", e); } catch (jakarta.mail.MessagingException e) { throw new RuntimeException(e); } @@ -110,24 +120,23 @@ public ResponseEntity getToken(Map authRequest) { return PICSUREResponse.success(responseMap); } - private JsonNode retrieveUserInfo(String accessToken) { - String auth0UserInfoURI = JAXRSConfiguration.auth0host + "/userinfo"; - Header[] headers = { - new BasicHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE), - new BasicHeader("Authorization", "Bearer " + accessToken) - }; + private JsonNode retrieveUserInfo(String accessToken) throws IOException { + String auth0UserInfoURI = this.auth0host + "/userinfo"; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + accessToken); + JsonNode auth0Response = null; - RequestConfig requestConfig = createRequestConfigWithCustomTimeout(); +// RequestConfig requestConfig = createRequestConfigWithCustomTimeout(); // TODO: How can we do this with the Spring rest client? for (int i = 1; i <= AUTH_RETRY_LIMIT && auth0Response == null; i++) { try { - auth0Response = HttpClientUtil.simpleGetWithConfig( + ResponseEntity response = RestClientUtil.retrieveGetResponse( auth0UserInfoURI, - JAXRSConfiguration.client, - JAXRSConfiguration.objectMapper, - requestConfig, headers ); + + auth0Response = objectMapper.readTree(response.getBody()); } catch (Exception e) { if (i < AUTH_RETRY_LIMIT) { logger.warn("Failed to authenticate. Retrying"); @@ -139,13 +148,13 @@ private JsonNode retrieveUserInfo(String accessToken) { } return auth0Response; } - - private RequestConfig createRequestConfigWithCustomTimeout() { - int timeoutMs = 2000; // 2 seconds, default is 3 seconds - return RequestConfig.custom() - .setConnectionRequestTimeout(timeoutMs) - .setConnectTimeout(timeoutMs) - .setSocketTimeout(timeoutMs) - .build(); - } +// TODO : This method is not used. we need to investigate if it is needed or not +// private RequestConfig createRequestConfigWithCustomTimeout() { +// int timeoutMs = 2000; // 2 seconds, default is 3 seconds +// return RequestConfig.custom() +// .setConnectionRequestTimeout(timeoutMs) +// .setConnectTimeout(timeoutMs) +// .setSocketTimeout(timeoutMs) +// .build(); +// } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java deleted file mode 100644 index 50607851e..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BaseEntityService.java +++ /dev/null @@ -1,266 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.service.impl; - -import edu.harvard.dbmi.avillach.data.entity.BaseEntity; -import edu.harvard.dbmi.avillach.data.repository.BaseRepository; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import javax.validation.constraints.NotNull; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.UUID; - -/** - *

Template for basic operations for REST entity classes.

- * @param - */ -public abstract class BaseEntityService { - - private final Logger logger; - - protected final Class type; - - private final String auditLogName; - - - protected BaseEntityService(Class type){ - this.type = type; - auditLogName = type.getSimpleName().equals(User.class.getSimpleName()) ? "ADMIN_LOG" : "SUPER_ADMIN_LOG"; - logger = LoggerFactory.getLogger(type); - } - - public ResponseEntity getEntityById(String id, BaseRepository baseRepository){ - SecurityContext securityContext = SecurityContextHolder.getContext(); - String principalName = securityContext.getAuthentication().getName(); - logger.info("User: " + principalName - + " Looking for " + type.getSimpleName() + - " by ID: " + id + "..."); - - T t = (T) baseRepository.getById(UUID.fromString(id)); - - if (t == null) - return PICSUREResponse.protocolError(type.getSimpleName() + " is not found by given " + - type.getSimpleName().toLowerCase() + " ID: " + id); - else - return PICSUREResponse.success(t); - } - - public ResponseEntity getEntityAll(BaseRepository baseRepository){ - SecurityContext securityContext = SecurityContextHolder.getContext(); - String principalName = securityContext.getAuthentication().getName(); - logger.info("User: " + principalName + - " Getting all " + type.getSimpleName() + - "s..."); - List ts = null; - - ts = baseRepository.list(); - - if (ts == null) - return PICSUREResponse.applicationError("Error occurs when listing all " - + type.getSimpleName() + - "s."); - - return PICSUREResponse.success(ts); - } - - public ResponseEntity addEntity(List entities, BaseRepository baseRepository){ - SecurityContext securityContext = SecurityContextHolder.getContext(); - String username = securityContext.getAuthentication().getName(); - if (entities == null || entities.isEmpty()) - return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + - " to be added."); - - logger.info("User: " + username + " is trying to add a list of " - + type.getSimpleName()); - - List addedEntities = addOrUpdate(entities, true, baseRepository); - for(T entity : addedEntities) { - logger.info(auditLogName + " ___ " + username + " ___ created ___ "+ entity.toString() + " ___ "); - } - - if (addedEntities.isEmpty()) - return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + - "(s) has been added."); - - if (addedEntities.size() < entities.size()) - return PICSUREResponse.success(Integer.toString(entities.size()-addedEntities.size()) - + " " + type.getSimpleName().toLowerCase() + - "s are NOT operated." + - " Added " + type.getSimpleName().toLowerCase() + - "s are as follow: ", addedEntities); - - return PICSUREResponse.success("All " + type.getSimpleName().toLowerCase() + - "s are added.", addedEntities); - } - - public ResponseEntity updateEntity(List entities, BaseRepository baseRepository){ - if (entities == null || entities.isEmpty()) - return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + - " to be updated."); - - SecurityContext securityContext = SecurityContextHolder.getContext(); - String username = securityContext.getAuthentication().getName(); - logger.info("User: " + username + " is trying to update a list of " - + type.getSimpleName()); - - List addedEntities = addOrUpdate(entities, false, baseRepository); - - if (addedEntities.isEmpty()) - return PICSUREResponse.protocolError("No " + type.getSimpleName().toLowerCase() + - "(s) has been updated."); - - for(T entity : addedEntities) { - logger.info(auditLogName + " ___ " + username + " ___ updated ___ "+ entity.toString() + " ___ "); - } - - if (addedEntities.size() < entities.size()) - return PICSUREResponse.success(Integer.toString(entities.size()-addedEntities.size()) - + " " +type.getSimpleName().toLowerCase()+ - "s are NOT operated." + - " Updated " + type.getSimpleName().toLowerCase() + - "(s) are as follow: ", addedEntities); - - return PICSUREResponse.success("All " + type.getSimpleName().toLowerCase() + - "(s) are updated.", addedEntities); - - } - - protected List addOrUpdate(@NotNull List entities, boolean forAdd, BaseRepository baseRepository){ - List operatedEntities = new ArrayList<>(); - for (T t : entities){ - boolean dbContacted = false; - if (forAdd) { - t.setUuid(null); - baseRepository.persist(t); - dbContacted = true; - } - else { - if (updateAllAttributes(t, baseRepository)){ - dbContacted = true; - } - } - - if (!dbContacted || t.getUuid() == null || baseRepository.getById(t.getUuid()) == null){ - continue; - } - - t = (T) baseRepository.getById(t.getUuid()); - operatedEntities.add(t); - } - return operatedEntities; - } - - private boolean updateAllAttributes(T detachedT, BaseRepository baseRepository){ - UUID uuid = detachedT.getUuid(); - if (uuid == null) - return false; - - T retrievedT = baseRepository.getById(uuid); - - if (retrievedT == null) - return false; - - try { - for (Field field : detachedT.getClass().getDeclaredFields()){ - String fieldName = field.getName(); - fieldName = fieldName.substring(0,1).toUpperCase() + fieldName.substring(1); - String getter = "get" + fieldName; - Class type = field.getType(); - if (type == boolean.class || type == null) { - getter = "is" + fieldName; - } - - String setter = "set" + fieldName; - - Object value = detachedT.getClass().getMethod(getter).invoke(detachedT); - Object retrievedValue = retrievedT.getClass().getMethod(getter).invoke(retrievedT); - - /** - * so the problem trying to solve here is: - * There is an POST_COLLECTION_UPDATE event will be triggered when - * later merging the entity. Even the performance down to the sql - * statement is not being affected, we still need to take care here - */ - boolean inputCollectionMatchedDBCollection = true; - String simpleName = type.getSimpleName(); - if (simpleName.contains("Set") || simpleName.contains("List")) { - - if (retrievedValue != null){ - Collection retrievedCollection = (Collection)retrievedValue; - Collection detachedCollection = (Collection)value; - - if (retrievedCollection != null && detachedCollection != null){ - for (BaseEntity baseEntity : retrievedCollection) { - if (!detachedCollection.contains(baseEntity)) { - inputCollectionMatchedDBCollection = false; - break; - } - } - for (BaseEntity baseEntity : detachedCollection) { - if (!retrievedCollection.contains(baseEntity)) { - inputCollectionMatchedDBCollection = false; - break; - } - } - } else { - inputCollectionMatchedDBCollection = false; - } - } else { - inputCollectionMatchedDBCollection = false; - } - } else { - inputCollectionMatchedDBCollection = false; - } - - if (value != null && !inputCollectionMatchedDBCollection) { - detachedT.getClass().getMethod(setter, field.getType()) - .invoke(retrievedT, value); - } - } - } catch (IllegalArgumentException | ReflectiveOperationException ex /* | IntrospectionException ex */){ - ex.printStackTrace(); - return false; - } - - baseRepository.merge(retrievedT); - - return true; - } - - public ResponseEntity removeEntityById(String id, BaseRepository baseRepository) { - SecurityContext securityContext = SecurityContextHolder.getContext(); - String username = securityContext.getAuthentication().getName(); - logger.info("User: " + username + " is trying to REMOVE an entity: " - + type.getSimpleName() + ", by uuid: " + id); - - UUID uuid = UUID.fromString(id); - T t = (T) baseRepository.getById(uuid); - if (t == null) - return PICSUREResponse.protocolError(type.getSimpleName() + - " is not found by " + type.getSimpleName().toLowerCase() + - " ID"); - - baseRepository.remove(t); - logger.info(auditLogName + " ___ " + username + " ___ updated ___ "+ t.toString() + " ___ "); - - - t = (T) baseRepository.getById(uuid); - if (t != null){ - return PICSUREResponse.applicationError("Cannot delete the " + type.getSimpleName().toLowerCase()+ - " by id: " + id); - } - - return PICSUREResponse.success("Successfully deleted " + type.getSimpleName().toLowerCase() + - " by id: " + id + ", listing rest of the " + type.getSimpleName().toLowerCase() + - "(s) as below" - , baseRepository.list()); - - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java index 80039c41e..2a4093e16 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java @@ -80,7 +80,7 @@ public void sendUsersAccessEmail(User user) throws MessagingException { if (this.accessGrantEmailSubject != null && !this.accessGrantEmailSubject.isEmpty() && !this.accessGrantEmailSubject.equals("none")){ subject = this.accessGrantEmailSubject; } - sendEmail(accessTemplate, user.getEmail(),subject, new AccessEmail(user)); + sendEmail(accessTemplate, user.getEmail(),subject, new AccessEmail(user, this.systemName)); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java index 1d666a635..2812b87df 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java @@ -1,51 +1,53 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service -public class ConnectionWebService extends BaseEntityService { +public class ConnectionWebService { private final ConnectionRepository connectionRepo; @Autowired - protected ConnectionWebService(Class type, ConnectionRepository connectionRepo) { - super(type); + protected ConnectionWebService(ConnectionRepository connectionRepo) { this.connectionRepo = connectionRepo; } - public ResponseEntity addEntity(List connections){ + public List addConnection(List connections) throws IllegalArgumentException { for (Connection c : connections){ if (c.getSubPrefix() == null || c.getRequiredFields() == null || c.getLabel() == null || c.getId() == null){ - return PICSUREResponse.protocolError("Id, Label, Subprefix, and RequiredFields cannot be null"); + throw new IllegalArgumentException("Id, Label, Subprefix, and RequiredFields cannot be null"); } - Connection conn = connectionRepo.findConnectionById(c.getId()); - if (conn != null){ - return PICSUREResponse.protocolError("Id must be unique, a connection with id " + c.getId() + " already exists in the database"); + Optional conn = connectionRepo.findById(UUID.fromString(c.getId())); + if (conn.isPresent()){ + throw new IllegalArgumentException("Id must be unique, a connection with id " + c.getId() + " already exists in the database"); } } - return addEntity(connections, connectionRepo); // TODO: This should be moved to an actual service class. We shouldn't need to pass a repo to the service class + + return this.connectionRepo.saveAll(connections); } - public ResponseEntity getEntityById(String connectionId) { - return getEntityById(connectionId, connectionRepo); + public Connection getConnectionById(String connectionId) { + return this.connectionRepo.findById(UUID.fromString(connectionId)) + .orElseThrow(() -> new IllegalArgumentException("Connection with id " + connectionId + " not found")); } - public ResponseEntity getEntityAll() { - return getEntityAll(connectionRepo); + public List getAllConnections() { + return this.connectionRepo.findAll(); } - public ResponseEntity updateEntity(List connections) { - return updateEntity(connections, connectionRepo); + public List updateConnections(List connections) { + return this.connectionRepo.saveAll(connections); } - public ResponseEntity removeEntityById(String connectionId) { - return removeEntityById(connectionId, connectionRepo); + public List removeConnectionById(String connectionId) { + this.connectionRepo.deleteById(UUID.fromString(connectionId)); + return this.getAllConnections(); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java index 2f133aefc..fb30a0c29 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java @@ -2,25 +2,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.dbmi.avillach.util.HttpClientUtil; import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.*; +import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; import jakarta.annotation.PostConstruct; -import org.apache.http.Header; -import org.apache.http.entity.StringEntity; -import org.apache.http.message.BasicHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; - -import +import org.springframework.web.client.RestClientException; import java.io.File; import java.io.IOException; @@ -28,6 +23,7 @@ @Service public class FENCEAuthenticationService { + private final Logger logger = LoggerFactory.getLogger(FENCEAuthenticationService.class); private final ObjectMapper objectMapper = new ObjectMapper(); @@ -38,7 +34,7 @@ public class FENCEAuthenticationService { private final ConnectionRepository connectionRepo; - private final AccessRuleRepository accessruleRepo; + private final AccessRuleRepository accessRuleRepo; private final ApplicationRepository applicationRepo; @@ -62,12 +58,9 @@ public class FENCEAuthenticationService { // ----------------- Template Path ----------------- private final String templatePath; - // ----------------- HTTP Client ----------------- - private final RestClient restClient; - @Autowired public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRepo, ConnectionRepository connectionRepo, - AccessRuleRepository accessruleRepo, ApplicationRepository applicationRepo, + AccessRuleRepository accessRuleRepo, ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, UserService userService, @Value("${application.idp.provider}") String idpProviderUri, @Value("${application.fence.client.id") String fenceClientId, @@ -80,7 +73,7 @@ public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRe this.userRepo = userRepo; this.roleRepo = roleRepo; this.connectionRepo = connectionRepo; - this.accessruleRepo = accessruleRepo; + this.accessRuleRepo = accessRuleRepo; this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; this.userService = userService; @@ -92,30 +85,35 @@ public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRe fence_harmonized_concept_path = fenceHarmonizedConceptPath; fence_standard_access_rules = fenceStandardAccessRules; this.templatePath = templatePath; - this.restClient = RestClient.builder() - .requestFactory(new HttpComponentsClientHttpRequestFactory()) - .build(); } @PostConstruct public void initializeFenceService() { - picSureApp = applicationRepo.getUniqueResultByColumn("name", "PICSURE"); - fenceConnection = connectionRepo.getUniqueResultByColumn("label", "FENCE"); + picSureApp = applicationRepo.findByName("PICSURE"); + fenceConnection = connectionRepo.findByLabel("FENCE"); fenceMapping = getFENCEMapping(); } private JsonNode getFENCEUserProfile(String access_token) { logger.debug("getFENCEUserProfile() starting"); - List
headers = new ArrayList<>(); - headers.add(new BasicHeader("Authorization", "Bearer " + access_token)); + logger.debug("getFENCEUserProfile() getting user profile from uri:{}/user/user", this.idp_provider_uri); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + access_token); + + JsonNode fence_user_profile_response = null; + try { + ResponseEntity response = RestClientUtil.retrieveGetResponse( + this.idp_provider_uri + "/user/user", + headers + ); - logger.debug("getFENCEUserProfile() getting user profile from uri:" + this.idp_provider_uri + "/user/user"); - JsonNode fence_user_profile_response = HttpClientUtil.simpleGet( - this.idp_provider_uri + "/user/user", - this.client, - this.objectMapper, - headers.toArray(new Header[headers.size()]) - ); + fence_user_profile_response = objectMapper.readTree(response.getBody()); + } catch (RestClientException ex) { + logger.error("getFENCEUserProfile() failed to call FENCE user service, {}", ex.getMessage()); + } catch (IOException e) { + throw new RuntimeException(e); + } logger.debug("getFENCEUserProfile() finished, returning user profile" + fence_user_profile_response.asText()); return fence_user_profile_response; @@ -124,12 +122,12 @@ private JsonNode getFENCEUserProfile(String access_token) { private JsonNode getFENCEAccessToken(String fence_code) { logger.debug("getFENCEAccessToken() starting, using FENCE code"); - List
headers = new ArrayList<>(); Base64.Encoder encoder = Base64.getEncoder(); String fence_auth_header = this.fence_client_id + ":" + this.fence_client_secret; - headers.add(new BasicHeader("Authorization", - "Basic " + encoder.encodeToString(fence_auth_header.getBytes()))); - headers.add(new BasicHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8")); + + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/x-www-form-urlencoded"); + headers.add("Authorization", "Basic " + encoder.encodeToString(fence_auth_header.getBytes())); // Build the request body, as JSON String query_string = @@ -141,17 +139,17 @@ private JsonNode getFENCEAccessToken(String fence_code) { JsonNode resp = null; try { - resp = HttpClientUtil.simplePost( + ResponseEntity response = RestClientUtil.retrievePostResponse( fence_token_url, - new StringEntity(query_string), - this.client, - this.objectMapper, - headers.toArray(new Header[headers.size()]) + headers, + query_string ); + + resp = objectMapper.readTree(response.getBody()); } catch (Exception ex) { - logger.error("getFENCEAccessToken() failed to call FENCE token service, " + ex.getMessage()); + logger.error("getFENCEAccessToken() failed to call FENCE token service, {}", ex.getMessage()); } - logger.debug("getFENCEAccessToken() finished. " + resp.asText()); + logger.debug("getFENCEAccessToken() finished. {}", resp.asText()); return resp; } @@ -165,12 +163,12 @@ public ResponseEntity getFENCEProfile(Map authRequest) { try { logger.debug("getFENCEProfile() query FENCE for user profile with code"); fence_user_profile = getFENCEUserProfile(getFENCEAccessToken(fence_code).get("access_token").asText()); - logger.debug("getFENCEProfile() user profile structure:" + fence_user_profile.asText()); - logger.debug("getFENCEProfile() .username:" + fence_user_profile.get("username")); - logger.debug("getFENCEProfile() .user_id:" + fence_user_profile.get("user_id")); - logger.debug("getFENCEProfile() .email:" + fence_user_profile.get("email")); + logger.debug("getFENCEProfile() user profile structure:{}", fence_user_profile.asText()); + logger.debug("getFENCEProfile() .username:{}", fence_user_profile.get("username")); + logger.debug("getFENCEProfile() .user_id:{}", fence_user_profile.get("user_id")); + logger.debug("getFENCEProfile() .email:{}", fence_user_profile.get("email")); } catch (Exception ex) { - logger.error("getFENCEToken() could not retrieve the user profile from the auth provider, because " + ex.getMessage(), ex); + logger.error("getFENCEToken() could not retrieve the user profile from the auth provider, because {}", ex.getMessage(), ex); throw new NotAuthorizedException("Could not get the user profile " + "from the Gen3 authentication provider." + ex.getMessage()); } @@ -180,13 +178,10 @@ public ResponseEntity getFENCEProfile(Map authRequest) { // Create or retrieve the user profile from our database, based on the the key // in the Gen3/FENCE profile current_user = createUserFromFENCEProfile(fence_user_profile); - logger.info("getFENCEProfile() saved details for user with e-mail:" - + current_user.getEmail() - + " and subject:" - + current_user.getSubject()); + logger.info("getFENCEProfile() saved details for user with e-mail:{} and subject:{}", current_user.getEmail(), current_user.getSubject()); } catch (Exception ex) { - logger.error("getFENCEToken() Could not persist the user information, because " + ex.getMessage()); + logger.error("getFENCEToken() Could not persist the user information, because {}", ex.getMessage()); throw new NotAuthorizedException("The user details could not be persisted. Please contact the administrator."); } @@ -219,10 +214,10 @@ public ResponseEntity getFENCEProfile(Map authRequest) { } } try { - userRepo.changeRole(current_user, current_user.getRoles()); - logger.debug("upsertRole() updated user, who now has " + current_user.getRoles().size() + " roles."); + userService.changeRole(current_user, current_user.getRoles()); + logger.debug("upsertRole() updated user, who now has {} roles.", current_user.getRoles().size()); } catch (Exception ex) { - logger.error("upsertRole() Could not add roles to user, because " + ex.getMessage()); + logger.error("upsertRole() Could not add roles to user, because {}", ex.getMessage()); } HashMap claims = new HashMap(); claims.put("name", fence_user_profile.get("name")); @@ -254,13 +249,11 @@ private User createUserFromFENCEProfile(JsonNode node) { new_user.setConnection(fenceConnection); logger.debug("createUserFromFENCEProfile() finished setting fields"); - User actual_user = userRepo.findOrCreate(new_user); - // Clear current set of roles every time we create or retrieve a user - actual_user.setRoles(new HashSet<>()); + new_user.setRoles(new HashSet<>()); logger.debug("createUserFromFENCEProfile() cleared roles"); - userRepo.persist(actual_user); + User actual_user = userRepo.save(new_user); logger.debug("createUserFromFENCEProfile() finished, user record inserted"); return actual_user; } @@ -275,7 +268,7 @@ private User createUserFromFENCEProfile(JsonNode node) { */ private boolean upsertRole(User u, String roleName, String roleDescription) { boolean status = false; - logger.debug("upsertRole() starting for user subject:" + u.getSubject()); + logger.debug("upsertRole() starting for user subject:{}", u.getSubject()); // Get the User's list of Roles. The first time, this will be an empty Set. // This method is called for every Role, and the User's list of Roles will @@ -283,7 +276,7 @@ private boolean upsertRole(User u, String roleName, String roleDescription) { try { Role r = null; // Create the Role in the repository, if it does not exist. Otherwise, add it. - Role existing_role = roleRepo.getUniqueResultByColumn("name", roleName); + Role existing_role = roleRepo.findByName(roleName); if (existing_role != null) { // Role already exists logger.info("upsertRole() role already exists"); @@ -296,13 +289,13 @@ private boolean upsertRole(User u, String roleName, String roleDescription) { // Since this is a new Role, we need to ensure that the // corresponding Privilege (with gates) and AccessRule is added. //r.setPrivileges(upsertPrivilege(u, r)); - roleRepo.persist(r); + roleRepo.save(r); logger.info("upsertRole() created new role"); } u.getRoles().add(r); status = true; } catch (Exception ex) { - logger.error("upsertRole() Could not inser/update role " + roleName + " to repo, because " + ex.getMessage()); + logger.error("upsertRole() Could not inser/update role {} to repo, because {}", roleName, ex.getMessage()); } @@ -312,31 +305,30 @@ private boolean upsertRole(User u, String roleName, String roleDescription) { private Set upsertPrivilege(User u, Role r) { String roleName = r.getName(); - logger.info("upsertPrivilege() starting, adding privilege to role " + roleName); + logger.info("upsertPrivilege() starting, adding privilege to role {}", roleName); String[] parts = roleName.split("_"); String project_name = parts[1]; String consent_group = parts[2]; - // TODO: How to alert when the mapping is not in the list. String concept_path = fenceMapping.get(project_name); // Get privilege and assign it to this role. String privilegeName = r.getName().replaceFirst("FENCE_*", "PRIV_FENCE_"); - logger.info("upsertPrivilege() Looking for privilege, with name : " + privilegeName); + logger.info("upsertPrivilege() Looking for privilege, with name : {}", privilegeName); Set privs = r.getPrivileges(); if (privs == null) { privs = new HashSet(); } - Privilege p = privilegeRepo.getUniqueResultByColumn("name", privilegeName); + Privilege p = privilegeRepo.findByName(privilegeName); if (p != null) { - logger.info("upsertPrivilege() Assigning privilege " + p.getName() + " to role " + r.getName()); + logger.info("upsertPrivilege() Assigning privilege {} to role {}", p.getName(), r.getName()); privs.add(p); } else { logger.info("upsertPrivilege() This is a new privilege"); - logger.info("upsertPrivilege() project:" + project_name + " consent_group:" + consent_group + " concept_path:" + concept_path); + logger.info("upsertPrivilege() project:{} consent_group:{} concept_path:{}", project_name, consent_group, concept_path); // Add new privilege PRIV_FENCE_phs######_c# and PRIV_FENCE_phs######_c#_HARMONIZED privs.add(createNewPrivilege(project_name, consent_group, concept_path, false)); @@ -357,7 +349,6 @@ private Privilege createNewPrivilege(String project_name, String consent_group, priv.setQueryScope(queryScopeConceptPath); String consent_concept_path = fence_consent_group_concept_path; - // TOOD: Change this to a mustache template String queryTemplateText = "{\"categoryFilters\": {\"" + consent_concept_path + "\":\"" @@ -371,22 +362,20 @@ private Privilege createNewPrivilege(String project_name, String consent_group, priv.setQueryScope(queryScopeConceptPath); AccessRule ar = upsertAccessRule(project_name, consent_group); - if (ar != null) { - Set accessrules = new HashSet(); - accessrules.add(ar); - // Add additionanl access rules - for (String arName : fence_standard_access_rules.split(",")) { - if (arName.startsWith("AR_")) { - logger.info("Adding AccessRule " + arName + " to privilege " + priv.getName()); - accessrules.add(accessruleRepo.getUniqueResultByColumn("name", arName)); - } + Set accessRules = new HashSet(); + accessRules.add(ar); + // Add additionanl access rules + for (String arName : fence_standard_access_rules.split(",")) { + if (arName.startsWith("AR_")) { + logger.info("Adding AccessRule {} to privilege {}", arName, priv.getName()); + accessRules.add(accessRuleRepo.findByName(arName)); } - priv.setAccessRules(accessrules); - logger.info("createNewPrivilege() Added " + accessrules.size() + " access_rules to privilege"); } + priv.setAccessRules(accessRules); + logger.info("createNewPrivilege() Added {} access_rules to privilege", accessRules.size()); - privilegeRepo.persist(priv); - logger.info("createNewPrivilege() Added new privilege " + priv.getName() + " to DB"); + privilegeRepo.save(priv); + logger.info("createNewPrivilege() Added new privilege {} to DB", priv.getName()); } catch (Exception ex) { ex.printStackTrace(); logger.error("createNewPrivilege() could not save privilege"); @@ -397,21 +386,20 @@ private Privilege createNewPrivilege(String project_name, String consent_group, private AccessRule upsertAccessRule(String project_name, String consent_group) { logger.debug("upsertAccessRule() starting"); String ar_name = "AR_" + project_name + "_" + consent_group; - AccessRule ar = accessruleRepo.getUniqueResultByColumn("name", ar_name); + AccessRule ar = accessRuleRepo.findByName(ar_name); if (ar != null) { - logger.info("upsertAccessRule() AccessRule " + ar_name + " already exists."); + logger.info("upsertAccessRule() AccessRule {} already exists.", ar_name); return ar; } - logger.info("upsertAccessRule() Creating new access rule " + ar_name); + logger.info("upsertAccessRule() Creating new access rule {}", ar_name); ar = new AccessRule(); ar.setName(ar_name); ar.setDescription("FENCE AR for " + project_name + "/" + consent_group); - StringBuilder ruleText = new StringBuilder(); - ruleText.append("$..categoryFilters.['"); - ruleText.append(fence_consent_group_concept_path); - ruleText.append("']"); - ar.setRule(ruleText.toString()); + String ruleText = "$..categoryFilters.['" + + fence_consent_group_concept_path + + "']"; + ar.setRule(ruleText); ar.setType(AccessRule.TypeNaming.ALL_EQUALS); ar.setValue(project_name + "." + consent_group); ar.setCheckMapKeyOnly(false); @@ -423,16 +411,13 @@ private AccessRule upsertAccessRule(String project_name, String consent_group) { Set gates = new HashSet(); for (String accessruleName : fence_standard_access_rules.split("\\,")) { if (accessruleName.startsWith("GATE_")) { - logger.info("upsertAccessRule() Assign gate " + accessruleName + - " to access_rule " + ar.getName()); - gates.add(accessruleRepo.getUniqueResultByColumn("name", accessruleName)); - } else { - continue; + logger.info("upsertAccessRule() Assign gate {} to access_rule {}", accessruleName, ar.getName()); + gates.add(accessRuleRepo.findByName(accessruleName)); } } ar.setGates(gates); - accessruleRepo.persist(ar); + accessRuleRepo.save(ar); logger.debug("upsertAccessRule() finished"); return ar; @@ -449,7 +434,7 @@ private Map getFENCEMapping() { new String[]{this.templatePath, "fence_mapping.json"})) , Map.class); } catch (IOException e) { - logger.error("fence_mapping.json not found at " + this.templatePath); + logger.error("fence_mapping.json not found at {}", this.templatePath); } return Map.of(); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java index b7fa284f1..7b1ca7f6c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -2,8 +2,9 @@ import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.UUID; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +31,7 @@ public class OauthUserMatchingService { private final UserRepository userRepo; - private final UserController userController; + private final UserService userService; private final UserMetadataMappingService mappingService; @@ -39,9 +40,9 @@ public class OauthUserMatchingService { private final ObjectMapper mapper = new ObjectMapper(); @Autowired - public OauthUserMatchingService(UserRepository userRepo, UserController userController, UserMetadataMappingService mappingService, ConnectionRepository connectionRepo) { + public OauthUserMatchingService(UserRepository userRepo, UserService userService, UserMetadataMappingService mappingService, ConnectionRepository connectionRepo) { this.userRepo = userRepo; - this.userController = userController; + this.userService = userService; this.mappingService = mappingService; this.connectionRepo = connectionRepo; } @@ -66,9 +67,9 @@ public User matchTokenToUser(JsonNode userInfo) { List connections = JsonPath.using(conf).parse(parsedInfo).read("$.identities[0].connection"); String connectionId = connections.get(0); - Connection connection = connectionRepo.getUniqueResultByColumn("id", connectionId); + Optional connection = connectionRepo.findById(UUID.fromString(connectionId)); - List mappings = mappingService.getAllMappingsForConnection(connection); + List mappings = mappingService.getAllMappingsForConnection(connection.orElse(null)); if (mappings == null || mappings.isEmpty()) { //We don't have any mappings for this connection yet @@ -77,7 +78,7 @@ public User matchTokenToUser(JsonNode userInfo) { } //We only care about unmatched users - List users = userRepo.listUnmatchedByConnectionId(connection); + List users = userRepo.findByConnectionAndMatched(connection.orElse(null), false); if (users == null || users.isEmpty()) { logger.info("No unmatched users exist with connectionId " + connection); return null; @@ -110,7 +111,7 @@ public User matchTokenToUser(JsonNode userInfo) { u.setAuth0metadata(userInfoString); u.setMatched(true); u.setSubject(userId); - userController.updateEntity(Arrays.asList(u), userRepo); + userService.updateUser(Arrays.asList(u)); return u; } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 13139d4e4..1026f71b2 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -6,17 +6,15 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfSerivceController; import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; +import jakarta.persistence.NoResultException; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import javax.persistence.NoResultException; -import java.util.Arrays; -import java.util.Date; -import java.util.List; +import java.util.*; /** *

Provides business logic for the TermsOfService endpoint.

> @@ -35,19 +33,19 @@ public class TOSService { private final UserRepository userRepo; - private final UserController userController; // TODO: This isn't a service its a controller. Why are we doing this? + private final UserController userService; // TODO: This isn't a service its a controller. Why are we doing this? @Autowired - public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo, UserController userController) { + public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo, UserController userService) { this.termsOfServiceRepo = termsOfServiceRepo; this.userRepo = userRepo; - this.userController = userController; + this.userService = userService; } - public boolean hasUserAcceptedLatest(String userId){ + public boolean hasUserAcceptedLatest(String userId) { // If TOS is not enabled, then the user has accepted it - if (!isToSEnabled){ + if (!isToSEnabled) { return true; } @@ -56,37 +54,52 @@ public boolean hasUserAcceptedLatest(String userId){ return true; } - logger.info("Checking Terms Of Service acceptance for user with id " + userId); - return userRepo.checkAgainstTOSDate(userId); + logger.info("Checking Terms Of Service acceptance for user with id {}", userId); + return checkAgainstTOSDate(userId); } - public TermsOfService updateTermsOfService(String html){ + public TermsOfService updateTermsOfService(String html) { TermsOfService updatedTOS = new TermsOfService(); updatedTOS.setContent(html); - termsOfServiceRepo.persist(updatedTOS); - return termsOfServiceRepo.getLatest(); + termsOfServiceRepo.save(updatedTOS); + return termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); } - public String getLatest(){ + public String getLatest() { try { - return termsOfServiceRepo.getLatest().getContent(); - } catch (NoResultException e){ + return termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getContent(); + } catch (NoResultException e) { logger.info("Terms Of Service disabled: No Terms of Service found in database"); return null; } } - public void acceptTermsOfService(String userId){ - logger.info("User " + userId + " accepting TOS"); + public void acceptTermsOfService(String userId) { + logger.info("User {} accepting TOS", userId); User user = userRepo.findBySubject(userId); - if (user == null){ + if (user == null) { throw new RuntimeException("User does not exist"); } user.setAcceptedTOS(new Date()); - List users = Arrays.asList(user); - Date tosDate = termsOfServiceRepo.getLatest().getDateUpdated(); - userController.updateUser(users); - logger.info("TOS_LOG : User " + (!StringUtils.isEmpty(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata()) + " accepted the Terms of Service dated " + tosDate.toString()); + List users = List.of(user); + Date tosDate = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getDateUpdated(); + userService.updateUser(users); + logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(), tosDate.toString()); + } + + private boolean checkAgainstTOSDate(String userId) { + Optional optUser = this.userRepo.findById(UUID.fromString(userId)); + if (optUser.isPresent()) { + User user = optUser.get(); + Date acceptedTOS = user.getAcceptedTOS(); + if (acceptedTOS == null) { + return false; + } + Date latestTOS = this.termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getDateUpdated(); + return acceptedTOS.after(latestTOS); + } + + return false; } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 3efaa981c..1a4195c2e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -24,10 +24,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; @@ -143,7 +140,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg isLongTermToken = true; } - user = this.userRepository.getUniqueResultByColumn("subject", subject); + user = this.userRepository.findBySubject(subject); logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); if (user == null) { logger.error("_inspectToken() could not find user with subject " + subject); @@ -238,18 +235,18 @@ public ResponseEntity refreshToken(String authorizationHeader) { return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - user = this.userRepository.getById(user.getUuid()); - if (user == null) { + Optional loadUser = this.userRepository.findById(user.getUuid()); + if (loadUser.isEmpty()) { logger.error("refreshToken() When retrieving current user, it returned null, the user might be removed from database"); throw new NotAuthorizedException("User doesn't exist anymore"); } - if (!user.isActive()) { + if (!loadUser.get().isActive()) { logger.error("refreshToken() The user has just been deactivated."); throw new NotAuthorizedException("User has been deactivated."); } - String subject = user.getSubject(); + String subject = loadUser.get().getSubject(); if (subject == null || subject.isEmpty()) { logger.error("refreshToken() subject doesn't exist in the user."); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index 9d00bc476..4cfeb1a9c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -11,12 +11,14 @@ import javax.transaction.Transactional; import java.util.List; +import java.util.Optional; +import java.util.UUID; /** *

Provides business logic for UserMetadataMapping endpoint.

*/ @Service -public class UserMetadataMappingService extends BaseEntityService { +public class UserMetadataMappingService { private final UserMetadataMappingRepository userMetadataMappingRepo; @@ -24,7 +26,6 @@ public class UserMetadataMappingService extends BaseEntityService getAllMappingsForConnection(Connection connecti } @Transactional - public ResponseEntity addMappings(List mappings) { - String errorMessage = "The following connectionIds do not exist:\n"; + public List addMappings(List mappings) { + StringBuilder errorMessage = new StringBuilder("The following connectionIds do not exist:\n"); boolean error = false; for (UserMetadataMapping umm : mappings) { - Connection c = connectionRepo.findConnectionById(umm.getConnection().getId()); - if (c == null) { + Optional c = connectionRepo.findById(UUID.fromString(umm.getConnection().getId())); + if (c.isEmpty()) { error = true; - errorMessage += umm.getConnection().getId() + "\n"; + errorMessage.append(umm.getConnection().getId()).append("\n"); } else { - umm.setConnection(c); + umm.setConnection(c.get()); } } + if (error) { - return PICSUREResponse.success(errorMessage); + throw new IllegalArgumentException(errorMessage.toString()); } - return addEntity(mappings, userMetadataMappingRepo); + + return this.userMetadataMappingRepo.saveAll(mappings); } public List getAllMappings() { - return userMetadataMappingRepo.list(); + return userMetadataMappingRepo.findAll(); } - public ResponseEntity getAllMappingsForConnection(String connection) { - return PICSUREResponse.success(getAllMappingsForConnection(connectionRepo.getUniqueResultByColumn("id", connection))); + public ResponseEntity getAllMappingsForConnection(String connectionId) { + Connection connection = this.connectionRepo.findById(UUID.fromString(connectionId)).orElseThrow(() -> new IllegalArgumentException("Connection not found")); + return PICSUREResponse.success(getAllMappingsForConnection(connection)); } - public ResponseEntity updateEntity(List mappings) { - return this.updateEntity(mappings, userMetadataMappingRepo); + public List updateUserMetadataMappings(List mappings) { + return this.userMetadataMappingRepo.saveAll(mappings); } - @Transactional - public ResponseEntity removeEntityById(String mappingId) { - return this.removeEntityById(mappingId, userMetadataMappingRepo); + public List removeMetadataMappingByIdAndRetrieveAll(String mappingId) { + this.userMetadataMappingRepo.deleteById(UUID.fromString(mappingId)); + return this.userMetadataMappingRepo.findAll(); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 91e1a6388..045e3db41 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -2,18 +2,23 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.dbmi.avillach.util.response.PICSUREResponseOKwithMsgAndContent; -import edu.harvard.hms.dbmi.avillach.auth.entity.*; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import edu.harvard.hms.dbmi.avillach.auth.utils.JsonUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; +import jakarta.mail.MessagingException; +import jakarta.validation.constraints.NotNull; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,16 +30,15 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import javax.mail.MessagingException; -import javax.transaction.Transactional; -import javax.validation.constraints.NotNull; +import jakarta.transaction.Transactional; + import java.io.IOException; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.*; @Service -public class UserService extends BaseEntityService { +public class UserService { private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); @@ -43,7 +47,7 @@ public class UserService extends BaseEntityService { private final UserRepository userRepository; private final ConnectionRepository connectionRepository; private final ApplicationRepository applicationRepository; - private final RoleRepository roleRepository; + private final RoleService roleService; private final String clientSecret; private final long tokenExpirationTime; private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour @@ -54,15 +58,14 @@ public class UserService extends BaseEntityService { private final ObjectMapper objectMapper = new ObjectMapper(); @Autowired - public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, RoleRepository roleRepository, ApplicationRepository applicationRepository, + public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, ApplicationRepository applicationRepository, RoleService roleService, @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime, @Value("${application.default.}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { - super(User.class); this.basicMailService = basicMailService; this.tosService = tosService; this.userRepository = userRepository; this.connectionRepository = connectionRepository; - this.roleRepository = roleRepository; + this.roleService = roleService; this.clientSecret = clientSecret; this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; this.applicationRepository = applicationRepository; @@ -112,17 +115,22 @@ public HashMap getUserProfileResponse(Map claims return responseMap; } - public ResponseEntity getEntityById(String userId) { - return getEntityById(userId, this.userRepository); - } + public User getUserById(String userId) { + Optional user = this.userRepository.findById(UUID.fromString(userId)); + if (user.isEmpty()) { + logger.error("getUserById() cannot find user by UUID: {}", userId); + throw new IllegalArgumentException("Cannot find user by input UUID: " + userId); + } - public ResponseEntity getEntityAll() { - return getEntityAll(this.userRepository); + return user.get(); } - public ResponseEntity addEntity(List users) { - return addEntity(users, this.userRepository); + public List getAllUsers() { + return this.userRepository.findAll(); + } + public List addUser(List users) { + return this.userRepository.saveAll(users); } /** @@ -137,7 +145,7 @@ public ResponseEntity addEntity(List users) { * if the state is changed, check if the user is a super admin to determine if the user could perform the action. * * @param currentUser the user trying to perform the action - * @param inputUser the user that is going to be updated + * @param inputUser the user that is going to be updated * @param originalUser there could be no original user when adding a new user * @return true if the user could perform the action, false otherwise */ @@ -216,7 +224,7 @@ public ResponseEntity addUsers(List users) { } } - ResponseEntity updateResponse = addEntity(users); + ResponseEntity updateResponse = PICSUREResponse.success(addUser(users)); sendUserUpdateEmailsFromResponse(updateResponse); return updateResponse; } @@ -232,18 +240,18 @@ private void checkAssociation(List users) { for (User user : users) { if (user.getRoles() != null) { Set roles = new HashSet<>(); - user.getRoles().forEach(t -> this.roleRepository.addObjectToSet(roles, this.roleRepository, t)); // TODO: We need to fix the exception that is thrown here + user.getRoles().forEach(t -> this.roleService.addObjectToSet(roles, t)); // TODO: We need to fix the exception that is thrown here user.setRoles(roles); } if (user.getConnection() != null) { - Connection connection = this.connectionRepository.getUniqueResultByColumn("id", user.getConnection().getId()); - user.setConnection(connection); + Optional connection = this.connectionRepository.findById(UUID.fromString(user.getConnection().getId())); + user.setConnection(connection.orElse(null)); } } } - @Transactional // TODO: Can this be moved further down the call hierarchy to improve performance? + @Transactional public ResponseEntity updateUser(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); User currentUser = (User) securityContext.getAuthentication().getPrincipal(); @@ -256,20 +264,20 @@ public ResponseEntity updateUser(List users) { boolean allowUpdate = true; for (User user : users) { - User originalUser = this.userRepository.getById(user.getUuid()); - if (!allowUpdateSuperAdminRole(currentUser, user, originalUser)) { + Optional originalUser = this.userRepository.findById(user.getUuid()); + if (!allowUpdateSuperAdminRole(currentUser, user, originalUser.orElse(null))) { allowUpdate = false; break; } } if (allowUpdate) { - ResponseEntity updateResponse = updateEntity(users, this.userRepository); + users = this.userRepository.saveAll(users); + ResponseEntity updateResponse = PICSUREResponse.success(users); sendUserUpdateEmailsFromResponse(updateResponse); return updateResponse; } else { - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant or remove " - + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); + logger.error("updateUser() user - {} - with roles [{}] - is not allowed to grant or remove " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege.", currentUser.getUuid(), currentUser.getRoleString()); throw new IllegalArgumentException("Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); } } @@ -278,15 +286,14 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) logger.debug("Sending email"); try { Object entity = updateResponse.getBody(); // TODO: Determine how to replicate this given the new approach - if (entity != null && entity instanceof PICSUREResponseOKwithMsgAndContent) { - PICSUREResponseOKwithMsgAndContent okResponse = (PICSUREResponseOKwithMsgAndContent) entity; + if (entity instanceof PICSUREResponseOKwithMsgAndContent okResponse) { List addedUsers = (List) okResponse.getContent(); String message = okResponse.getMessage(); for (User user : addedUsers) { try { basicMailService.sendUsersAccessEmail(user); } catch (MessagingException e) { - logger.error("Failed to send email! " + e.getLocalizedMessage()); + logger.error("Failed to send email! {}", e.getLocalizedMessage()); logger.debug("Exception Trace: ", e); okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); } @@ -298,6 +305,7 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) logger.debug("finished email sending method"); } + @Transactional public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasToken) { SecurityContext securityContext = SecurityContextHolder.getContext(); User user = (User) securityContext.getAuthentication().getPrincipal(); @@ -327,10 +335,10 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { try { Arrays.stream(objectMapper.readValue(privilege.getQueryScope(), String[].class)) - .filter(x -> x != null) - .forEach(scopeList -> scopes.addAll(Arrays.asList(scopeList))); + .filter(Objects::nonNull) + .forEach(scopes::add); } catch (IOException e) { - logger.error("Parsing issue for privilege " + privilege.getUuid() + " queryScope", e); + logger.error("Parsing issue for privilege {} queryScope", privilege.getUuid(), e); } }); userForDisplay.setQueryScopes(scopes); @@ -342,7 +350,7 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT userForDisplay.setToken(user.getToken()); } else { user.setToken(generateUserLongTermToken(authorizationHeader)); - this.userRepository.merge(user); + this.userRepository.save(user); userForDisplay.setToken(user.getToken()); } } @@ -350,38 +358,44 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT return PICSUREResponse.success(userForDisplay); } - public ResponseEntity getQueryTemplate(String applicationId) { + @Transactional + public Optional getQueryTemplate(String applicationId) { if (applicationId == null || applicationId.trim().isEmpty()) { logger.error("getQueryTemplate() input application UUID is null or empty."); throw new IllegalArgumentException("Input application UUID is incorrect."); } SecurityContext securityContext = SecurityContextHolder.getContext(); - User user = (User) securityContext.getAuthentication().getPrincipal(); - if (user == null || user.getUuid() == null) { + Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); + if (user.isEmpty() || user.get().getUuid() == null) { logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return Optional.empty(); } - user = this.userRepository.getById(user.getUuid()); - if (user == null) { + user = this.userRepository.findById(user.get().getUuid()); + if (user.isEmpty()) { logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return Optional.empty(); } - Application application = this.applicationRepository.getById(UUID.fromString(applicationId)); - - if (application == null) { - logger.error("getQueryTemplate() cannot find corresponding application by UUID: " + applicationId); + Optional application = this.applicationRepository.findById(UUID.fromString(applicationId)); + if (application.isEmpty()) { + logger.error("getQueryTemplate() cannot find corresponding application by UUID: {}", applicationId); throw new IllegalArgumentException("Cannot find application by input UUID: " + applicationId); } - return PICSUREResponse.success( - Map.of("queryTemplate", mergeTemplate(user, application))); + return Optional.ofNullable(mergeTemplate(user.orElse(null), application.orElse(null))); } public ResponseEntity getDefaultQueryTemplate() { - return getQueryTemplate(this.applicationUUID); + Optional mergedTemplate = getQueryTemplate(this.applicationUUID); + + if (mergedTemplate.isEmpty()) { + logger.error("getDefaultQueryTemplate() cannot find corresponding application by UUID: {}", this.applicationUUID); + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + return PICSUREResponse.success(Map.of("queryTemplate", mergedTemplate.orElse(null))); } private String mergeTemplate(User user, Application application) { @@ -397,7 +411,7 @@ private String mergeTemplate(User user, Application application) { try { templateMap = objectMapper.readValue(template, Map.class); } catch (IOException ex) { - logger.error("mergeTemplate() cannot convert stored queryTemplate using Jackson, the queryTemplate is: " + template); + logger.error("mergeTemplate() cannot convert stored queryTemplate using Jackson, the queryTemplate is: {}", template); throw new IllegalArgumentException("Inner application error, please contact admin."); } @@ -416,7 +430,7 @@ private String mergeTemplate(User user, Application application) { try { resultJSON = objectMapper.writeValueAsString(mergedTemplateMap); } catch (JsonProcessingException ex) { - logger.error("mergeTemplate() cannot convert map to json string. The map mergedTemplate is: " + mergedTemplateMap); + logger.error("mergeTemplate() cannot convert map to json string. The map mergedTemplate is: {}", mergedTemplateMap); throw new IllegalArgumentException("Inner application error, please contact admin."); } @@ -427,23 +441,23 @@ private String mergeTemplate(User user, Application application) { @Transactional public ResponseEntity refreshUserToken(HttpHeaders httpHeaders) { SecurityContext securityContext = SecurityContextHolder.getContext(); - User user = (User) securityContext.getAuthentication().getPrincipal(); - if (user == null || user.getUuid() == null) { + Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); + if (user.isEmpty() || user.get().getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - user = this.userRepository.getById(user.getUuid()); - if (user == null) { + user = this.userRepository.findById(user.get().getUuid()); + if (user.isEmpty()) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } String authorizationHeader = httpHeaders.getFirst("Authorization"); String longTermToken = generateUserLongTermToken(authorizationHeader); - user.setToken(longTermToken); + user.get().setToken(longTermToken); - this.userRepository.merge(user); + this.userRepository.save(user.get()); return PICSUREResponse.success(Map.of("userLongTermToken", longTermToken)); } @@ -486,4 +500,10 @@ private String generateUserLongTermToken(String authorizationHeader) { AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + tokenSubject, this.longTermTokenExpirationTime); } + + public void changeRole(User currentUser, Set roles) { + // set the users roles and merge the user + currentUser.setRoles(roles); + this.userRepository.save(currentUser); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java new file mode 100644 index 000000000..a517af1e0 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -0,0 +1,82 @@ +package edu.harvard.hms.dbmi.avillach.auth.utils; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.*; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; + +public class RestClientUtil { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final Logger logger = LoggerFactory.getLogger(RestClientUtil.class); + private static final RestTemplate restTemplate = new RestTemplate(); + + public static boolean is2xx(ResponseEntity response) { + return response.getStatusCode().is2xxSuccessful(); + } + + public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders headers) { + try { + logger.debug("HttpClientUtilSpring retrieveGetResponse()"); + HttpEntity entity = new HttpEntity<>("parameters", headers); + // Pass custom configuration to the RestTemplate + return restTemplate.exchange(uri, HttpMethod.GET, entity, String.class); + } catch (HttpClientErrorException ex) { + logger.error("HttpClientErrorException: " + ex.getMessage()); + throw ex; + } + } + + public static String composeURL(String baseURL, String pathName, String query) { + try { + URI uri = new URI(baseURL); + List basePathComponents = Arrays.asList(uri.getPath().split("/")); + List pathNameComponents = Arrays.asList(pathName.split("/")); + List allPathComponents = new LinkedList<>(); + Predicate nonEmpty = segment -> !segment.isEmpty(); + allPathComponents.addAll(basePathComponents.stream().filter(nonEmpty).toList()); + allPathComponents.addAll(pathNameComponents.stream().filter(nonEmpty).toList()); + String queryString = query == null ? uri.getQuery() : query; + return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), "/" + String.join("/", allPathComponents), queryString, uri.getFragment()).toString(); + } catch (URISyntaxException e) { + throw new RuntimeException("baseURL invalid : " + baseURL, e); + } + } + + public static ResponseEntity retrievePostResponse(String uri, HttpHeaders headers, String body) { + try { + logger.debug("HttpClientUtilSpring retrievePostResponse()"); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(body, headers); + return restTemplate.postForEntity(uri, entity, String.class); + } catch (HttpClientErrorException ex) { + logger.error("HttpClientErrorException: " + ex.getMessage()); + throw ex; + } + } + + public static T readObjectFromResponse(ResponseEntity response, Class expectedElementType) { + logger.debug("HttpClientUtilSpring readObjectFromResponse()"); + + try { + return objectMapper.readValue(response.getBody(), objectMapper.getTypeFactory().constructType(expectedElementType)); + } catch (JsonMappingException | JsonParseException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java index 865a8f01f..659f7398e 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java @@ -9,9 +9,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import edu.harvard.hms.dbmi.avillach.auth.service.impl.OauthUserMatchingService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -42,10 +42,10 @@ public class Auth0MatchingServiceTest { UserMetadataMappingService mappingService = mock(UserMetadataMappingService.class); @Mock - UserController userController = mock(UserController.class); + UserService userService = mock(UserService.class); @InjectMocks - OauthUserMatchingService cut = new OauthUserMatchingService(userRepo, userController, mappingService, mock(ConnectionRepository.class)); + OauthUserMatchingService cut = new OauthUserMatchingService(userRepo, userService, mappingService, mock(ConnectionRepository.class)); User persistedUser; ObjectMapper mapper = new ObjectMapper(); @@ -55,7 +55,7 @@ public void setUp() throws Auth0Exception { MockitoAnnotations.initMocks(this); //Instead of calling the database doAnswer(invocation -> (listUnmatchedByConnectionIdMock(invocation.getArgument(0)))). - when(userRepo).listUnmatchedByConnectionId(any()); + when(userRepo).findByConnectionAndMatched(any(), any()); doAnswer(invocation -> (getAllMappingsForConnectionMock(invocation.getArgument(0)))). when(mappingService).getAllMappingsForConnection((Connection) any()); //So we can check that the user is persisted @@ -65,7 +65,7 @@ public Void answer(InvocationOnMock invocation) { persistedUser = userList.get(0); return null; } - }).when(userController).updateUser(any(List.class)); + }).when(userService).updateUser(any(List.class)); } @Test diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtilsTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtilsTest.java index 973e99a8c..97eee2851 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtilsTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtilsTest.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import static edu.harvard.hms.dbmi.avillach.auth.JAXRSConfiguration.objectMapper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -9,6 +8,7 @@ import java.util.Collection; import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Before; import org.junit.Test; @@ -36,11 +36,12 @@ public void init() { } @Test - public void testmergeTemplateMap() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMap() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"numericFilters\":{},\"categoryFilters\":{\"\\\\demographics\\\\SEX\\\\\":[\"male\"]},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); - + //template with one numeric filter and one required field String template2Str = "{\"categoryFilters\":{},\"numericFilters\":{\"\\\\demographics\\\\AGE\\\\\":{\"max\":\"50\"}},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template2 = objectMapper.readValue(template2Str, Map.class); @@ -71,7 +72,8 @@ public void testmergeTemplateMap() throws JsonParseException, JsonMappingExcepti * @throws IOException */ @Test - public void testmergeTemplateMapEmptyMap() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMapEmptyMap() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"numericFilters\":{},\"categoryFilters\":{\"\\\\demographics\\\\SEX\\\\\":[\"male\"]},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); @@ -106,7 +108,8 @@ public void testmergeTemplateMapEmptyMap() throws JsonParseException, JsonMappin * @throws IOException */ @Test - public void testmergeTemplateMapEmptyMapInverse() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMapEmptyMapInverse() throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"numericFilters\":{},\"categoryFilters\":{\"\\\\demographics\\\\SEX\\\\\":[\"male\"]},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); @@ -136,7 +139,8 @@ public void testmergeTemplateMapEmptyMapInverse() throws JsonParseException, Jso } @Test - public void testmergeTemplateMapMergeFilters() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMapMergeFilters() throws IOException{ + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"numericFilters\":{},\"categoryFilters\":{\"\\\\demographics\\\\SEX\\\\\":[\"male\"]},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); @@ -174,7 +178,8 @@ public void testmergeTemplateMapMergeFilters() throws JsonParseException, JsonMa } @Test - public void testmergeTemplateMapMultipleNumericFilters() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMapMultipleNumericFilters() throws IOException{ + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"categoryFilters\":{},\"numericFilters\":{\"\\\\demographics\\\\AGE\\\\\":{\"min\":\"20\"}},\"requiredFields\":[\"\\\\_Study Accession with Patient ID\\\\\"],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); @@ -216,7 +221,8 @@ public void testmergeTemplateMapMultipleNumericFilters() throws JsonParseExcepti @Test - public void testmergeTemplateMapVariantInfo() throws JsonParseException, JsonMappingException, IOException{ + public void testmergeTemplateMapVariantInfo() throws IOException{ + ObjectMapper objectMapper = new ObjectMapper(); //template with one category filter and one required field String template1Str = "{\"numericFilters\":{},\"categoryFilters\":{},\"requiredFields\":[],\"anyRecordOf\":[],\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{\"variant_severity\":[\"HIGH\"]},\"numericVariantInfoFilters\":{}}],\"expectedResultType\":\"COUNT\"}"; Map template1 = objectMapper.readValue(template1Str, Map.class); @@ -242,27 +248,7 @@ public void testmergeTemplateMapVariantInfo() throws JsonParseException, JsonMap assertNotNull(mergedTemplate.get("variantInfoFilters")); assertTrue(mergedTemplate.get("variantInfoFilters") instanceof Collection); Collection variantInfoFilters = (Collection)mergedTemplate.get("variantInfoFilters"); - - // We do not support merging lists of maps; it doesn't make sense to combine or - // merge the variant info filters - -// assertEquals(1, variantInfoFilters.size()); -// Object filter = variantInfoFilters.iterator().next(); -// assertTrue( filter instanceof Map); -// assertEquals(2, ((Map)filter).size()); -// -// assertTrue(((Map)filter).containsKey("categoryVariantInfoFilters")); -// assertTrue(((Map)filter).containsKey("numericVariantInfoFilters")); -// -// assertTrue(((Map)filter).get("categoryVariantInfoFilters") instanceof Map); -// Map catVarInfoFilter = (Map)((Map)filter).get("categoryVariantInfoFilters"); -// -// assertEquals(1, catVarInfoFilter.size()); -// assertTrue(catVarInfoFilter.get("variant_severity") instanceof Collection); -// -// assertTrue(((Collection)catVarInfoFilter.get("variant_severity")).contains("LOW")); -// assertTrue(((Collection)catVarInfoFilter.get("variant_severity")).contains("HIGH")); - + assertNotNull(mergedTemplate.get("expectedResultType")); } } From 3d85ebdce5e0a9da7b85241151ef5de0277e574b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 8 Apr 2024 07:35:39 -0400 Subject: [PATCH 014/222] Update UserService and User entity classes --- .../edu/harvard/hms/dbmi/avillach/auth/entity/User.java | 3 --- .../hms/dbmi/avillach/auth/service/impl/UserService.java | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 1fc346e98..55a050e47 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import jakarta.persistence.*; -import org.hibernate.annotations.Type; import java.io.Serializable; import java.security.Principal; @@ -53,11 +52,9 @@ public class User extends BaseEntity implements Serializable, Principal { private Date acceptedTOS; @Column(name = "auth0_metadata") - @Type(type = "text") private String auth0metadata; @Column(name = "general_metadata") - @Type(type = "text") private String generalMetadata; @Column(name = "is_active") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 045e3db41..9c30ea738 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -286,16 +286,16 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) logger.debug("Sending email"); try { Object entity = updateResponse.getBody(); // TODO: Determine how to replicate this given the new approach - if (entity instanceof PICSUREResponseOKwithMsgAndContent okResponse) { - List addedUsers = (List) okResponse.getContent(); - String message = okResponse.getMessage(); + if (entity instanceof HashMap okResponse) { + List addedUsers = (List) okResponse.get("content"); + String message = okResponse.get("message") != null ? okResponse.get("message").toString() : ""; for (User user : addedUsers) { try { basicMailService.sendUsersAccessEmail(user); } catch (MessagingException e) { logger.error("Failed to send email! {}", e.getLocalizedMessage()); logger.debug("Exception Trace: ", e); - okResponse.setMessage(message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); + okResponse.put("message", message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); } } } From aff802aa681db244b3f43c2049dc509d39b1c8d8 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 10 Apr 2024 13:58:45 -0400 Subject: [PATCH 015/222] Update more dependencies to jakarta Updated swagger, mail, and jackson dependencies to java 21 versions. --- pic-sure-auth-db/Dockerfile | 2 +- pic-sure-auth-services/pom.xml | 522 +++++++++--------- .../hms/dbmi/avillach/auth/Application.java | 2 + .../dbmi/avillach/auth/entity/AccessRule.java | 4 +- .../hms/dbmi/avillach/auth/entity/User.java | 6 +- .../dbmi/avillach/auth/filter/JWTFilter.java | 2 + .../auth/repository/UserRepository.java | 17 - .../auth/rest/AccessRuleController.java | 26 +- .../auth/rest/ApplicationController.java | 30 +- .../avillach/auth/rest/AuthController.java | 12 +- .../auth/rest/ConnectionWebController.java | 29 +- .../auth/rest/PrivilegeController.java | 26 +- .../avillach/auth/rest/RoleController.java | 26 +- .../auth/rest/TermsOfSerivceController.java | 27 +- .../avillach/auth/rest/TokenController.java | 14 +- .../avillach/auth/rest/UserController.java | 34 +- .../UserMetadataMappingWebController.java | 24 +- .../auth/security/SecurityConfig.java | 12 +- .../auth/service/PrivilegeService.java | 2 +- .../auth/service/impl/AccessRuleService.java | 4 +- .../auth/service/impl/ApplicationService.java | 7 +- .../service/impl/ConnectionWebService.java | 3 + .../auth/service/impl/TOSService.java | 10 +- .../impl/UserMetadataMappingService.java | 2 +- .../auth/service/impl/UserService.java | 6 +- .../dbmi/avillach/auth/utils/JsonUtils.java | 2 +- .../src/main/resources/application.properties | 16 +- .../dbmi/avillach/ApplicationServiceTest.java | 16 +- 28 files changed, 431 insertions(+), 452 deletions(-) diff --git a/pic-sure-auth-db/Dockerfile b/pic-sure-auth-db/Dockerfile index e558e9182..b9f5cc3b2 100644 --- a/pic-sure-auth-db/Dockerfile +++ b/pic-sure-auth-db/Dockerfile @@ -1,4 +1,4 @@ -FROM mysql:5.7.24 +FROM mysql:8.2.0 RUN mkdir /tempsql/ COPY db/picsure-auth-dump-initial.sql /tempsql/picsure-auth-dump-initial.sql diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index cd1f5dffc..dec0fcb3f 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -1,278 +1,262 @@ - 4.0.0 - - pic-sure-auth-microapp - edu.harvard.hms.dbmi.avillach.picsure.auth.microapp - 1.0-SNAPSHOT - - edu.harvard.hms.dbmi.avillach - pic-sure-auth-services - war - - 2.12.7 - 2.3.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ch.qos.logback - logback-classic - 1.2.3 - - - - - - - org.springframework.boot - spring-boot - 3.2.3 - - - - org.springframework - spring-web - 6.1.5 - - - - org.springframework.boot - spring-boot-starter-web - 3.2.3 - - - org.springframework.boot - spring-boot-starter-logging - - - - - - org.springframework.boot - spring-boot-autoconfigure - 3.2.3 - - - org.springframework.boot - spring-boot-starter-security - 3.2.4 - - - - org.springframework.boot - spring-boot-starter-mail - 3.2.4 - - - - org.springframework.boot - spring-boot-starter-data-jpa - 3.2.4 - - - - - - com.mysql - mysql-connector-j - 8.3.0 - - - io.jsonwebtoken - jjwt - 0.9.0 - - - com.auth0 - auth0 - 1.8.0 - - - org.hibernate - hibernate-core - 5.3.20.Final - provided - - - - junit - junit - 4.13.1 - test - - - - - com.jayway.jsonpath - json-path - 2.9.0 - - - - org.mockito - mockito-core - 2.23.4 - test - - - - - com.github.spullara.mustache.java - compiler - 0.9.5 - - - - javax.mail - mail - 1.5.0-b01 - - - - com.sun.mail - javax.mail - 1.6.2 - - - - javax.mail - javax.mail-api - 1.6.2 - - - - - io.swagger - swagger-jaxrs - 1.5.20 - - - javax.ws.rs - jsr311-api - - - - - - io.swagger.core.v3 - swagger-jaxrs2 - 2.0.0 - - - io.swagger.core.v3 - swagger-jaxrs2-servlet-initializer - 2.0.0 - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + 4.0.0 + + pic-sure-auth-microapp + edu.harvard.hms.dbmi.avillach.picsure.auth.microapp + 1.0-SNAPSHOT + + edu.harvard.hms.dbmi.avillach + pic-sure-auth-services + war + + 2.3.0 + + + + + + org.springframework.boot + spring-boot + 3.2.3 + org.springframework.boot spring-boot-starter-validation RELEASE compile - - jakarta.transaction - jakarta.transaction-api - 2.0.1 - - - - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - - - - org.apache.maven.plugins - maven-war-plugin - - true - false - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - retrieve-mysql-driver - generate-sources - - copy - - false - - - - mysql - mysql-connector-java - 5.1.38 - jar - true - ${project.build.directory}/modules/system/layers/base/com/sql/mysql/main/ - - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.5 - - - reserve-network-port - - reserve-network-port - - process-test-resources - - - test.server.port - - - - - - - + + + net.bytebuddy + byte-buddy + 1.14.13 + + + + org.springframework + spring-web + 6.1.5 + + + + org.springframework.boot + spring-boot-starter-web + 3.2.3 + + + + org.springframework.boot + spring-boot-autoconfigure + 3.2.3 + + + org.springframework.boot + spring-boot-starter-security + 3.2.4 + + + + org.springframework.boot + spring-boot-starter-mail + 3.2.4 + + + + org.springframework.boot + spring-boot-starter-data-jpa + 3.2.4 + + + + com.mysql + mysql-connector-j + 8.2.0 + + + io.jsonwebtoken + jjwt + 0.9.0 + + + com.auth0 + auth0 + 1.8.0 + + + junit + junit + 4.13.1 + test + + + com.jayway.jsonpath + json-path + 2.9.0 + + + org.mockito + mockito-core + 2.23.4 + test + + + com.github.spullara.mustache.java + compiler + 0.9.5 + + + + com.sun.mail + jakarta.mail + 2.0.1 + + + + jakarta.mail + jakarta.mail-api + 2.1.3 + + + + + io.swagger.core.v3 + swagger-jaxrs2-jakarta + 2.2.21 + + + + io.swagger.core.v3 + swagger-jaxrs2-servlet-initializer-jakarta + 2.2.21 + + + + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.21 + + + + jakarta.transaction + jakarta.transaction-api + 2.0.1 + + + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + + + + org.glassfish.jaxb + jaxb-runtime + 4.0.5 + + + org.glassfish.jaxb + jaxb-core + 4.0.5 + + + + com.fasterxml.jackson.core + jackson-databind + 2.17.0 + + + + com.fasterxml.jackson.core + jackson-core + 2.17.0 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.17.0 + + + org.jetbrains + annotations + RELEASE + compile + + + + + + ${project.artifactId} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + + + + org.apache.maven.plugins + maven-war-plugin + + true + false + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + retrieve-mysql-driver + generate-sources + + copy + + false + + + + mysql + mysql-connector-java + 5.1.38 + jar + true + + ${project.build.directory}/modules/system/layers/base/com/sql/mysql/main/ + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.5 + + + reserve-network-port + + reserve-network-port + + process-test-resources + + + test.server.port + + + + + + + diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java index bd6737731..25c528eb6 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @SpringBootApplication +@EnableJpaRepositories public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java index 314a68a50..fbd8a1bb7 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/AccessRule.java @@ -132,8 +132,8 @@ public static Map getTypeNameMap(){ */ @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "accessRule_gate", - joinColumns = {@JoinColumn(name = "accessRule_id", nullable = false, updatable = false)}, - inverseJoinColumns = {@JoinColumn(name = "gate_id", nullable = false, updatable = false)}) + joinColumns = {@JoinColumn(name = "accessRule_id", nullable = false)}, + inverseJoinColumns = {@JoinColumn(name = "gate_id", nullable = false)}) private Set gates; /** diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 55a050e47..50eb5c194 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import jakarta.persistence.*; import java.io.Serializable; @@ -24,8 +23,8 @@ public class User extends BaseEntity implements Serializable, Principal { @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "user_role", - joinColumns = {@JoinColumn(name = "user_id", nullable = false, updatable = false)}, - inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false, updatable = false)}) + joinColumns = {@JoinColumn(name = "user_id", nullable = false, referencedColumnName = "uuid")}, + inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false, referencedColumnName = "uuid")}) private Set roles; private String email; @@ -41,7 +40,6 @@ public class User extends BaseEntity implements Serializable, Principal { *

* Note: This is because of the checkAssociation() method in UserService. *


- * @see UserController */ @ManyToOne @JoinColumn(name = "connectionId") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 80dd91090..184e21b1e 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -42,6 +43,7 @@ */ @Component +@Order(1) public class JWTFilter extends OncePerRequestFilter { private final static Logger logger = LoggerFactory.getLogger(JWTFilter.class); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java index 4fc0c6369..38d07ad38 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/UserRepository.java @@ -22,23 +22,6 @@ public interface UserRepository extends JpaRepository { List findByConnectionAndMatched(Connection connection, boolean matched); -// -// public User changeRole(User user, Set roles){ -// logger.info("Starting changing the role of user: " + user.getUuid() -// + ", with subject: " + user.getSubject() + ", to " + roles.stream().map(role -> role.getName()).collect(Collectors.joining(","))); -// user.setRoles(roles); -// em().merge(user); -// User updatedUser = getById(user.getUuid()); -// logger.info("User: " + updatedUser.getUuid() + ", with subject: " + -// updatedUser.getSubject() + ", now has a new role: " + updatedUser.getRoleString()); -// return updatedUser; -// } -// -// @Override -// public void persist(User user) { -// findOrCreate(user); -// } - /** *

Find a user by email.

* @param email the email to search for diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index bd59944e9..9be2fddbc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -3,8 +3,8 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AccessRuleService; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.*; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -25,7 +25,7 @@ *

* Path: /accessRule */ - +@Tag(name = "Access Rule Management") @Controller("/accessRule") public class AccessRuleController { @@ -36,11 +36,11 @@ public AccessRuleController(AccessRuleService accessRuleService) { this.accessRuleService = accessRuleService; } - @ApiOperation(value = "GET information of one AccessRule with the UUID, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET information of one AccessRule with the UUID, requires ADMIN or SUPER_ADMIN role") @Secured(value = {ADMIN, SUPER_ADMIN}) @GetMapping(value = "/{accessRuleId}") public ResponseEntity getAccessRuleById( - @ApiParam(value = "The UUID of the accessRule to fetch information about") + @Parameter(description = "The UUID of the accessRule to fetch information about") @PathVariable("accessRuleId") String accessRuleId) { Optional entityById = this.accessRuleService.getAccessRuleById(accessRuleId); @@ -51,7 +51,7 @@ public ResponseEntity getAccessRuleById( return PICSUREResponse.success(entityById.get()); } - @ApiOperation(value = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") @Secured({ADMIN, SUPER_ADMIN}) @GetMapping("") public ResponseEntity getAccessRuleAll() { @@ -59,11 +59,11 @@ public ResponseEntity getAccessRuleAll() { return PICSUREResponse.success(allAccessRules); } - @ApiOperation(value = "POST a list of AccessRules, requires SUPER_ADMIN role") + @Operation(description = "POST a list of AccessRules, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity addAccessRule( - @ApiParam(required = true, value = "A list of AccessRule in JSON format") + @Parameter(required = true, description = "A list of AccessRule in JSON format") List accessRules) { accessRules = this.accessRuleService.addAccessRule(accessRules); @@ -74,26 +74,26 @@ public ResponseEntity addAccessRule( return PICSUREResponse.success(accessRules); } - @ApiOperation(value = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @PutMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity updateAccessRule( - @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") List accessRules) { accessRules = this.accessRuleService.updateAccessRules(accessRules); return PICSUREResponse.success(accessRules); } - @ApiOperation(value = "DELETE an AccessRule by Id only if the accessRule is not associated by others, requires SUPER_ADMIN role") + @Operation(description = "DELETE an AccessRule by Id only if the accessRule is not associated by others, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @DeleteMapping(path = "/{accessRuleId}") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid accessRule Id") + @Parameter(required = true, description = "A valid accessRule Id") @PathVariable("accessRuleId") final String accessRuleId) { return PICSUREResponse.success(this.accessRuleService.removeAccessRuleById(accessRuleId)); } - @ApiOperation(value = "GET all types listed for the rule in accessRule that could be used, requires SUPER_ADMIN role") + @Operation(description = "GET all types listed for the rule in accessRule that could be used, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @GetMapping(path = "/allTypes", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getAllTypes() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index 4f82a7933..c07581fec 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -23,7 +23,7 @@ *
* Note: Only users with the super admin role can access this endpoint.

*/ -@Api +@Tag(name = "Application Management") @Controller @RequestMapping(value = "/application") public class ApplicationController { @@ -35,10 +35,10 @@ public ApplicationController(ApplicationService applicationService) { this.applicationService = applicationService; } - @ApiOperation(value = "GET information of one Application with the UUID, no role restrictions") + @Operation(description = "GET information of one Application with the UUID, no role restrictions") @GetMapping(value = "/{applicationId}") public ResponseEntity getApplicationById( - @ApiParam(required = true, value = "The UUID of the application to fetch information about") + @Parameter(required = true, description = "The UUID of the application to fetch information about") @PathVariable("applicationId") String applicationId) { Optional entityById = applicationService.getApplicationByID(applicationId); @@ -49,47 +49,47 @@ public ResponseEntity getApplicationById( return PICSUREResponse.success(entityById.get()); } - @ApiOperation(value = "GET a list of existing Applications, no role restrictions") + @Operation(description = "GET a list of existing Applications, no role restrictions") @GetMapping public ResponseEntity getApplicationAll() { return PICSUREResponse.success(applicationService.getAllApplications()); } - @ApiOperation(value = "POST a list of Applications, requires SUPER_ADMIN role") + @Operation(description = "POST a list of Applications, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(value = "/", consumes = "application/json", produces = "application/json") public ResponseEntity addApplication( - @ApiParam(required = true, value = "A list of AccessRule in JSON format") + @Parameter(required = true, description = "A list of AccessRule in JSON format") List applications) { applications = applicationService.addNewApplications(applications); return PICSUREResponse.success(applications); } - @ApiOperation(value = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(value = "/", consumes = "application/json", produces = "application/json") public ResponseEntity updateApplication( - @ApiParam(required = true, value = "A list of AccessRule with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") List applications) { applications = applicationService.updateApplications(applications); return PICSUREResponse.success(applications); } - @ApiOperation(value = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") + @Operation(description = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @GetMapping(value = "/refreshToken/{applicationId}") public ResponseEntity refreshApplicationToken( - @ApiParam(required = true, value = "A valid application Id") + @Parameter(required = true, description = "A valid application Id") @PathVariable("applicationId") String applicationId) { String newApplicationToken = applicationService.refreshApplicationToken(applicationId); return PICSUREResponse.success(Map.of("token", newApplicationToken)); } - @ApiOperation(value = "DELETE an Application by Id only if the application is not associated by others, requires SUPER_ADMIN role") + @Operation(description = "DELETE an Application by Id only if the application is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(value = "/{applicationId}") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid accessRule Id") + @Parameter(required = true, description = "A valid accessRule Id") @PathVariable("applicationId") final String applicationId) { try { List applications = applicationService.deleteApplicationById(applicationId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index 3a80c7778..1e25828d2 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthenticationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.FENCEAuthenticationService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -22,7 +22,7 @@ /** *

The authentication endpoint for PSAMA.

*/ -@Api +@Tag(name = "Authentication") @Controller @RequestMapping("/") public class AuthController { @@ -44,9 +44,9 @@ public AuthController(AuthorizationService authorizationService, AuthenticationS this.idp_provider = idpProvider; } - @ApiOperation(value = "The authentication endpoint for retrieving a valid user token") + @Operation(description = "The authentication endpoint for retrieving a valid user token") @PostMapping(path = "/authentication", consumes = "application/json", produces = "application/json") - public ResponseEntity authentication(@ApiParam(required = true, value = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) throws IOException { + public ResponseEntity authentication(@Parameter(required = true, description = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) throws IOException { logger.debug("authentication() starting..."); if (this.idp_provider.equalsIgnoreCase("fence")) { logger.debug("authentication() FENCE authentication"); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index a6ccf5f36..2766bbf4f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -3,16 +3,15 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ConnectionWebService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import javax.transaction.Transactional; import java.util.List; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -22,7 +21,7 @@ *

Endpoint for service handling business logic for connections to PSAMA.
* Note: Only users with the super admin role can access this endpoint.

*/ -@Api +@Tag(name = "Connection Management") @Controller @RequestMapping("/connection") public class ConnectionWebController { // TODO: This isn't a service class, it's a controller. We should refactor this so it doesn't extend BaseEntityService @@ -35,11 +34,11 @@ public ConnectionWebController(ConnectionWebService connectionWebSerivce) { this.connectionWebService = connectionWebSerivce; } - @ApiOperation(value = "GET information of one Connection with the UUID, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET information of one Connection with the UUID, requires ADMIN or SUPER_ADMIN role") @GetMapping(path = "/{connectionId}", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getConnectionById( - @ApiParam(required = true, value = "The UUID of the Connection to fetch information about") + @Parameter(required = true, description = "The UUID of the Connection to fetch information about") @PathVariable("connectionId") String connectionId) { try { Connection connectionById = connectionWebService.getConnectionById(connectionId); @@ -49,7 +48,7 @@ public ResponseEntity getConnectionById( } } - @ApiOperation(value = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") + @Operation(description = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") @GetMapping(path = "/", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getAllConnections() { @@ -57,12 +56,11 @@ public ResponseEntity getAllConnections() { return ResponseEntity.ok(allConnections); } - @ApiOperation(value = "POST a list of Connections, requires SUPER_ADMIN role") - @Transactional + @Operation(description = "POST a list of Connections, requires SUPER_ADMIN role") @Secured({SUPER_ADMIN}) @PostMapping(produces = "application/json", consumes = "application/json") public ResponseEntity addConnection( - @ApiParam(required = true, value = "A list of Connections in JSON format") + @Parameter(required = true, description = "A list of Connections in JSON format") List connections) { try { connections = connectionWebService.addConnection(connections); @@ -73,22 +71,21 @@ public ResponseEntity addConnection( return PICSUREResponse.success("All connections are added.", connections); } - @ApiOperation(value = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") @Secured({SUPER_ADMIN}) @PutMapping(produces = "application/json", consumes = "application/json") public ResponseEntity updateConnection( - @ApiParam(required = true, value = "A list of Connection with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of Connection with fields to be updated in JSON format") List connections) { List responseEntity = connectionWebService.updateConnections(connections); return ResponseEntity.ok(responseEntity); } - @ApiOperation(value = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") - @Transactional + @Operation(description = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") @Secured({SUPER_ADMIN}) @DeleteMapping(path = "/{connectionId}", produces = "application/json") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid connection Id") + @Parameter(required = true, description = "A valid connection Id") @PathVariable("connectionId") final String connectionId) { List connections = connectionWebService.removeConnectionById(connectionId); return ResponseEntity.ok(connections); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index ec91ccf39..7924a3078 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.PrivilegeService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -20,7 +20,7 @@ *

Endpoint for service handling business logic for privileges. *
Note: Only users with the super admin role can access this endpoint.

*/ -@Api +@Tag(name = "Privilege Management") @RestController @RequestMapping("/privilege") public class PrivilegeController { @@ -32,11 +32,11 @@ public PrivilegeController(PrivilegeService privilegeService) { this.privilegeService = privilegeService; } - @ApiOperation(value = "GET information of one Privilege with the UUID, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET information of one Privilege with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/{privilegeId}", produces = "application/json") public ResponseEntity getPrivilegeById( - @ApiParam(value="The UUID of the privilege to fetch information about") + @Parameter(description="The UUID of the privilege to fetch information about") @PathVariable("privilegeId") String privilegeId) { Privilege privilegeById = this.privilegeService.getPrivilegeById(privilegeId); @@ -47,7 +47,7 @@ public ResponseEntity getPrivilegeById( return PICSUREResponse.success(privilegeById); } - @ApiOperation(value = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/", produces = "application/json") public ResponseEntity getPrivilegeAll() { @@ -55,31 +55,31 @@ public ResponseEntity getPrivilegeAll() { return PICSUREResponse.success(privilegesAll); } - @ApiOperation(value = "POST a list of privileges, requires SUPER_ADMIN role") + @Operation(description = "POST a list of privileges, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(path = "/", consumes = "application/json", produces = "application/json") public ResponseEntity addPrivilege( - @ApiParam(required = true, value = "A list of privileges in JSON format") + @Parameter(required = true, description = "A list of privileges in JSON format") List privileges){ privileges = this.privilegeService.addPrivileges(privileges); return PICSUREResponse.success(privileges); } - @ApiOperation(value = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(path = "/", consumes = "application/json", produces = "application/json") public ResponseEntity updatePrivilege( - @ApiParam(required = true, value = "A list of privilege with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of privilege with fields to be updated in JSON format") List privileges){ privileges = this.privilegeService.updatePrivileges(privileges); return ResponseEntity.ok(privileges); } - @ApiOperation(value = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") + @Operation(description = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(path = "/{privilegeId}", produces = "application/json") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid privilege Id") + @Parameter(required = true, description = "A valid privilege Id") @PathVariable("privilegeId") final String privilegeId) { List privileges = this.privilegeService.deletePrivilegeByPrivilegeId(privilegeId); return ResponseEntity.ok(privileges); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index 4b59d9ea3..825b3b1b1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -23,7 +23,7 @@ *

Endpoint for service handling business logic for user roles. *
Note: Users with admin level access can view roles, but only super admin users can modify them.

*/ -@Api +@Tag(name = "Role Management") @Controller @RequestMapping(value = "/role") public class RoleController { @@ -35,16 +35,16 @@ public RoleController(RoleService roleService) { this.roleService = roleService; } - @ApiOperation(value = "GET information of one Role with the UUID, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET information of one Role with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json", path = "/{roleId}") public ResponseEntity getRoleById( - @ApiParam(value = "The UUID of the Role to fetch information about") + @Parameter(description = "The UUID of the Role to fetch information about") @PathVariable("roleId") String roleId) { return this.roleService.getRoleById(roleId); } - @ApiOperation(value = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") @GetMapping(produces = "application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) public ResponseEntity getRoleAll() { @@ -52,21 +52,21 @@ public ResponseEntity getRoleAll() { return PICSUREResponse.success(allRoles); } - @ApiOperation(value = "POST a list of Roles, requires SUPER_ADMIN role") + @Operation(description = "POST a list of Roles, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(produces = "application/json") public ResponseEntity addRole( - @ApiParam(required = true, value = "A list of Roles in JSON format") + @Parameter(required = true, description = "A list of Roles in JSON format") List roles) { List savedRoles = this.roleService.addRoles(roles); return PICSUREResponse.success("All roles are added.", savedRoles); } - @ApiOperation(value = "Update a list of Roles, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of Roles, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(produces = "application/json") public ResponseEntity updateRole( - @ApiParam(required = true, value = "A list of Roles with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of Roles with fields to be updated in JSON format") List roles) { List updatedRoles = this.roleService.updateRoles(roles); if (updatedRoles.isEmpty()) { @@ -76,11 +76,11 @@ public ResponseEntity updateRole( return PICSUREResponse.success("All Roles are updated.", updatedRoles); } - @ApiOperation(value = "DELETE an Role by Id only if the Role is not associated by others, requires SUPER_ADMIN role") + @Operation(description = "DELETE an Role by Id only if the Role is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(produces = "application/json", path = "/{roleId}") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid Role Id") + @Parameter(required = true, description = "A valid Role Id") @PathVariable("roleId") final String roleId) { Optional> roles = this.roleService.removeRoleById(roleId); if (roles.isEmpty()) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java index 577ecbd7e..de81ce3ef 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java @@ -1,10 +1,13 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -15,37 +18,42 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; + import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; /** *

Endpoint for creating and updating terms of service entities. Records when a user accepts a term of service.

*/ +@Tag(name = "Terms of Service Management") @Controller @RequestMapping("/tos") public class TermsOfSerivceController { private final TOSService tosService; + private final UserService userService; @Autowired - public TermsOfSerivceController(TOSService tosService) { + public TermsOfSerivceController(TOSService tosService, UserService userService) { this.tosService = tosService; + this.userService = userService; } - @ApiOperation(value = "GET the latest Terms of Service") + @Operation(description = "GET the latest Terms of Service") @GetMapping(path = "/latest", produces = "text/html") public ResponseEntity getLatestTermsOfService(){ return PICSUREResponse.success(tosService.getLatest()); } - @ApiOperation(value = "Update the Terms of Service html body") + @Operation(description = "Update the Terms of Service html body") @RolesAllowed({AuthNaming.AuthRoleNaming.ADMIN, SUPER_ADMIN}) @PostMapping(path = "/update", consumes = "text/html", produces = "application/json") public ResponseEntity updateTermsOfService( - @ApiParam(required = true, value = "A html page for updating") String html){ + @Parameter(required = true, description = "A html page for updating") String html){ return PICSUREResponse.success(tosService.updateTermsOfService(html)); } - @ApiOperation(value = "GET if current user has acceptted his TOS or not") + @Operation(description = "GET if current user has acceptted his TOS or not") @GetMapping(path = "/", produces = "text/plain") public ResponseEntity hasUserAcceptedTOS(){ SecurityContext context = SecurityContextHolder.getContext(); @@ -53,12 +61,13 @@ public ResponseEntity hasUserAcceptedTOS(){ return PICSUREResponse.success(tosService.hasUserAcceptedLatest(userSubject)); } - @ApiOperation(value = "Endpoint for current user to accept his terms of service") + @Operation(description = "Endpoint for current user to accept his terms of service") @PostMapping(path = "/accept", produces = "application/json") public ResponseEntity acceptTermsOfService(){ SecurityContext context = SecurityContextHolder.getContext(); String userSubject = context.getAuthentication().getName(); - tosService.acceptTermsOfService(userSubject); + User user = tosService.acceptTermsOfService(userSubject); + userService.updateUser(List.of(user)); return PICSUREResponse.success(); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index fdff23928..1e21e9624 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -2,9 +2,9 @@ import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TokenService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -23,7 +23,7 @@ * is authorized to access the URLs they queried and send data along with them. The AuthorizationService class handles authorization * {@link AuthorizationService} at the access rule level, but this endpoint handles token validation and pre-check at the privilege level.

*/ -@Api +@Tag(name = "Token Management") @Controller("/token") public class TokenController { @@ -34,16 +34,16 @@ public TokenController(TokenService tokenService) { this.tokenService = tokenService; } - @ApiOperation(value = "Token introspection endpoint for user to retrieve a valid token") + @Operation(description = "Token introspection endpoint for user to retrieve a valid token") @PostMapping(path = "/inspect", produces = "application/json") public ResponseEntity inspectToken( - @ApiParam(required = true, value = "A JSON object that at least" + + @Parameter(required = true, description = "A JSON object that at least" + " include a user the token for validation") Map inputMap) { return this.tokenService.inspectToken(inputMap); } - @ApiOperation(value = "To refresh current user's token if the user is an active user") + @Operation(description = "To refresh current user's token if the user is an active user") @GetMapping(path = "/refresh", produces = "application/json") public ResponseEntity refreshToken(@RequestHeader("Authorization") String authorizationHeader) { return this.tokenService.refreshToken(authorizationHeader); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index a52f8fbb9..f82290ea0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +15,6 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; -import javax.transaction.Transactional; import java.util.*; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -24,7 +23,7 @@ /** *

Endpoint for service handling business logic for users.

*/ -@Api +@Tag(name = "User Management") @Controller("/user") public class UserController { @@ -38,17 +37,17 @@ public UserController(UserService userService) { this.userService = userService; } - @ApiOperation(value = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") + @Operation(description = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/{userId}", produces = "application/json") public ResponseEntity getUserById( - @ApiParam(required = true, value = "The UUID of the user to fetch information about") + @Parameter(required = true, description = "The UUID of the user to fetch information about") @PathVariable("userId") String userId) { User userById = this.userService.getUserById(userId); return ResponseEntity.ok(userById); } - @ApiOperation(value = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") + @Operation(description = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json") public ResponseEntity getUserAll() { @@ -56,18 +55,17 @@ public ResponseEntity getUserAll() { return ResponseEntity.ok(entityAll); } - @ApiOperation(value = "POST a list of users, requires ADMIN role") - @Transactional // TODO: Move this to the service layer + @Operation(description = "POST a list of users, requires ADMIN role") @RolesAllowed({ADMIN}) @PostMapping(produces = "application/json") public ResponseEntity addUser( - @ApiParam(required = true, value = "A list of user in JSON format") + @Parameter(required = true, description = "A list of user in JSON format") List users) { return this.userService.addUsers(users); } - @ApiOperation(value = "Update a list of users, will only update the fields listed, requires ADMIN role") + @Operation(description = "Update a list of users, will only update the fields listed, requires ADMIN role") @RolesAllowed({ADMIN}) @PutMapping(produces = "application/json") public ResponseEntity updateUser(List users) { @@ -82,19 +80,19 @@ public ResponseEntity updateUser(List users) { * @param hasToken * @return */ - @ApiOperation(value = "Retrieve information of current user") + @Operation(description = "Retrieve information of current user") @GetMapping(produces = "application/json", path = "/me") public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, - @ApiParam(required = false, value = "Attribute that represents if a long term token will attach to the response") + @Parameter(required = false, description = "Attribute that represents if a long term token will attach to the response") @RequestParam("hasToken") Boolean hasToken) { return this.userService.getCurrentUser(authorizationHeader, hasToken); } - @ApiOperation(value = "Retrieve the queryTemplate of certain application by given application Id for the currentUser ") + @Operation(description = "Retrieve the queryTemplate of certain application by given application Id for the currentUser ") @GetMapping(path = "/me/queryTemplate/{applicationId}", produces = "application/json") public ResponseEntity getQueryTemplate( - @ApiParam(value = "Application Id for the returning queryTemplate") + @Parameter(description = "Application Id for the returning queryTemplate") @PathVariable("applicationId") String applicationId) { Optional mergedTemplate = this.userService.getQueryTemplate(applicationId); @@ -106,7 +104,7 @@ public ResponseEntity getQueryTemplate( return PICSUREResponse.success(Map.of("queryTemplate", mergedTemplate.orElse(null))); } - @ApiOperation(value = "Retrieve the queryTemplate of default application") + @Operation(description = "Retrieve the queryTemplate of default application") @GetMapping(path = "/me/queryTemplate", produces = "application/json") public ResponseEntity getQueryTemplate() { return this.userService.getDefaultQueryTemplate(); @@ -121,7 +119,7 @@ public ResponseEntity getQueryTemplate() { * @param httpHeaders the http headers * @return the refreshed long term token */ - @ApiOperation(value = "refresh the long term tokne of current user") + @Operation(description = "refresh the long term tokne of current user") @GetMapping(path = "/me/refresh_long_term_token", produces = "application/json") public ResponseEntity refreshUserToken( @RequestHeader HttpHeaders httpHeaders) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index 892be8a60..f6fb42a50 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -3,9 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -21,7 +21,7 @@ *

Endpoint for service handling business logic for user metadata mapping.

*

*/ -@Api +@Tag(name = "User Metadata Mapping Management") @Controller @RequestMapping("/mapping") public class UserMetadataMappingWebController { @@ -33,14 +33,14 @@ public UserMetadataMappingWebController(UserMetadataMappingService mappingServic this.mappingService = mappingService; } - @ApiOperation(value = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "{connectionId}", produces = "application/json") public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { return this.mappingService.getAllMappingsForConnection(connection); } - @ApiOperation(value = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") + @Operation(description = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/", produces = "application/json") public ResponseEntity getAllMappings() { @@ -48,11 +48,11 @@ public ResponseEntity getAllMappings() { return PICSUREResponse.success(allMappings); } - @ApiOperation(value = "POST a list of UserMetadataMappings, requires SUPER_ADMIN role") + @Operation(description = "POST a list of UserMetadataMappings, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(path = "/", consumes = "application/json", produces = "application/json") public ResponseEntity addMapping( - @ApiParam(required = true, value = "A list of UserMetadataMapping in JSON format") + @Parameter(required = true, description = "A list of UserMetadataMapping in JSON format") List mappings) { try { @@ -63,11 +63,11 @@ public ResponseEntity addMapping( } } - @ApiOperation(value = "Update a list of UserMetadataMappings, will only update the fields listed, requires SUPER_ADMIN role") + @Operation(description = "Update a list of UserMetadataMappings, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(path = "/", consumes = "application/json", produces = "application/json") public ResponseEntity updateMapping( - @ApiParam(required = true, value = "A list of UserMetadataMapping with fields to be updated in JSON format") + @Parameter(required = true, description = "A list of UserMetadataMapping with fields to be updated in JSON format") List mappings) { List userMetadataMappings = this.mappingService.updateUserMetadataMappings(mappings); @@ -78,11 +78,11 @@ public ResponseEntity updateMapping( return PICSUREResponse.success(userMetadataMappings); } - @ApiOperation(value = "DELETE an UserMetadataMapping by Id only if the UserMetadataMapping is not associated by others, requires SUPER_ADMIN role") + @Operation(description = "DELETE an UserMetadataMapping by Id only if the UserMetadataMapping is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(path = "/{mappingId}", produces = "application/json") public ResponseEntity removeById( - @ApiParam(required = true, value = "A valid UserMetadataMapping Id") + @Parameter(required = true, description = "A valid UserMetadataMapping Id") @PathVariable("mappingId") final String mappingId) { List userMetadataMappings = this.mappingService.removeMetadataMappingByIdAndRetrieveAll(mappingId); return PICSUREResponse.success(userMetadataMappings); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java index 3ef9cd348..3fe25997e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java @@ -5,8 +5,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import static org.springframework.security.config.Customizer.withDefaults; @@ -23,17 +23,13 @@ public SecurityConfig(JWTFilter jwtFilter) { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((authz) -> - authz.anyRequest().authenticated() + authz.requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json").permitAll() + .anyRequest().authenticated() ) - .addFilter(this.jwtFilter) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .httpBasic(withDefaults()); return http.build(); } - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json"); - } - } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java index 50f871a2a..8bad31127 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeService.java @@ -8,8 +8,8 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import javax.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java index 62e079e0f..09c06b0f0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleService.java @@ -4,8 +4,8 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import javax.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -16,7 +16,7 @@ public class AccessRuleService { private final AccessRuleRepository accessRuleRepo; @Autowired - protected AccessRuleService(Class type, AccessRuleRepository accessRuleRepo) { + protected AccessRuleService(AccessRuleRepository accessRuleRepo) { this.accessRuleRepo = accessRuleRepo; } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index e576d4ff2..5f680df08 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -2,20 +2,17 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; -import org.hibernate.PropertyNotFoundException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import javax.transaction.Transactional; import java.util.*; @Service @@ -29,7 +26,7 @@ public class ApplicationService { private String CLIENT_SECRET; @Autowired - protected ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo) { + ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo) { this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java index 2812b87df..c4765f2ee 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java @@ -4,6 +4,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @@ -19,6 +20,7 @@ protected ConnectionWebService(ConnectionRepository connectionRepo) { this.connectionRepo = connectionRepo; } + @Transactional public List addConnection(List connections) throws IllegalArgumentException { for (Connection c : connections){ if (c.getSubPrefix() == null || c.getRequiredFields() == null || c.getLabel() == null || c.getId() == null){ @@ -46,6 +48,7 @@ public List updateConnections(List connections) { return this.connectionRepo.saveAll(connections); } + @Transactional public List removeConnectionById(String connectionId) { this.connectionRepo.deleteById(UUID.fromString(connectionId)); return this.getAllConnections(); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 1026f71b2..5125ca92b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -5,7 +5,6 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.TermsOfServiceRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfSerivceController; -import edu.harvard.hms.dbmi.avillach.auth.rest.UserController; import jakarta.persistence.NoResultException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -33,13 +32,11 @@ public class TOSService { private final UserRepository userRepo; - private final UserController userService; // TODO: This isn't a service its a controller. Why are we doing this? @Autowired - public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo, UserController userService) { + public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo) { this.termsOfServiceRepo = termsOfServiceRepo; this.userRepo = userRepo; - this.userService = userService; } @@ -74,17 +71,16 @@ public String getLatest() { } } - public void acceptTermsOfService(String userId) { + public User acceptTermsOfService(String userId) { logger.info("User {} accepting TOS", userId); User user = userRepo.findBySubject(userId); if (user == null) { throw new RuntimeException("User does not exist"); } user.setAcceptedTOS(new Date()); - List users = List.of(user); Date tosDate = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getDateUpdated(); - userService.updateUser(users); logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(), tosDate.toString()); + return user; } private boolean checkAgainstTOSDate(String userId) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index 4cfeb1a9c..a14e9c19c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -8,8 +8,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import javax.transaction.Transactional; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 9c30ea738..8f53907c5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -29,8 +29,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; - -import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.time.ZoneOffset; @@ -60,7 +59,7 @@ public class UserService { @Autowired public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, ApplicationRepository applicationRepository, RoleService roleService, @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime, - @Value("${application.default.}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { + @Value("${application.default.uuid}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { this.basicMailService = basicMailService; this.tosService = tosService; this.userRepository = userRepository; @@ -194,6 +193,7 @@ private boolean allowUpdateSuperAdminRole( } + @Transactional public ResponseEntity addUsers(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); User currentUser = (User) securityContext.getAuthentication().getPrincipal(); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java index ecbdf0efd..ef89e8306 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java @@ -1,9 +1,9 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.validation.constraints.NotNull; import java.util.*; import java.util.stream.Collectors; diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index f82505076..b9e7a63ef 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -2,15 +2,15 @@ server.port=8090 server.servlet.context-path=/auth -spring.datasource.url=jdbc:your_database_url -spring.datasource.username=your_db_username -spring.datasource.password=your_db_password -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/auth?useSSL=false&serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=password +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.YourDialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com @@ -46,5 +46,7 @@ application.fence.standard.access.rules=your_standard_access_rules application.fence.harmonized.concept.path=your_harmonized_concept_path # Token Expiration -application.token.expiration.time=3600000 # 1 hour in milliseconds -application.long.term.token.expiration.time=2592000000 # 30 days in milliseconds +# 1 hour in milliseconds +application.token.expiration.time=3600000 +# 30 days in milliseconds +application.long.term.token.expiration.time=2592000000 diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java index 35d46c8e7..0019bd640 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java @@ -1,26 +1,38 @@ package edu.harvard.hms.dbmi.avillach; import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.PrivilegeService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import java.util.UUID; +@RunWith(MockitoJUnitRunner.class) public class ApplicationServiceTest { - @Mock + @InjectMocks private ApplicationService applicationService; + @Mock + private PrivilegeRepository privilegeRepository; + + @Mock + private ApplicationRepository applicationRepository; + @Before public void init() { } @Test public void testGenerateToken(){ - Application application = new Application(); application.setUuid(UUID.randomUUID()); application.setName("Testing Application"); From b612748517ec5bdd0333a2c51e89c5b752d2246f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 10 Apr 2024 14:57:13 -0400 Subject: [PATCH 016/222] Add trace level logging to improve debugging --- .../src/main/resources/application.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index b9e7a63ef..2542ccbdc 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -50,3 +50,6 @@ application.fence.harmonized.concept.path=your_harmonized_concept_path application.token.expiration.time=3600000 # 30 days in milliseconds application.long.term.token.expiration.time=2592000000 + +# Logging +logging.level.org.springframework.security=TRACE \ No newline at end of file From dfce7355058d3f46027d2e216089312499b9a7b6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 10 Apr 2024 14:57:39 -0400 Subject: [PATCH 017/222] Refactor logging in JWTFilter --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 184e21b1e..2afa91a64 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -81,7 +81,6 @@ public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, * @throws IOException if an I/O error occurs during the execution of the filter */ @Override - // Ends that are allowed are handled by the configured security filter chain in the SecurityConfig class protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException { // Get headers from the request String authorizationHeader = request.getHeader("Authorization"); @@ -120,21 +119,20 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet // Check if user is attempting to access the correct introspect endpoint. If not reject the request // log an error indicating the user's token may be being used by a malicious actor. if (!request.getRequestURI().endsWith("token/inspect")) { - logger.error(userId + " attempted to perform request " + request.getRequestURI() + " token may be compromised."); + logger.error("{} attempted to perform request {} token may be compromised.", userId, request.getRequestURI()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User is deactivated"); } // Authenticate as Application Optional authenticatedApplication = applicationRepo.findById(UUID.fromString(userId.split("\\|")[1])); if (authenticatedApplication.isEmpty()) { - logger.error("Cannot find an application by userId: " + userId); + logger.error("Cannot find an application by userId: {}", userId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); return; } if (!authenticatedApplication.get().getToken().equals(token)) { - logger.error("filter() incoming application token - " + token + - " - is not the same as record, might because the token has been refreshed. Subject: " + userId); + logger.error("filter() incoming application token - {} - is not the same as record, might because the token has been refreshed. Subject: {}", token, userId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token has been inactivated, please contact admin to grab you the latest one."); } From a4dabcce00316ae9ee0b465d200708aab45d2ab3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 09:32:22 -0400 Subject: [PATCH 018/222] Refactor security configuration and improve authentication handling Code changes include an extensive refactoring of the security configuration to disable CSRF, enable stateless sessions, and other settings. A spring-boot-devtools dependency was added to the pom.xml file for development purposes. Authentication handling was also improved in the AuthController by adding additional checks and better handling of null requests. Trace logging for spring security was deactivated from application.properties file. A test file for authentication was also added. --- pic-sure-auth-services/AuthenicationTest.http | 7 ++++++ pic-sure-auth-services/pom.xml | 8 +++++++ .../hms/dbmi/avillach/auth/Application.java | 4 +++- .../dbmi/avillach/auth/filter/JWTFilter.java | 8 ++++--- .../avillach/auth/rest/AuthController.java | 17 +++++++++++++- .../auth/security/SecurityConfig.java | 23 +++++++++++++------ .../src/main/resources/application.properties | 2 +- 7 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 pic-sure-auth-services/AuthenicationTest.http diff --git a/pic-sure-auth-services/AuthenicationTest.http b/pic-sure-auth-services/AuthenicationTest.http new file mode 100644 index 000000000..2f9845631 --- /dev/null +++ b/pic-sure-auth-services/AuthenicationTest.http @@ -0,0 +1,7 @@ +POST http://localhost:8090/auth/authentication +Content-Type: application/json + +# JSON Encode request parameters +{ + "test": "test" +} \ No newline at end of file diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index dec0fcb3f..1e1910644 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -68,6 +68,14 @@ spring-boot-starter-data-jpa 3.2.4 + + + org.springframework.boot + spring-boot-devtools + 3.2.4 + development + true + com.mysql diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java index 25c528eb6..a1d987168 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/Application.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -@SpringBootApplication +@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class}) @EnableJpaRepositories public class Application { public static void main(String[] args) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 2afa91a64..23885fe9a 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -11,6 +11,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; @@ -81,13 +82,14 @@ public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, * @throws IOException if an I/O error occurs during the execution of the filter */ @Override - protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException { + protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws IOException, ServletException { // Get headers from the request String authorizationHeader = request.getHeader("Authorization"); if (!StringUtils.isNotBlank(authorizationHeader)) { - // If the header is not present, then the request is not authorized - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "No authorization header found."); + // If the header is not present, we allow the request to pass through + // without any authentication or authorization checks + filterChain.doFilter(request, response); } else { // If the header is present, we need to check the token String token = authorizationHeader.substring(6).trim(); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index 1e25828d2..10ab75342 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -13,6 +13,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import java.io.IOException; @@ -46,8 +47,22 @@ public AuthController(AuthorizationService authorizationService, AuthenticationS @Operation(description = "The authentication endpoint for retrieving a valid user token") @PostMapping(path = "/authentication", consumes = "application/json", produces = "application/json") - public ResponseEntity authentication(@Parameter(required = true, description = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") Map authRequest) throws IOException { + public ResponseEntity authentication( + @Parameter(required = true, description = "A json object that includes all Oauth authentication needs, for example, access_token and redirectURI") + @RequestBody Map authRequest) throws IOException { logger.debug("authentication() starting..."); + + if(authRequest == null) { + logger.error("authentication() authRequest is null"); + return ResponseEntity.badRequest().body("authRequest is null"); + } + + if(authRequest.containsKey("test")) { + logger.debug("authentication() test authentication"); + // This is a test request, just return a 200 + return ResponseEntity.ok().build(); + } + if (this.idp_provider.equalsIgnoreCase("fence")) { logger.debug("authentication() FENCE authentication"); return fenceAuthenticationService.getFENCEProfile(authRequest); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java index 3fe25997e..bd27634fc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java @@ -4,13 +4,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import static org.springframework.security.config.Customizer.withDefaults; +import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration +@EnableMethodSecurity +@EnableWebSecurity public class SecurityConfig { private final JWTFilter jwtFilter; @@ -19,15 +24,19 @@ public class SecurityConfig { public SecurityConfig(JWTFilter jwtFilter) { this.jwtFilter = jwtFilter; } - +//"/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json","/auth/authentication" @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authz) -> - authz.requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json").permitAll() - .anyRequest().authenticated() + http.csrf(AbstractHttpConfigurer::disable) + .sessionManagement((session) -> session.sessionCreationPolicy(STATELESS)) + .authorizeHttpRequests((authorizeRequests) -> + authorizeRequests.requestMatchers("/auth/authentication", "/authentication").permitAll() + .anyRequest().authenticated() ) - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) - .httpBasic(withDefaults()); + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .logout(AbstractHttpConfigurer::disable) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 2542ccbdc..17c9ef4fd 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -52,4 +52,4 @@ application.token.expiration.time=3600000 application.long.term.token.expiration.time=2592000000 # Logging -logging.level.org.springframework.security=TRACE \ No newline at end of file +#logging.level.org.springframework.security=TRACE \ No newline at end of file From 3450930e2a55e1b506811b5168ecf0c32a4fa589 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 09:33:55 -0400 Subject: [PATCH 019/222] Update authorized request paths in SecurityConfig The commit expands the permitted public paths within the SecurityConfig file. The "/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json" paths have been added to the request matchers to allow these requests to pass through unhindered. --- .../hms/dbmi/avillach/auth/security/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java index bd27634fc..8cc4daadd 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java @@ -24,13 +24,13 @@ public class SecurityConfig { public SecurityConfig(JWTFilter jwtFilter) { this.jwtFilter = jwtFilter; } -//"/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json","/auth/authentication" + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .sessionManagement((session) -> session.sessionCreationPolicy(STATELESS)) .authorizeHttpRequests((authorizeRequests) -> - authorizeRequests.requestMatchers("/auth/authentication", "/authentication").permitAll() + authorizeRequests.requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json","/authentication").permitAll() .anyRequest().authenticated() ) .httpBasic(AbstractHttpConfigurer::disable) From f66615893365b2a6579e7bba821c8d412ea7a901 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 10:10:11 -0400 Subject: [PATCH 020/222] Remove test code --- .../harvard/hms/dbmi/avillach/auth/rest/AuthController.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index 10ab75342..1ed79334c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -57,12 +57,6 @@ public ResponseEntity authentication( return ResponseEntity.badRequest().body("authRequest is null"); } - if(authRequest.containsKey("test")) { - logger.debug("authentication() test authentication"); - // This is a test request, just return a 200 - return ResponseEntity.ok().build(); - } - if (this.idp_provider.equalsIgnoreCase("fence")) { logger.debug("authentication() FENCE authentication"); return fenceAuthenticationService.getFENCEProfile(authRequest); From d2ed5fcb71a426ae09fac7dbe9cd3182730c4593 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 10:10:20 -0400 Subject: [PATCH 021/222] Optimize log message formatting in AuthenticationService This update replaces string concatenation in a logging statement with parameterized logging. This change improves logging efficiency by avoiding unnecessary string concatenation when the logging level is set such that the log message wouldn't be outputted. --- .../dbmi/avillach/auth/service/impl/AuthenticationService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 972141952..48d729b1c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -80,8 +80,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce } String userId = userIdNode.asText(); - logger.info("Successfully retrieved userId, " + userId + - ", from the provided code and redirectURI"); + logger.info("Successfully retrieved userId, {}, from the provided code and redirectURI", userId); String connectionId; try { From 56339f011389f3e401213c0bb6b2f1a4c6749e3f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 10:10:41 -0400 Subject: [PATCH 022/222] Update dependency and refactor JWTUtil Updated com.auth0 dependency version from 1.8.0 to 2.10.1 in pom.xml. Also, modified import statement and argument on Base64.decodeBase64 in JWTUtil. The change was needed to support the upgrade and ensure proper parsing of Base64 encoded client secrets. --- pic-sure-auth-services/pom.xml | 2 +- .../edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 1e1910644..e52b68c10 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -90,7 +90,7 @@ com.auth0 auth0 - 1.8.0 + 2.10.1 junit diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index a908d03ab..28e67bb97 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -2,7 +2,7 @@ import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import io.jsonwebtoken.*; -import org.apache.commons.codec.binary.Base64; +import org.apache.tomcat.util.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -88,7 +88,7 @@ public static Jws parseToken(String token) { try { if (clientSecretIsBase64) { // handle if client secret is base64 encoded - jws = Jwts.parser().setSigningKey(Base64.decodeBase64(clientSecret.getBytes(StandardCharsets.UTF_8))).parseClaimsJws(token); + jws = Jwts.parser().setSigningKey(Base64.decodeBase64(clientSecret)).parseClaimsJws(token); } else { // handle if client secret is not base64 encoded jws = Jwts.parser().setSigningKey(clientSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); From 0387850f54145694d477a137089cbf9c30ae0803 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 12:55:36 -0400 Subject: [PATCH 023/222] Update import statement in JsonUtils.java The import statement for NotNull annotation in JsonUtils.java file has been updated. The previous import from org.jetbrains.annotations.NotNull has been replaced with jakarta.validation.constraints.NotNull. --- .../edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java index ef89e8306..03d5fa958 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JsonUtils.java @@ -1,6 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import org.jetbrains.annotations.NotNull; +import jakarta.validation.constraints.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From cb6ac1c31a2360200e16c729ce02375d66b60271 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 12:57:15 -0400 Subject: [PATCH 024/222] Update pom.xml version and dependencies Increased the pic-sure-auth-services version from 1.0 to 2.0 and changed the packaging from war to jar. Removed some dependencies, added and reordered some others to improve the project setup. Also, updated the build plugins not only to reflect these changes but to streamline the build process as well. --- pic-sure-auth-services/pom.xml | 97 +++++++++------------------------- 1 file changed, 26 insertions(+), 71 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index e52b68c10..8789f2562 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -5,34 +5,25 @@ pic-sure-auth-microapp edu.harvard.hms.dbmi.avillach.picsure.auth.microapp - 1.0-SNAPSHOT + 2.0-SNAPSHOT edu.harvard.hms.dbmi.avillach pic-sure-auth-services - war + jar + 2.3.0 - - org.springframework.boot - spring-boot - 3.2.3 - org.springframework.boot spring-boot-starter-validation RELEASE compile - - - net.bytebuddy - byte-buddy - 1.14.13 - + org.springframework @@ -76,6 +67,12 @@ development true + + + net.bytebuddy + byte-buddy + 1.14.13 + com.mysql @@ -145,12 +142,6 @@ swagger-annotations-jakarta 2.2.21 - - - jakarta.transaction - jakarta.transaction-api - 2.0.1 - jakarta.xml.bind @@ -186,66 +177,30 @@ jackson-annotations 2.17.0 - - org.jetbrains - annotations - RELEASE - compile - - ${project.artifactId} - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.11.0 - - 21 - - - - org.apache.maven.plugins - maven-war-plugin - - true - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - retrieve-mysql-driver - generate-sources - - copy - - false - - - - mysql - mysql-connector-java - 5.1.38 - jar - true - - ${project.build.directory}/modules/system/layers/base/com/sql/mysql/main/ - - - - - - + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + + org.springframework.boot + spring-boot-maven-plugin + 3.2.4 + + edu.harvard.hms.dbmi.avillach.auth.Application + JAR + + org.codehaus.mojo build-helper-maven-plugin From 23996d55a8c5d3e7bd6d592c8e322da5ed34a113 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 12:57:27 -0400 Subject: [PATCH 025/222] Update pom.xml version Increased the pic-sure-auth-services version from 1.0 to 2.0. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a9debb4cf..f0a497ade 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 edu.harvard.hms.dbmi.avillach.picsure.auth.microapp pic-sure-auth-microapp - 1.0-SNAPSHOT + 2.0-SNAPSHOT pom pic-sure-auth-microapp From 3dbd70580a85bc5af62eda0ce8b08bb5ddd872e7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 12:58:23 -0400 Subject: [PATCH 026/222] Move original dockerfile to bak Moved original dockerfile to the bak directory in order to be able to quickly reference it fow now. --- pic-sure-auth-services/Dockerfile | 42 +++---------------------------- 1 file changed, 4 insertions(+), 38 deletions(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 41b42c7d8..10d45399d 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -1,40 +1,6 @@ -FROM jboss/wildfly:17.0.0.Final AS production +FROM amazoncorretto:21.0.1-alpine3.18 -# TODO: It'd be nice if wildfly used alpine -Andre +# Copy jar and access token from maven build +COPY target/pic-sure-auth-services-*.jar /pic-sure-auth-service.jar -LABEL maintainer="avillach_lab_developers@googlegroups.com" - -# mysql database -ENV AUTH_DB_CONNECTION_USER root -ENV AUTH_MYSQLADDRESS localhost -ENV AUTH_DB_PORT 3306 -ENV AUTH_MYSQLPASS password - -# JWT Token -ENV AUTH_CLIENT_SECRET secret -ENV AUTH_USER_ID_CLAIM sub - -# root required to create default scratch directory - Andre -USER root - -# Add certs for AWS to trust store -COPY aws_certs/certificate.der certificate.der -RUN keytool -noprompt -import -alias root_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file certificate.der - -# copy modules -COPY target/modules/system/layers/base/com/sql/mysql/main/* /modules/ -COPY src/main/resources/emailTemplates/accessEmail.mustache /usr/local/share/applications/ - -# install modules -RUN wildfly/bin/jboss-cli.sh --command="module add --name=com.sql.mysql \ - --resources=/modules/mysql-connector-java-5.1.38.jar --dependencies=javax.api" - -# Copy standalone.xml -COPY bak/standalone.xml wildfly/standalone/configuration/ - -# Copy war file -COPY target/pic-sure-auth-services.war wildfly/standalone/deployments/pic-sure-auth-services.war - -#USER jboss - -ENTRYPOINT ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"] +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /pic-sure-auth-service.jar"] \ No newline at end of file From 60a588e81148c12c8f488af7e8f161f9e32d607e Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 12:58:57 -0400 Subject: [PATCH 027/222] Create new Dockerfile for java 21 and tomcat --- pic-sure-auth-services/bak/Dockerfile | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 pic-sure-auth-services/bak/Dockerfile diff --git a/pic-sure-auth-services/bak/Dockerfile b/pic-sure-auth-services/bak/Dockerfile new file mode 100644 index 000000000..aa603a904 --- /dev/null +++ b/pic-sure-auth-services/bak/Dockerfile @@ -0,0 +1,40 @@ +FROM jboss/wildfly:17.0.0.Final AS production + +# TODO: It'd be nice if wildfly used alpine -Andre + +LABEL maintainer="avillach_lab_developers@googlegroups.com" + +# mysql database +ENV AUTH_DB_CONNECTION_USER root +ENV AUTH_MYSQLADDRESS localhost +ENV AUTH_DB_PORT 3306 +ENV AUTH_MYSQLPASS password + +# JWT Token +ENV AUTH_CLIENT_SECRET secret +ENV AUTH_USER_ID_CLAIM sub + +# root required to create default scratch directory - Andre +USER root + +# Add certs for AWS to trust store +COPY ../aws_certs/certificate.der certificate.der +RUN keytool -noprompt -import -alias root_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file certificate.der + +# copy modules +COPY target/modules/system/layers/base/com/sql/mysql/main/* /modules/ +COPY ../src/main/resources/emailTemplates/accessEmail.mustache /usr/local/share/applications/ + +# install modules +RUN wildfly/bin/jboss-cli.sh --command="module add --name=com.sql.mysql \ + --resources=/modules/mysql-connector-java-5.1.38.jar --dependencies=javax.api" + +# Copy standalone.xml +COPY standalone.xml wildfly/standalone/configuration/ + +# Copy war file +COPY ../target/pic-sure-auth-services.war wildfly/standalone/deployments/pic-sure-auth-services.war + +#USER jboss + +ENTRYPOINT ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"] From 25785c4d09abc509cb84520eb614c766acc812b6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 16:52:11 -0400 Subject: [PATCH 028/222] Update pom.xml to repackage application --- pic-sure-auth-services/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 8789f2562..ea662223c 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -199,6 +199,13 @@ edu.harvard.hms.dbmi.avillach.auth.Application JAR + + + + repackage + + + From 4129b2193665811e1d4f2f73ac3ce0eca01f2254 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 16:52:46 -0400 Subject: [PATCH 029/222] Update application.properties to use environment variables The application.properties file has been updated to make use of environment variables. This brings more flexibility and security as it allows for passing sensitive data, like server port numbers and passwords, through the system environment rather than having them in the plain text files. The default values are set to maintain backward compatibility. --- .../src/main/resources/application.properties | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 17c9ef4fd..a26a60956 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -1,55 +1,54 @@ # Application server port and context path -server.port=8090 +server.port=${SERVER_PORT:8090} server.servlet.context-path=/auth -spring.datasource.url=jdbc:mysql://localhost:3306/auth?useSSL=false&serverTimezone=UTC -spring.datasource.username=root -spring.datasource.password=password +spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://picsure-db:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&serverTimezone=UTC} +spring.datasource.username=${DATASOURCE_USERNAME:root} +spring.datasource.password=${DATASOURCE_PASSWORD:password} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com spring.mail.port=587 -spring.mail.username=your_email@gmail.com -spring.mail.password=your_email_password +spring.mail.username=${EMAIL_ADDRESS:your_email@gmail.com} +spring.mail.password=${EMAIL_PASSWORD:your_email_password} spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true # Custom application properties -application.client.id=your_client_id -application.client.secret=your_client_secret -application.client.secret.base64=your_base64_flag -application.user.id.claim=sub -application.auth0.host=your_auth0_host -application.tos.enabled=true -application.default.uuid=your_default_application_uuid -application.system.name=your_system_name -application.template.path=your_template_path -application.access.grant.email.subject=your_email_subject -application.user.activation.reply.to=your_reply_to_address -application.admin.users=your_admin_users -application.denied.email.enabled=your_denied_email_enabled_flag +application.client.id=${CLIENT_ID:client_id} +application.client.secret=${CLIENT_SECRET:client_secret} +application.client.secret.base64=${CLIENT_SECRET_IS_BASE_64:false} +application.user.id.claim=${USER_ID_CLAIM:sub} +application.auth0.host=${AUTH0_HOST:https://__AUTH0_DOMAIN__.auth0.com/} +application.tos.enabled=${TOS_ENABLED:true} +application.default.uuid=${__STACK_SPECIFIC_APPLICATION_ID__} +application.system.name=${SYSTEM_NAME:PIC-SURE All-in-one} +application.template.path=${TEMPLATE_PATH:/opt/jboss/wildfly/standalone/configuration/emailTemplates/} +application.access.grant.email.subject=${GRANT_EMAIL_SUBJECT:email_subject} +application.user.activation.reply.to=${USER_ACTIVATION_REPLY_TO:__USER_ACTIVATION_REPLY_TO__} +application.admin.users=${ADMIN_USERS:__ADMIN_USERS__} +application.denied.email.enabled=${DENIED_EMAIL_ENABLED:false} # IDP Provider configurations -application.idp.provider=your_idp_provider -application.idp.provider.uri=your_idp_provider_uri -application.fence.client.id=your_fence_client_id -application.fence.client.secret=your_fence_client_secret -application.fence.redirect.url=your_fence_redirect_url -application.fence.consent.group.concept.path=your_consent_group_concept_path -application.fence.standard.access.rules=your_standard_access_rules -application.fence.harmonized.concept.path=your_harmonized_concept_path +application.idp.provider=${IDP_PROVIDER:auth0} +application.idp.provider.uri=${IDP_PROVIDER_URI:https://__AUTH0_DOMAIN__.auth0.com/} +application.fence.client.id=${FENCE_CLIENT_ID:false} +application.fence.client.secret=${FENCE_CLIENT_SECRET:false} +application.fence.redirect.url=${FENCE_REDIRECT_URL:false} +application.fence.consent.group.concept.path=${FENCE_CONSENT_GROUP_CONCEPT_PATH:false} +application.fence.standard.access.rules=${FENCE_STANDARD_ACCESS_RULES:false} +application.fence.harmonized.concept.path=${FENCE_HARMONIZED_CONCEPT_PATH:false} # Token Expiration # 1 hour in milliseconds -application.token.expiration.time=3600000 +application.token.expiration.time=${TOKEN_EXPIRATION_TIME:3600000} # 30 days in milliseconds -application.long.term.token.expiration.time=2592000000 +application.long.term.token.expiration.time=${LONG_TERM_TOKEN_EXPIRATION_TIME:2592000000} # Logging #logging.level.org.springframework.security=TRACE \ No newline at end of file From 1e75a386351b6915f315c2d67e0d2b7388a43e04 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 16:53:12 -0400 Subject: [PATCH 030/222] Add Dockerfile and docker-compose for auth services for development --- docker-compose.dev.yml | 30 +++++++++++++++++++++++++++ pic-sure-auth-services/dev.Dockerfile | 8 +++++++ 2 files changed, 38 insertions(+) create mode 100644 docker-compose.dev.yml create mode 100644 pic-sure-auth-services/dev.Dockerfile diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 000000000..ffdcd40c6 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,30 @@ +version: '3.8' +services: + pic-sure-auth-service: + build: + context: ./pic-sure-auth-services/ + dockerfile: dev.Dockerfile + environment: + JAVA_OPTS: "-Xmx1024m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + ports: + - "8090:8090" # Application port + - "5005:5005" # Debug port + depends_on: + pic-sure-auth-db: + condition: service_healthy + + pic-sure-auth-db: + build: + context: ./pic-sure-auth-db/ + dockerfile: Dockerfile + environment: + MYSQL_DATABASE: auth + MYSQL_ROOT_PASSWORD: password +# command: --default-authentication-plugin=mysql_native_password + ports: + - "3306:3306" # MySQL port + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 \ No newline at end of file diff --git a/pic-sure-auth-services/dev.Dockerfile b/pic-sure-auth-services/dev.Dockerfile new file mode 100644 index 000000000..d4e36facd --- /dev/null +++ b/pic-sure-auth-services/dev.Dockerfile @@ -0,0 +1,8 @@ +FROM amazoncorretto:21.0.1-alpine3.18 + +# Copy jar and access token from maven build +COPY target/pic-sure-auth-services.jar /pic-sure-auth-service.jar + +EXPOSE 8090 + +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /pic-sure-auth-service.jar"] \ No newline at end of file From ccd5c0ff0a6afb193a11d65a928ce4c66156e482 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 16:53:34 -0400 Subject: [PATCH 031/222] Add AWS certificate to Dockerfile In this update, an AWS certificate is copied into the pic-sure-auth-services Dockerfile. The certificate is subsequently imported into the Java trust store. This ensures secure and trusted connections when running the pic-sure-auth-service within a Docker container. --- pic-sure-auth-services/Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 10d45399d..8c00aca58 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -3,4 +3,10 @@ FROM amazoncorretto:21.0.1-alpine3.18 # Copy jar and access token from maven build COPY target/pic-sure-auth-services-*.jar /pic-sure-auth-service.jar +# Copy the AWS certificate +COPY aws_certs/certificate.der /certificate.der + +# Import the certificate into the Java trust store +RUN keytool -noprompt -import -alias aws_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file /certificate.der + ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /pic-sure-auth-service.jar"] \ No newline at end of file From ab9f080be85d177de359c24f0f18d1c2ebed89ee Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 16:56:26 -0400 Subject: [PATCH 032/222] Update properties in application.properties file Several properties in the application.properties file have been updated. The spring.datasource.url has been adjusted to improve URL encoding. Besides, an unused JPA configuration property has been commented out. Lastly, default values for certain application related properties have been provided to enhance flexibility during deployment. --- .../src/main/resources/application.properties | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index a26a60956..0347c3fc5 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -2,7 +2,7 @@ server.port=${SERVER_PORT:8090} server.servlet.context-path=/auth -spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://picsure-db:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&serverTimezone=UTC} +spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://picsure-db:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&serverTimezone=UTC} spring.datasource.username=${DATASOURCE_USERNAME:root} spring.datasource.password=${DATASOURCE_PASSWORD:password} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver @@ -10,6 +10,7 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +#spring.jpa.hibernate.ddl-auto=create # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com @@ -26,11 +27,11 @@ application.client.secret.base64=${CLIENT_SECRET_IS_BASE_64:false} application.user.id.claim=${USER_ID_CLAIM:sub} application.auth0.host=${AUTH0_HOST:https://__AUTH0_DOMAIN__.auth0.com/} application.tos.enabled=${TOS_ENABLED:true} -application.default.uuid=${__STACK_SPECIFIC_APPLICATION_ID__} +application.default.uuid=${__STACK_SPECIFIC_APPLICATION_ID__:default_uuid} application.system.name=${SYSTEM_NAME:PIC-SURE All-in-one} application.template.path=${TEMPLATE_PATH:/opt/jboss/wildfly/standalone/configuration/emailTemplates/} application.access.grant.email.subject=${GRANT_EMAIL_SUBJECT:email_subject} -application.user.activation.reply.to=${USER_ACTIVATION_REPLY_TO:__USER_ACTIVATION_REPLY_TO__} +application.user.activation.reply.to=${USER_ACTIVATION_REPLY_TO:reply_to_email} application.admin.users=${ADMIN_USERS:__ADMIN_USERS__} application.denied.email.enabled=${DENIED_EMAIL_ENABLED:false} From 6aba8dc7c00ce245e0fa954f403e6e190cbdfc12 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 18:17:51 -0400 Subject: [PATCH 033/222] Update Dockerfiles and dependencies in pom.xml The Dockerfiles for both production and development have been updated to include a new multi-stage build process. This process involves compiling the application within a Docker container, which provides many benefits, including isolation from the host environment. Additionally, spring-boot-starter-validation has been updated to version 3.2.4 in the pom.xml file. --- docker-compose.dev.yml | 6 +++--- pic-sure-auth-services/Dockerfile | 13 ++++++++++++- pic-sure-auth-services/dev.Dockerfile | 16 +++++++++++++++- pic-sure-auth-services/pom.xml | 4 +++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ffdcd40c6..0a6fdb40a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,8 +2,8 @@ version: '3.8' services: pic-sure-auth-service: build: - context: ./pic-sure-auth-services/ - dockerfile: dev.Dockerfile + context: . + dockerfile: ./pic-sure-auth-services/dev.Dockerfile environment: JAVA_OPTS: "-Xmx1024m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" ports: @@ -13,7 +13,7 @@ services: pic-sure-auth-db: condition: service_healthy - pic-sure-auth-db: + picsure-db: build: context: ./pic-sure-auth-db/ dockerfile: Dockerfile diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 8c00aca58..4e3f31fa7 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -1,7 +1,18 @@ +FROM maven:3.9.6-amazoncorretto-21 as build + +# Copy the source code into the container +COPY ../ /app + +# Change the working directory +WORKDIR /app + +# Build the jar +RUN mvn clean install -DskipTests + FROM amazoncorretto:21.0.1-alpine3.18 # Copy jar and access token from maven build -COPY target/pic-sure-auth-services-*.jar /pic-sure-auth-service.jar +COPY --from=build /app/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar # Copy the AWS certificate COPY aws_certs/certificate.der /certificate.der diff --git a/pic-sure-auth-services/dev.Dockerfile b/pic-sure-auth-services/dev.Dockerfile index d4e36facd..fe2283b90 100644 --- a/pic-sure-auth-services/dev.Dockerfile +++ b/pic-sure-auth-services/dev.Dockerfile @@ -1,7 +1,21 @@ +FROM maven:3.9.6-amazoncorretto-21 as build + +# Copy the source code into the container +COPY ../ /app + +# Change the working directory +WORKDIR /app + +RUN ls -la + +# Build the jar +RUN mvn clean install -DskipTests + FROM amazoncorretto:21.0.1-alpine3.18 # Copy jar and access token from maven build -COPY target/pic-sure-auth-services.jar /pic-sure-auth-service.jar +#COPY target/pic-sure-auth-services.jar /pic-sure-auth-service.jar +COPY --from=build /app/pic-sure-auth-services/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar EXPOSE 8090 diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index ea662223c..c2fb1c4ae 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -20,7 +20,7 @@ org.springframework.boot spring-boot-starter-validation - RELEASE + 3.2.4 compile @@ -30,12 +30,14 @@ spring-web 6.1.5 + org.springframework.boot spring-boot-starter-web 3.2.3 + org.springframework.boot From d87d4a6acd146cd853818fc37181a52cd06a7501 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 11 Apr 2024 18:18:57 -0400 Subject: [PATCH 034/222] Remove commented out MySQL command --- docker-compose.dev.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 0a6fdb40a..000185cf3 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -20,7 +20,6 @@ services: environment: MYSQL_DATABASE: auth MYSQL_ROOT_PASSWORD: password -# command: --default-authentication-plugin=mysql_native_password ports: - "3306:3306" # MySQL port healthcheck: From 2214d287152f240151de0a85efa98d19a10565b1 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 16 Apr 2024 08:54:21 -0400 Subject: [PATCH 035/222] Update Dockerfiles for AWS certificate and clean-up Updated the path for AWS certificate in the pic-sure-auth-services Dockerfile to ensure a correct reference. Additionally, removed a redundant list command from dev.Dockerfile to maintain a clean and efficient script. --- pic-sure-auth-services/Dockerfile | 2 +- pic-sure-auth-services/dev.Dockerfile | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 4e3f31fa7..5bac1dd45 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -15,7 +15,7 @@ FROM amazoncorretto:21.0.1-alpine3.18 COPY --from=build /app/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar # Copy the AWS certificate -COPY aws_certs/certificate.der /certificate.der +COPY ../aws_certs/certificate.der /certificate.der # Import the certificate into the Java trust store RUN keytool -noprompt -import -alias aws_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file /certificate.der diff --git a/pic-sure-auth-services/dev.Dockerfile b/pic-sure-auth-services/dev.Dockerfile index fe2283b90..7eaf4df6d 100644 --- a/pic-sure-auth-services/dev.Dockerfile +++ b/pic-sure-auth-services/dev.Dockerfile @@ -6,8 +6,6 @@ COPY ../ /app # Change the working directory WORKDIR /app -RUN ls -la - # Build the jar RUN mvn clean install -DskipTests From bdaeef99cf95c6c3eb04468f6bc401b030c3a681 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 17 Apr 2024 13:01:55 -0400 Subject: [PATCH 036/222] Update source code path in Dockerfile --- pic-sure-auth-services/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 5bac1dd45..4221b95bb 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -1,7 +1,7 @@ FROM maven:3.9.6-amazoncorretto-21 as build # Copy the source code into the container -COPY ../ /app +COPY ./ /app # Change the working directory WORKDIR /app From 4ef49ce45a1df57551b6843c521e0784b6c060c3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 17 Apr 2024 13:05:30 -0400 Subject: [PATCH 037/222] Update AWS certificate path in Dockerfile --- pic-sure-auth-services/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 4221b95bb..f9956a9a4 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -15,7 +15,7 @@ FROM amazoncorretto:21.0.1-alpine3.18 COPY --from=build /app/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar # Copy the AWS certificate -COPY ../aws_certs/certificate.der /certificate.der +COPY aws_certs/certificate.der /certificate.der # Import the certificate into the Java trust store RUN keytool -noprompt -import -alias aws_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file /certificate.der From 7046b0caa2d9a1b8bb09b80b4fc93a45bfaa2d7d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 17 Apr 2024 13:06:14 -0400 Subject: [PATCH 038/222] Update AWS certificate path in Dockerfile --- pic-sure-auth-services/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index f9956a9a4..76df82bde 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -15,7 +15,7 @@ FROM amazoncorretto:21.0.1-alpine3.18 COPY --from=build /app/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar # Copy the AWS certificate -COPY aws_certs/certificate.der /certificate.der +COPY pic-sure-auth-services/aws_certs/certificate.der /certificate.der # Import the certificate into the Java trust store RUN keytool -noprompt -import -alias aws_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file /certificate.der From 49b45daeaca43641384849dfa653484e18f513a6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 17 Apr 2024 13:07:39 -0400 Subject: [PATCH 039/222] Update Dockerfile to correct jar file path --- pic-sure-auth-services/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index 76df82bde..ed2598cc7 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -12,7 +12,7 @@ RUN mvn clean install -DskipTests FROM amazoncorretto:21.0.1-alpine3.18 # Copy jar and access token from maven build -COPY --from=build /app/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar +COPY --from=build /app/pic-sure-auth-services/target/pic-sure-auth-services.jar /pic-sure-auth-service.jar # Copy the AWS certificate COPY pic-sure-auth-services/aws_certs/certificate.der /certificate.der From 45d2df0ebbaef417a59a18c3129257ada5b2d33d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 17 Apr 2024 13:43:33 -0400 Subject: [PATCH 040/222] Update MySQL connector dependency in pom.xml --- pic-sure-auth-services/pom.xml | 14 ++++++++++---- .../src/main/resources/application.properties | 11 +++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index c2fb1c4ae..2a8df5ae5 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -76,10 +76,16 @@ 1.14.13 - - com.mysql - mysql-connector-j - 8.2.0 + + + + + + + + mysql + mysql-connector-java + 5.1.38 io.jsonwebtoken diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 0347c3fc5..ce4598705 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -5,13 +5,20 @@ server.servlet.context-path=/auth spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://picsure-db:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true&serverTimezone=UTC} spring.datasource.username=${DATASOURCE_USERNAME:root} spring.datasource.password=${DATASOURCE_PASSWORD:password} -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +# MySQL 8 driver +#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +# MySQL 5 driver +spring.datasource.driver-class-name=com.mysql.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +# MySQL 8 dialect +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +# MySQL 5 dialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #spring.jpa.hibernate.ddl-auto=create + # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com spring.mail.port=587 From 6c86cac3650ef9e4a89bfe7024859e3e9b027ab6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 18 Apr 2024 08:47:16 -0400 Subject: [PATCH 041/222] Update Hibernate dialect to MySQL5InnoDB --- .../src/main/resources/application.properties | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index ce4598705..a6df31aad 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -14,11 +14,10 @@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.jpa.show-sql=true # MySQL 8 dialect #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -# MySQL 5 dialect -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +# MySQL 5 dialect for innoDB +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect #spring.jpa.hibernate.ddl-auto=create - # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com spring.mail.port=587 From defb0361384bfe22b811fdd6e8383085225e8e3c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 18 Apr 2024 08:58:39 -0400 Subject: [PATCH 042/222] Revert database dialect --- .../src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index a6df31aad..8b82fa7f8 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -15,7 +15,7 @@ spring.jpa.show-sql=true # MySQL 8 dialect #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # MySQL 5 dialect for innoDB -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #spring.jpa.hibernate.ddl-auto=create # Mail session configuration (Assuming Gmail SMTP for example) From 9439cf3499f53c1fd7252b0eeb140a26e336235a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 18 Apr 2024 09:17:56 -0400 Subject: [PATCH 043/222] Update MySQL connector and dialect version The pom.xml dependency was updated from using MySQL connector 5.1.38 to 8.2.0. Along with this change, the Hibernate dialect in the application properties was switched from MySQL5Dialect to MySQL8Dialect to accommodate the updated connector. --- pic-sure-auth-services/pom.xml | 18 +++++++++--------- .../src/main/resources/application.properties | 6 ++---- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 2a8df5ae5..875eb5b63 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -76,17 +76,17 @@ 1.14.13 - - - - - - - mysql - mysql-connector-java - 5.1.38 + com.mysql + mysql-connector-j + 8.2.0 + + + + + + io.jsonwebtoken jjwt diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 8b82fa7f8..dff1e833e 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -12,10 +12,8 @@ spring.datasource.driver-class-name=com.mysql.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true -# MySQL 8 dialect -#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -# MySQL 5 dialect for innoDB -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #spring.jpa.hibernate.ddl-auto=create # Mail session configuration (Assuming Gmail SMTP for example) From b1f65e49d4559439d86fdc48d606c99b3663af09 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 18 Apr 2024 09:24:11 -0400 Subject: [PATCH 044/222] Update MySQL connector and dialect version --- .../src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index dff1e833e..2a9cd9b02 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -6,9 +6,9 @@ spring.datasource.url=${DATASOURCE_URL:jdbc:mysql://picsure-db:3306/auth?useUnic spring.datasource.username=${DATASOURCE_USERNAME:root} spring.datasource.password=${DATASOURCE_PASSWORD:password} # MySQL 8 driver -#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MySQL 5 driver -spring.datasource.driver-class-name=com.mysql.jdbc.Driver +#spring.datasource.driver-class-name=com.mysql.jdbc.Driver # JPA/Hibernate properties spring.jpa.show-sql=true From 3f2923a0072389e28fddf3cb73099f392042a536 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 18 Apr 2024 10:46:12 -0400 Subject: [PATCH 045/222] Disable hibernate's default camelCase to snake_case conversion The updated application.properties file disables the default hibernate behavior to convert camelCase to snake_case for table and column names. Now, naming will follow standard physical strategy policies as specified by Hibernate. --- .../src/main/resources/application.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 2a9cd9b02..0b60f6112 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -16,6 +16,9 @@ spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #spring.jpa.hibernate.ddl-auto=create +# By default hibernate will convert camelCase to snake_case for table and column names. This property disables that behavior. +spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + # Mail session configuration (Assuming Gmail SMTP for example) spring.mail.host=smtp.gmail.com spring.mail.port=587 From 2fd473eec570c9e113c3a38811914f7f677a9c2e Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 19 Apr 2024 08:50:14 -0400 Subject: [PATCH 046/222] Add logging to getResponse for debugging --- .../avillach/auth/service/impl/AuthenticationService.java | 4 ++-- .../harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 48d729b1c..3aabe0475 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -75,7 +75,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce JsonNode userInfo = retrieveUserInfo(accessToken); JsonNode userIdNode = userInfo.get("user_id"); if (userIdNode == null) { - logger.error("getToken() cannot find user_id by retrieveUserInfo(), return json response: " + userInfo.toString()); + logger.error("getToken() cannot find user_id by retrieveUserInfo(), return json response: {}", userInfo.toString()); throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); } String userId = userIdNode.asText(); @@ -86,7 +86,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce try { connectionId = userInfo.get("identities").get(0).get("connection").asText(); } catch (Exception e) { - logger.error("getToken() cannot find connection_id by retrieveUserInfo(), return json response: " + userInfo.toString()); + logger.error("getToken() cannot find connection_id by retrieveUserInfo(), return json response: {}", userInfo.toString()); throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index a517af1e0..b067a56f9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -28,6 +28,8 @@ public static boolean is2xx(ResponseEntity response) { } public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders headers) { + logger.info("HttpClientUtilSpring retrieveGetResponse()"); + logger.info("uri: {}", uri); try { logger.debug("HttpClientUtilSpring retrieveGetResponse()"); HttpEntity entity = new HttpEntity<>("parameters", headers); @@ -62,7 +64,7 @@ public static ResponseEntity retrievePostResponse(String uri, HttpHeader HttpEntity entity = new HttpEntity<>(body, headers); return restTemplate.postForEntity(uri, entity, String.class); } catch (HttpClientErrorException ex) { - logger.error("HttpClientErrorException: " + ex.getMessage()); + logger.error("HttpClientErrorException: {}", ex.getMessage()); throw ex; } } From 75ad2d810c4f0e8ff22b46357c235ba78878bfcc Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 19 Apr 2024 09:29:07 -0400 Subject: [PATCH 047/222] Make AUTH0_HOST have no default --- .../dbmi/avillach/auth/service/impl/AuthenticationService.java | 3 +++ .../src/main/resources/application.properties | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 3aabe0475..24d02e1cd 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -120,6 +120,9 @@ public ResponseEntity getToken(Map authRequest) throws IOExce } private JsonNode retrieveUserInfo(String accessToken) throws IOException { + // TODO: Remove this after debugging + logger.info("accessToken: {}", accessToken); + logger.info("auth0host: {}", this.auth0host); String auth0UserInfoURI = this.auth0host + "/userinfo"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 0b60f6112..0f1e64730 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -32,7 +32,7 @@ application.client.id=${CLIENT_ID:client_id} application.client.secret=${CLIENT_SECRET:client_secret} application.client.secret.base64=${CLIENT_SECRET_IS_BASE_64:false} application.user.id.claim=${USER_ID_CLAIM:sub} -application.auth0.host=${AUTH0_HOST:https://__AUTH0_DOMAIN__.auth0.com/} +application.auth0.host=${AUTH0_HOST} application.tos.enabled=${TOS_ENABLED:true} application.default.uuid=${__STACK_SPECIFIC_APPLICATION_ID__:default_uuid} application.system.name=${SYSTEM_NAME:PIC-SURE All-in-one} From 21e4507dba7c2bc1c24eebd1b0c2948feef6755f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 19 Apr 2024 11:03:29 -0400 Subject: [PATCH 048/222] Update methods to fetch connection by id Previously assumed the id was the UUID and they are distinct properties. --- .../dbmi/avillach/auth/repository/ConnectionRepository.java | 3 +++ .../dbmi/avillach/auth/service/impl/AuthenticationService.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java index ccd3ab5ef..95946111a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; /** @@ -16,4 +17,6 @@ public interface ConnectionRepository extends JpaRepository { Connection findByLabel(String label); + Optional findById(String id); + } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 24d02e1cd..3c56df6d6 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -90,7 +90,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce throw new NotAuthorizedException("cannot get sufficient user information. Please contact admin."); } - Connection connection = connectionRepository.findById(UUID.fromString(connectionId)).orElseThrow(() -> new NotAuthorizedException("No connection found for connection_id " + connectionId)); + Connection connection = connectionRepository.findById(connectionId).orElseThrow(() -> new NotAuthorizedException("No connection found for connection_id " + connectionId)); //Do we have this user already? User user = userRepository.findBySubjectAndConnection(userId, connection); if (user == null) { From 15ea78d0c692e56fe1ed6ef0566c9c113b89ee58 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 19 Apr 2024 11:26:01 -0400 Subject: [PATCH 049/222] Change connection ID type in auth services Previously assumed that the findById was using the UUID, but it is actually a different field. It is a string like 'oauth'. --- .../auth/repository/ConnectionRepository.java | 2 ++ .../auth/service/impl/ConnectionWebService.java | 6 +++--- .../service/impl/OauthUserMatchingService.java | 17 ++++++++--------- .../impl/UserMetadataMappingService.java | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java index 95946111a..93f6d0813 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ConnectionRepository.java @@ -19,4 +19,6 @@ public interface ConnectionRepository extends JpaRepository { Optional findById(String id); + void deleteById(String id); + } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java index c4765f2ee..22b217f46 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebService.java @@ -26,7 +26,7 @@ public List addConnection(List connections) throws Illeg if (c.getSubPrefix() == null || c.getRequiredFields() == null || c.getLabel() == null || c.getId() == null){ throw new IllegalArgumentException("Id, Label, Subprefix, and RequiredFields cannot be null"); } - Optional conn = connectionRepo.findById(UUID.fromString(c.getId())); + Optional conn = connectionRepo.findById(c.getId()); if (conn.isPresent()){ throw new IllegalArgumentException("Id must be unique, a connection with id " + c.getId() + " already exists in the database"); } @@ -36,7 +36,7 @@ public List addConnection(List connections) throws Illeg } public Connection getConnectionById(String connectionId) { - return this.connectionRepo.findById(UUID.fromString(connectionId)) + return this.connectionRepo.findById(connectionId) .orElseThrow(() -> new IllegalArgumentException("Connection with id " + connectionId + " not found")); } @@ -50,7 +50,7 @@ public List updateConnections(List connections) { @Transactional public List removeConnectionById(String connectionId) { - this.connectionRepo.deleteById(UUID.fromString(connectionId)); + this.connectionRepo.deleteById(connectionId); return this.getAllConnections(); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java index 7b1ca7f6c..581465f05 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -58,7 +58,7 @@ public User matchTokenToUser(JsonNode userInfo) { // This retrieves a map of UserInfo as JSON. try { String userInfoString = mapper.writeValueAsString(userInfo); - logger.info("Attempting to find match for user with info: " + userInfo); + logger.info("Attempting to find match for user with info: {}", userInfo); //Parse this once so it doesn't get re-parsed every time we read from it Configuration conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.ALWAYS_RETURN_LIST); @@ -66,28 +66,27 @@ public User matchTokenToUser(JsonNode userInfo) { //Return lists or null so that we don't have to worry about whether it's a single object or an array, or catch errors List connections = JsonPath.using(conf).parse(parsedInfo).read("$.identities[0].connection"); String connectionId = connections.get(0); - - Optional connection = connectionRepo.findById(UUID.fromString(connectionId)); + Optional connection = connectionRepo.findById(connectionId); List mappings = mappingService.getAllMappingsForConnection(connection.orElse(null)); if (mappings == null || mappings.isEmpty()) { //We don't have any mappings for this connection yet - logger.warn("Unable to find user metadata mappings for connectionId " + connection); + logger.warn("Unable to find user metadata mappings for connectionId {}", connection); return null; } //We only care about unmatched users List users = userRepo.findByConnectionAndMatched(connection.orElse(null), false); if (users == null || users.isEmpty()) { - logger.info("No unmatched users exist with connectionId " + connection); + logger.info("No unmatched users exist with connectionId {}", connection); return null; } for (UserMetadataMapping umm : mappings) { List auth0values = JsonPath.using(conf).parse(parsedInfo).read(umm.getAuth0MetadataJsonPath()); if (auth0values == null || auth0values.isEmpty()) { //Well, nothing found, let's move on. - logger.info("Fetched data has no value at " + umm.getAuth0MetadataJsonPath()); + logger.info("Fetched data has no value at {}", umm.getAuth0MetadataJsonPath()); break; } String auth0value = auth0values.get(0); @@ -96,18 +95,18 @@ public User matchTokenToUser(JsonNode userInfo) { try{ values = JsonPath.using(conf).parse(u.getGeneralMetadata()).read(umm.getGeneralMetadataJsonPath()); } catch (JsonPathException e) { - logger.warn("User " + u.getUuid() + " has invalid general metadata: " + u.getGeneralMetadata()); + logger.warn("User {} has invalid general metadata: {}", u.getUuid(), u.getGeneralMetadata()); continue; } if (values == null || values.isEmpty()) { - logger.warn("User " + u.getUuid() + " has no value at " + umm.getGeneralMetadataJsonPath()); + logger.warn("User {} has no value at {}", u.getUuid(), umm.getGeneralMetadataJsonPath()); continue; } String generalValue = values.get(0); if (auth0value.equalsIgnoreCase(generalValue)) { //Match found!! String userId = JsonPath.read(parsedInfo, "$.user_id"); - logger.info("Matching user with user_id " + userId); + logger.info("Matching user with user_id {}", userId); u.setAuth0metadata(userInfoString); u.setMatched(true); u.setSubject(userId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index a14e9c19c..c5b9ebdae 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -39,7 +39,7 @@ public List addMappings(List mappings) StringBuilder errorMessage = new StringBuilder("The following connectionIds do not exist:\n"); boolean error = false; for (UserMetadataMapping umm : mappings) { - Optional c = connectionRepo.findById(UUID.fromString(umm.getConnection().getId())); + Optional c = connectionRepo.findById(umm.getConnection().getId()); if (c.isEmpty()) { error = true; errorMessage.append(umm.getConnection().getId()).append("\n"); @@ -60,7 +60,7 @@ public List getAllMappings() { } public ResponseEntity getAllMappingsForConnection(String connectionId) { - Connection connection = this.connectionRepo.findById(UUID.fromString(connectionId)).orElseThrow(() -> new IllegalArgumentException("Connection not found")); + Connection connection = this.connectionRepo.findById(connectionId).orElseThrow(() -> new IllegalArgumentException("Connection not found")); return PICSUREResponse.success(getAllMappingsForConnection(connection)); } From dfdaa450c708b57cb90c2988d8ed4a7582908061 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 10:10:01 -0400 Subject: [PATCH 050/222] Update JWT implementation and remove unused clientSecret parameter The changes include a major update in JWT token handling using the latest version of jjwt libraries. Features include encryption and decryption of tokens, and claims handling has been improved for better security. The clientSecret parameter, which was previously used inconsistently or not at all, was removed in favour of a new approach using the HMAC SHA-256 signing key. --- pic-sure-auth-services/pom.xml | 22 +++++- .../auth/service/impl/ApplicationService.java | 2 +- .../auth/service/impl/TokenService.java | 2 +- .../auth/service/impl/UserService.java | 3 +- .../hms/dbmi/avillach/auth/utils/JWTUtil.java | 68 +++++++++---------- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 875eb5b63..cafca3824 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -87,10 +87,28 @@ + + + + + + + + io.jsonwebtoken + jjwt-api + 0.12.5 + + + io.jsonwebtoken + jjwt-impl + 0.12.5 + runtime + io.jsonwebtoken - jjwt - 0.9.0 + jjwt-jackson + 0.12.5 + runtime com.auth0 diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 5f680df08..5a3b9924c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -129,7 +129,7 @@ public String generateApplicationToken(Application application) { } return JWTUtil.createJwtToken( - this.CLIENT_SECRET, null, null, + null, null, new HashMap<>( Map.of( "user_id", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getName() diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 1a4195c2e..146a6a676 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -271,7 +271,7 @@ public ResponseEntity refreshToken(String authorizationHeader) { } Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + this.tokenExpirationTime); - String refreshedToken = JWTUtil.createJwtToken(this.clientSecret, + String refreshedToken = JWTUtil.createJwtToken( claims.getId(), claims.getIssuer(), claims, diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 8f53907c5..a53cd06a9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -84,7 +84,6 @@ public HashMap getUserProfileResponse(Map claims logger.info("getUserProfileResponse() using claims:" + claims.toString()); String token = JWTUtil.createJwtToken( - this.clientSecret, "whatever", "edu.harvard.hms.dbmi.psama", claims, @@ -493,7 +492,7 @@ private String generateUserLongTermToken(String authorizationHeader) { tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); } - return JWTUtil.createJwtToken(clientSecret, + return JWTUtil.createJwtToken( claims.getId(), claims.getIssuer(), claims, diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 28e67bb97..d183a727c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -1,16 +1,22 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; +import com.auth0.json.mgmt.client.SigningKey; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; -import io.jsonwebtoken.*; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureException; +import io.jsonwebtoken.security.Keys; import org.apache.tomcat.util.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; -import java.security.Key; import java.util.Date; import java.util.Map; import java.util.Optional; @@ -31,16 +37,22 @@ public class JWTUtil { @Value("${application.client.secret.base64}") private static boolean clientSecretIsBase64; + private static String getDecodedClientSecret() { + if (clientSecretIsBase64) { + return new String(Base64.decodeBase64(clientSecret)); + } + + return clientSecret; + } + /** - * @param clientSecret - client secret * @param id - id * @param issuer - issuer * @param claims - claims * @param subject - subject - * @param ttlMillis - time to live in milliseconds * @return JWT token */ - public static String createJwtToken(String clientSecret, String id, String issuer, Map claims, String subject, long ttlMillis) { + public static String createJwtToken(String id, String issuer, Map claims, String subject, long ttlMillis) { logger.debug("createJwtToken() starting..."); String jwt_token = null; @@ -52,58 +64,44 @@ public static String createJwtToken(String clientSecret, String id, String issue ttlMillis = 999L * 1000 * 60 * 60 * 24; } - SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); - //We will sign our JWT with our ApiKey secret - byte[] apiKeySecretBytes = clientSecret.getBytes(); - - Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); + String clientSecret = getDecodedClientSecret(); + SecretKey signingKey = Keys.hmacShaKeyFor(clientSecret.getBytes(StandardCharsets.UTF_8)); //Builds the JWT and serializes it to a compact, URL-safe string JwtBuilder builder = Jwts.builder() - .setClaims(claims) - .setId(id) - .setIssuedAt(now) - .setSubject(subject) - .setIssuer(issuer) - .signWith(signatureAlgorithm, signingKey); + .claims(claims) + .id(id) + .issuedAt(now) + .subject(subject) + .issuer(issuer) + .encryptWith(signingKey, Jwts.ENC.A128CBC_HS256); //if it has been specified, let's add the expiration long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); - builder.setExpiration(exp); + builder.expiration(exp); jwt_token = builder.compact(); return jwt_token; } public static Jws parseToken(String token) { - Jws jws = null; + String clientSecret = getDecodedClientSecret(); + SecretKey signingKey = Keys.hmacShaKeyFor(clientSecret.getBytes(StandardCharsets.UTF_8)); + Jws jws; try { - jws = Jwts.parser().setSigningKey(clientSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); - } catch (SignatureException e) { - try { - if (clientSecretIsBase64) { - // handle if client secret is base64 encoded - jws = Jwts.parser().setSigningKey(Base64.decodeBase64(clientSecret)).parseClaimsJws(token); - } else { - // handle if client secret is not base64 encoded - jws = Jwts.parser().setSigningKey(clientSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token); - } - } catch (JwtException | IllegalArgumentException ex) { - logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); - throw new NotAuthorizedException(ex.getClass().getSimpleName() + ": " + ex.getMessage()); - } + jws = Jwts.parser().decryptWith(signingKey).build().parseSignedClaims(token); } catch (JwtException | IllegalArgumentException e) { - logger.error("parseToken() throws: " + e.getClass().getSimpleName() + ", " + e.getMessage()); + logger.error("parseToken() throws: {}, {}", e.getClass().getSimpleName(), e.getMessage()); throw new NotAuthorizedException(e.getClass().getSimpleName() + ": " + e.getMessage()); } if (jws == null) { - logger.error("parseToken() get null for jws body by parsing Token - " + token); + logger.error("parseToken() get null for jws body by parsing Token - {}", token); throw new NotAuthorizedException("Please contact admin to see the log"); } From c3d71120b8335e53d875a01504875d88f51bdae5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 10:32:17 -0400 Subject: [PATCH 051/222] Add logging of client secret in JWTUtil A logger statement has been inserted in the `getDecodedClientSecret` method of the JWTUtil class. This will log the client secret, which can be helpful for debugging and tracking purposes. --- .../java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index d183a727c..fa2ce9066 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -38,6 +38,7 @@ public class JWTUtil { private static boolean clientSecretIsBase64; private static String getDecodedClientSecret() { + logger.info("Client secret {}", clientSecret); if (clientSecretIsBase64) { return new String(Base64.decodeBase64(clientSecret)); } From 3f955d6bddd56889e927f8b8136126f4ce2d92e6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 10:54:22 -0400 Subject: [PATCH 052/222] Refactor JWT operations utilizing JWTUtil dependency injection The commit refactors several classes for JWT operations to use dependency injection for the JWTUtil. The JWTUtil instance is now passed into the respective services at construction, replacing usages of static JWTUtil methods. Improvements also include changing the use of "getBody()" to "getPayload()" to obtain Claims from Jws tokens. --- .../dbmi/avillach/auth/filter/JWTFilter.java | 14 ++++++++------ .../auth/service/impl/ApplicationService.java | 9 ++++----- .../auth/service/impl/TokenService.java | 16 +++++++--------- .../auth/service/impl/UserService.java | 18 +++++++++--------- .../hms/dbmi/avillach/auth/utils/JWTUtil.java | 19 ++++++++++++------- 5 files changed, 40 insertions(+), 36 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 23885fe9a..26edf1b55 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -8,6 +8,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import jakarta.servlet.FilterChain; @@ -29,7 +30,6 @@ import java.util.Set; import java.util.UUID; -import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; /** * The main gate for PSAMA that filters all incoming requests against PSAMA. @@ -58,15 +58,17 @@ public class JWTFilter extends OncePerRequestFilter { private final String userClaimId; private final boolean tosEnabled; + private final JWTUtil jwtUtil; @Autowired public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService, - @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled) { + @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled, JWTUtil jwtUtil) { this.userRepo = userRepo; this.applicationRepo = applicationRepo; this.tosService = tosService; this.userClaimId = userClaimId; this.tosEnabled = tosEnabled; + this.jwtUtil = jwtUtil; } /** @@ -96,8 +98,8 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet logger.debug(" token: {}", token); // Parse the token - Jws jws = parseToken(token); - String userId = jws.getBody().get(this.userClaimId, String.class); + Jws jws = this.jwtUtil.parseToken(token); + String userId = jws.getPayload().get(this.userClaimId, String.class); if (userId.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX)) { // For profile information, we do indeed allow long term token @@ -105,7 +107,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet if (request.getRequestURI().startsWith("/user/me")) { // Get the subject claim, remove the LONG_TERM_TOKEN_PREFIX, and use that String value to // look up the existing user. - String realClaimsSubject = jws.getBody().getSubject().substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); + String realClaimsSubject = jws.getPayload().getSubject().substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); setSecurityContextForUser(request, response, realClaimsSubject); } else { @@ -144,7 +146,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet } else { logger.debug("UserID: {} is not a long term token and not a PSAMA application token.", userId); // Authenticate as User - setSecurityContextForUser(request, response, jws.getBody().getSubject()); + setSecurityContextForUser(request, response, jws.getPayload().getSubject()); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 5a3b9924c..b7afc394c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -21,14 +21,13 @@ public class ApplicationService { private final static Logger logger = LoggerFactory.getLogger(ApplicationService.class); private final ApplicationRepository applicationRepo; private final PrivilegeRepository privilegeRepo; - - @Value("${application.client.secret}") - private String CLIENT_SECRET; + private final JWTUtil jwtUtil; @Autowired - ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo) { + ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, JWTUtil jwtUtil) { this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; + this.jwtUtil = jwtUtil; } /** @@ -128,7 +127,7 @@ public String generateApplicationToken(Application application) { throw new NullPointerException("Cannot generate application token, please contact admin"); } - return JWTUtil.createJwtToken( + return this.jwtUtil.createJwtToken( null, null, new HashMap<>( Map.of( diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 146a6a676..b74f4601b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -27,7 +27,6 @@ import java.util.*; import java.util.stream.Collectors; -import static edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil.parseToken; @Service public class TokenService { @@ -39,18 +38,17 @@ public class TokenService { private final UserRepository userRepository; private final long tokenExpirationTime; - private final String clientSecret; private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour TODO: Move to a global configuration or enum? + private final JWTUtil jwtUtil; @Autowired public TokenService(AuthorizationService authorizationService, UserRepository userRepository, - @Value("${application.client.secret}") String clientSecret, - @Value("${application.token.expiration.time}") long tokenExpirationTime) { + @Value("${application.token.expiration.time}") long tokenExpirationTime, JWTUtil jwtUtil) { this.authorizationService = authorizationService; this.userRepository = userRepository; - this.clientSecret = clientSecret; this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; + this.jwtUtil = jwtUtil; } public ResponseEntity inspectToken(Map inputMap) { @@ -87,7 +85,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg // don't need to check if jws is null or not, since parse function has already checked Jws jws; try { - jws = JWTUtil.parseToken(token); + jws = this.jwtUtil.parseToken(token); /* * token has been verified, now we remove it from inputMap, so further logs will not be able to log @@ -255,13 +253,13 @@ public ResponseEntity refreshToken(String authorizationHeader) { Jws jws; try { String token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader).orElseThrow(() -> new NotAuthorizedException("Token not found")); - jws = parseToken(token); + jws = this.jwtUtil.parseToken(token); } catch (NotAuthorizedException ex) { return PICSUREResponse.protocolError("Cannot parse original token"); } - Claims claims = jws.getBody(); + Claims claims = jws.getPayload(); // just check if the subject is along with the database record, // just in case something has changed in middle @@ -271,7 +269,7 @@ public ResponseEntity refreshToken(String authorizationHeader) { } Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + this.tokenExpirationTime); - String refreshedToken = JWTUtil.createJwtToken( + String refreshedToken = this.jwtUtil.createJwtToken( claims.getId(), claims.getIssuer(), claims, diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index a53cd06a9..a6c39b758 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -47,7 +47,6 @@ public class UserService { private final ConnectionRepository connectionRepository; private final ApplicationRepository applicationRepository; private final RoleService roleService; - private final String clientSecret; private final long tokenExpirationTime; private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour @@ -55,20 +54,21 @@ public class UserService { private final String applicationUUID; private final ObjectMapper objectMapper = new ObjectMapper(); + private final JWTUtil jwtUtil; @Autowired public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, ApplicationRepository applicationRepository, RoleService roleService, - @Value("${application.client.secret}") String clientSecret, @Value("${application.token.expiration.time}") long tokenExpirationTime, - @Value("${application.default.uuid}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime) { + @Value("${application.token.expiration.time}") long tokenExpirationTime, + @Value("${application.default.uuid}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime, JWTUtil jwtUtil) { this.basicMailService = basicMailService; this.tosService = tosService; this.userRepository = userRepository; this.connectionRepository = connectionRepository; this.roleService = roleService; - this.clientSecret = clientSecret; this.tokenExpirationTime = tokenExpirationTime > 0 ? tokenExpirationTime : defaultTokenExpirationTime; this.applicationRepository = applicationRepository; this.applicationUUID = applicationUUID; + this.jwtUtil = jwtUtil; long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30; // this.longTermTokenExpirationTime = longTermTokenExpirationTime > 0 ? longTermTokenExpirationTime : defaultLongTermTokenExpirationTime; @@ -81,9 +81,9 @@ public HashMap getUserProfileResponse(Map claims HashMap responseMap = new HashMap(); logger.info("getUserProfileResponse() initialized map"); - logger.info("getUserProfileResponse() using claims:" + claims.toString()); + logger.info("getUserProfileResponse() using claims:{}", claims.toString()); - String token = JWTUtil.createJwtToken( + String token = this.jwtUtil.createJwtToken( "whatever", "edu.harvard.hms.dbmi.psama", claims, @@ -480,9 +480,9 @@ private String generateUserLongTermToken(String authorizationHeader) { throw new IllegalArgumentException("Token is not presented in the authorization header."); } - Jws jws = JWTUtil.parseToken(token.get()); + Jws jws = this.jwtUtil.parseToken(token.get()); - Claims claims = jws.getBody(); + Claims claims = jws.getPayload(); String tokenSubject = claims.getSubject(); if (tokenSubject.startsWith(AuthNaming.LONG_TERM_TOKEN_PREFIX + "|")) { @@ -492,7 +492,7 @@ private String generateUserLongTermToken(String authorizationHeader) { tokenSubject = tokenSubject.substring(AuthNaming.LONG_TERM_TOKEN_PREFIX.length() + 1); } - return JWTUtil.createJwtToken( + return this.jwtUtil.createJwtToken( claims.getId(), claims.getIssuer(), claims, diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index fa2ce9066..05009ecc3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -1,13 +1,11 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import com.auth0.json.mgmt.client.SigningKey; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.security.Keys; import org.apache.tomcat.util.codec.binary.Base64; import org.slf4j.Logger; @@ -27,17 +25,24 @@ */ @Component public class JWTUtil { + private final static Logger logger = LoggerFactory.getLogger(JWTUtil.class); private static final long defaultTTLMillis = 1000L * 60 * 60 * 24 * 7; @Value("${application.client.secret}") - private static String clientSecret; + private String clientSecret; @Value("${application.client.secret.base64}") - private static boolean clientSecretIsBase64; + private boolean clientSecretIsBase64; + + public JWTUtil(@Value("${application.client.secret}") String clientSecret, + @Value("${application.client.secret.base64}") boolean clientSecretIsBase64) { + this.clientSecret = clientSecret; + this.clientSecretIsBase64 = clientSecretIsBase64; + } - private static String getDecodedClientSecret() { + private String getDecodedClientSecret() { logger.info("Client secret {}", clientSecret); if (clientSecretIsBase64) { return new String(Base64.decodeBase64(clientSecret)); @@ -53,7 +58,7 @@ private static String getDecodedClientSecret() { * @param subject - subject * @return JWT token */ - public static String createJwtToken(String id, String issuer, Map claims, String subject, long ttlMillis) { + public String createJwtToken(String id, String issuer, Map claims, String subject, long ttlMillis) { logger.debug("createJwtToken() starting..."); String jwt_token = null; @@ -89,7 +94,7 @@ public static String createJwtToken(String id, String issuer, Map parseToken(String token) { + public Jws parseToken(String token) { String clientSecret = getDecodedClientSecret(); SecretKey signingKey = Keys.hmacShaKeyFor(clientSecret.getBytes(StandardCharsets.UTF_8)); From aea07898a97938e64abdf5c0eac98c4901bfc339 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 11:34:35 -0400 Subject: [PATCH 053/222] Update client secret setting and JWT encryption method The default value for application.client.secret configuration item has been removed in application.properties to enforce the requirement of setting this value. Additionally, the JWTUtil now uses a stronger encryption method (A256CBC_HS512) for JWT generation instead of the previous one (A128CBC_HS256) to enhance the security. --- .../java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 2 +- .../src/main/resources/application.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 05009ecc3..763833370 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -83,7 +83,7 @@ public String createJwtToken(String id, String issuer, Map claim .issuedAt(now) .subject(subject) .issuer(issuer) - .encryptWith(signingKey, Jwts.ENC.A128CBC_HS256); + .encryptWith(signingKey, Jwts.ENC.A256CBC_HS512); //if it has been specified, let's add the expiration long expMillis = nowMillis + ttlMillis; diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 0f1e64730..6ad7d8f48 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -29,7 +29,7 @@ spring.mail.properties.mail.smtp.starttls.enable=true # Custom application properties application.client.id=${CLIENT_ID:client_id} -application.client.secret=${CLIENT_SECRET:client_secret} +application.client.secret=${CLIENT_SECRET} application.client.secret.base64=${CLIENT_SECRET_IS_BASE_64:false} application.user.id.claim=${USER_ID_CLAIM:sub} application.auth0.host=${AUTH0_HOST} From d986cbec9ff8c0ada123996dc34194b969c27d2f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 12:13:41 -0400 Subject: [PATCH 054/222] Update handling of Terms of Service updates and acceptance --- .../hms/dbmi/avillach/auth/entity/User.java | 1 + .../repository/TermsOfServiceRepository.java | 3 ++- .../auth/rest/TermsOfSerivceController.java | 8 ++++++- .../auth/service/impl/TOSService.java | 23 ++++++++++++++----- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 50eb5c194..03ebefbcb 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -47,6 +47,7 @@ public class User extends BaseEntity implements Serializable, Principal { private boolean matched; + @Column(nullable = true) private Date acceptedTOS; @Column(name = "auth0_metadata") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java index f18914ec5..47bc83161 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/TermsOfServiceRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; /** @@ -17,6 +18,6 @@ public interface TermsOfServiceRepository extends JpaRepositoryFind the latest TermsOfService by date updated.

* @return TermsOfService */ - TermsOfService findTopByOrderByDateUpdatedDesc(); + Optional findTopByOrderByDateUpdatedDesc(); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java index de81ce3ef..281a3a129 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java @@ -1,5 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; +import java.util.Optional; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; @@ -50,7 +52,11 @@ public ResponseEntity getLatestTermsOfService(){ @PostMapping(path = "/update", consumes = "text/html", produces = "application/json") public ResponseEntity updateTermsOfService( @Parameter(required = true, description = "A html page for updating") String html){ - return PICSUREResponse.success(tosService.updateTermsOfService(html)); + Optional termsOfService = tosService.updateTermsOfService(html); + if (termsOfService.isEmpty()){ + return PICSUREResponse.success(); + } + return PICSUREResponse.success(termsOfService.get()); } @Operation(description = "GET if current user has acceptted his TOS or not") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 5125ca92b..35afe0436 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -55,7 +55,7 @@ public boolean hasUserAcceptedLatest(String userId) { return checkAgainstTOSDate(userId); } - public TermsOfService updateTermsOfService(String html) { + public Optional updateTermsOfService(String html) { TermsOfService updatedTOS = new TermsOfService(); updatedTOS.setContent(html); termsOfServiceRepo.save(updatedTOS); @@ -64,7 +64,12 @@ public TermsOfService updateTermsOfService(String html) { public String getLatest() { try { - return termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getContent(); + Optional termsOfService = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); + if (termsOfService.isPresent()) { + return termsOfService.get().getContent(); + } + + throw new NoResultException(); } catch (NoResultException e) { logger.info("Terms Of Service disabled: No Terms of Service found in database"); return null; @@ -78,8 +83,12 @@ public User acceptTermsOfService(String userId) { throw new RuntimeException("User does not exist"); } user.setAcceptedTOS(new Date()); - Date tosDate = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getDateUpdated(); - logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(), tosDate.toString()); + Optional tosDate = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); + if (tosDate.isEmpty()) { + throw new RuntimeException("No Terms of Service found in database"); + } + + logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(), tosDate.get().getDateUpdated()); return user; } @@ -88,11 +97,13 @@ private boolean checkAgainstTOSDate(String userId) { if (optUser.isPresent()) { User user = optUser.get(); Date acceptedTOS = user.getAcceptedTOS(); + logger.info("User {} accepted TOS on {}", userId, acceptedTOS); if (acceptedTOS == null) { return false; } - Date latestTOS = this.termsOfServiceRepo.findTopByOrderByDateUpdatedDesc().getDateUpdated(); - return acceptedTOS.after(latestTOS); + + Optional latestTOS = this.termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); + return latestTOS.filter(termsOfService -> acceptedTOS.after(termsOfService.getDateUpdated())).isPresent(); } return false; From 9dc2cea0df450d97ee3a574877cc3dfa8e6141f5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 13:45:12 -0400 Subject: [PATCH 055/222] Update JWT encryption method Changed the JWT encryption method from encryptWith to signWith in the JWTUtil class. This change was made to match with the signing key, as the previous method was causing inconsistencies. --- .../java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 763833370..9202e3d2f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -83,7 +83,7 @@ public String createJwtToken(String id, String issuer, Map claim .issuedAt(now) .subject(subject) .issuer(issuer) - .encryptWith(signingKey, Jwts.ENC.A256CBC_HS512); + .signWith(signingKey); //if it has been specified, let's add the expiration long expMillis = nowMillis + ttlMillis; From 7f7f7805c2809127ff5cb7b82de498c6f49f5a52 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 14:01:26 -0400 Subject: [PATCH 056/222] Replace 'decryptWith' method with 'verifyWith' --- .../java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 9202e3d2f..79dd349d1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -100,7 +100,7 @@ public Jws parseToken(String token) { Jws jws; try { - jws = Jwts.parser().decryptWith(signingKey).build().parseSignedClaims(token); + jws = Jwts.parser().verifyWith(signingKey).build().parseSignedClaims(token); } catch (JwtException | IllegalArgumentException e) { logger.error("parseToken() throws: {}, {}", e.getClass().getSimpleName(), e.getMessage()); throw new NotAuthorizedException(e.getClass().getSimpleName() + ": " + e.getMessage()); From 61cd5368005e6087ab3503059234f1d5ffc4eb30 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 14:25:05 -0400 Subject: [PATCH 057/222] Refactor UserService and TOSService, rename TermsOfServiceController, log adjustments The code changes primarily involve refactoring of UserService and TOSService. UserService now uses Optional, simplifying null-checking on users. There's also a renaming of TermsOfSerivceController to correct the typo. Logging adjustments have been made across related files to improve data quality and tracking. --- .../dbmi/avillach/auth/filter/JWTFilter.java | 2 ++ ...ler.java => TermsOfServiceController.java} | 9 ++++-- .../auth/service/impl/TOSService.java | 5 ++-- .../auth/service/impl/TokenService.java | 18 +++++------- .../auth/service/impl/UserService.java | 29 ++++++++++--------- 5 files changed, 34 insertions(+), 29 deletions(-) rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/{TermsOfSerivceController.java => TermsOfServiceController.java} (89%) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 26edf1b55..bbeb9308c 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -182,6 +182,8 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe throw new NotAuthorizedException("User is deactivated"); } + logger.info("User with ID: {} is active.", authenticatedUser.getUuid()); + logger.info("Checking if user has accepted the latest terms of service."); if (this.tosEnabled && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate try { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java similarity index 89% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java index 281a3a129..5cce51440 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfSerivceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java @@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.security.RolesAllowed; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; @@ -30,13 +32,14 @@ @Tag(name = "Terms of Service Management") @Controller @RequestMapping("/tos") -public class TermsOfSerivceController { +public class TermsOfServiceController { + private final Logger logger = LoggerFactory.getLogger(TermsOfServiceController.class); private final TOSService tosService; private final UserService userService; @Autowired - public TermsOfSerivceController(TOSService tosService, UserService userService) { + public TermsOfServiceController(TOSService tosService, UserService userService) { this.tosService = tosService; this.userService = userService; } @@ -44,6 +47,7 @@ public TermsOfSerivceController(TOSService tosService, UserService userService) @Operation(description = "GET the latest Terms of Service") @GetMapping(path = "/latest", produces = "text/html") public ResponseEntity getLatestTermsOfService(){ + logger.info("Getting latest Terms of Service"); return PICSUREResponse.success(tosService.getLatest()); } @@ -64,6 +68,7 @@ public ResponseEntity updateTermsOfService( public ResponseEntity hasUserAcceptedTOS(){ SecurityContext context = SecurityContextHolder.getContext(); String userSubject = context.getAuthentication().getName(); + logger.info("Checking if user {} has accepted the latest TOS", userSubject); return PICSUREResponse.success(tosService.hasUserAcceptedLatest(userSubject)); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 35afe0436..64c017e12 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -4,7 +4,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.repository.TermsOfServiceRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfSerivceController; +import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfServiceController; import jakarta.persistence.NoResultException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -18,7 +18,7 @@ /** *

Provides business logic for the TermsOfService endpoint.

> * - * @see TermsOfSerivceController + * @see TermsOfServiceController */ @Service public class TOSService { @@ -41,6 +41,7 @@ public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository us public boolean hasUserAcceptedLatest(String userId) { + logger.info("Checking if user {} has accepted the latest TOS", userId); // If TOS is not enabled, then the user has accepted it if (!isToSEnabled) { return true; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index b74f4601b..947979bbf 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -94,7 +94,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg inputMap.remove("token"); } catch (NotAuthorizedException ex) { // only when the token is for sure invalid, we can dump it into the log. - logger.error("_inspectToken() the token - " + token + " - is invalid with exception: " + ex.getMessage()); + logger.error("_inspectToken() the token - {} - is invalid with exception: {}", token, ex.getMessage()); tokenInspection.setMessage(ex.getMessage()); return tokenInspection; } @@ -121,7 +121,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg throw new NullPointerException("Inner application error, please ask admin to check the log."); } - String subject = jws.getBody().getSubject(); + String subject = jws.getPayload().getSubject(); // get the user based on subject field in token User user; @@ -158,8 +158,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg if (isLongTermToken && !token.equals(user.getToken())) { // in long_term_token mode, the token needs to be exactly the same as the token in user table isLongTermTokenCompromised = true; - logger.error("_inspectToken User " + user.getUuid() + "|" + user.getSubject() - + "is sending a long term token that is not matching the record in database user table."); + logger.error("_inspectToken User {}|{}is sending a long term token that is not matching the record in database user table.", user.getUuid(), user.getSubject()); errorMsg = "Cannot find matched long term token, your token might have been refreshed."; } @@ -171,9 +170,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg // if no privileges associated isAuthorizationPassed = true; //we still want to log this, though. - logger.info("ACCESS_LOG ___ " + user.getUuid() + "," + user.getEmail() + "," + user.getName() + - " ___ has been granted access to execute query ___ " + inputMap.get("request") + " ___ in application ___ " + application.getName() - + " ___ NO APP PRIVILEGES DEFINED"); + logger.info("ACCESS_LOG ___ {},{},{} ___ has been granted access to execute query ___ {} ___ in application ___ {} ___ NO APP PRIVILEGES DEFINED", user.getUuid(), user.getEmail(), user.getName(), inputMap.get("request"), application.getName()); } else if (!isLongTermTokenCompromised && user.getRoles() != null // The protocol between applications and PSAMA is application will @@ -190,7 +187,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg if (isAuthorizationPassed) { tokenInspection.addField("active", true); - ArrayList roles = new ArrayList(); + ArrayList roles = new ArrayList<>(); for (Privilege p : user.getTotalPrivilege()) { roles.add(p.getName()); } @@ -200,14 +197,13 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg return tokenInspection; } - tokenInspection.addAllFields(jws.getBody()); + tokenInspection.addAllFields(jws.getPayload()); // attach all privileges associated with the application to the responseMap tokenInspection.addField("privileges", user.getPrivilegeNameSetByApplication(application)); - logger.info("_inspectToken() Successfully inspect and return response map: " - + tokenInspection.getResponseMap().entrySet() + logger.info("_inspectToken() Successfully inspect and return response map: {}", tokenInspection.getResponseMap().entrySet() .stream() .map(entry -> entry.getKey() + " - " + entry.getValue()) .collect(Collectors.joining(", "))); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index a6c39b758..cf6a20c56 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -307,28 +307,29 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) @Transactional public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasToken) { SecurityContext securityContext = SecurityContextHolder.getContext(); - User user = (User) securityContext.getAuthentication().getPrincipal(); - if (user == null || user.getUuid() == null) { + Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); + if (user.isEmpty() || user.get().getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - user = this.userRepository.getById(user.getUuid()); - if (user == null) { + user = this.userRepository.findById(user.get().getUuid()); + if (user.isEmpty()) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } + logger.info("getCurrentUser() user found: {}", user.get().getEmail()); User.UserForDisplay userForDisplay = new User.UserForDisplay() - .setEmail(user.getEmail()) - .setPrivileges(user.getPrivilegeNameSet()) - .setUuid(user.getUuid().toString()) - .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.getSubject())); + .setEmail(user.get().getEmail()) + .setPrivileges(user.get().getPrivilegeNameSet()) + .setUuid(user.get().getUuid().toString()) + .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.get().getSubject())); // currently, the queryScopes are simple combination of queryScope string together as a set. // We are expecting the queryScope string as plain string. If it is a JSON, we could change the // code to use JsonUtils.mergeTemplateMap(Map, Map) - Set privileges = user.getTotalPrivilege(); + Set privileges = user.get().getTotalPrivilege(); if (privileges != null && !privileges.isEmpty()) { Set scopes = new TreeSet<>(); privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { @@ -345,12 +346,12 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT if (hasToken != null) { - if (user.getToken() != null && !user.getToken().isEmpty()) { - userForDisplay.setToken(user.getToken()); + if (user.get().getToken() != null && !user.get().getToken().isEmpty()) { + userForDisplay.setToken(user.get().getToken()); } else { - user.setToken(generateUserLongTermToken(authorizationHeader)); - this.userRepository.save(user); - userForDisplay.setToken(user.getToken()); + user.get().setToken(generateUserLongTermToken(authorizationHeader)); + this.userRepository.save(user.get()); + userForDisplay.setToken(user.get().getToken()); } } From ee82e9059163cce1d915125b50f5f8ec9e62860f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 14:45:39 -0400 Subject: [PATCH 058/222] Updated logging messages in TOS related services Improved the clarity and consistency of log messages regarding Terms Of Service (TOS) acceptance. This includes providing more informative log statements when the TOS is either disabled or not found in the database, as well as when users have not accepted the latest TOS. This update allows for easier troubleshooting and understanding of TOS related operations. --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 3 ++- .../hms/dbmi/avillach/auth/rest/TermsOfServiceController.java | 2 +- .../hms/dbmi/avillach/auth/service/impl/TOSService.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index bbeb9308c..7e5cc5f73 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -184,7 +184,8 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe logger.info("User with ID: {} is active.", authenticatedUser.getUuid()); logger.info("Checking if user has accepted the latest terms of service."); - if (this.tosEnabled && tosService.getLatest() != null && !tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { + if (!tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { + logger.info("User with ID: {} has not accepted the latest terms of service.", authenticatedUser.getUuid()); //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate try { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User must accept terms of service"); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java index 5cce51440..7126ea25b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java @@ -68,7 +68,7 @@ public ResponseEntity updateTermsOfService( public ResponseEntity hasUserAcceptedTOS(){ SecurityContext context = SecurityContextHolder.getContext(); String userSubject = context.getAuthentication().getName(); - logger.info("Checking if user {} has accepted the latest TOS", userSubject); + logger.info("hasUserAcceptedTOS for user {}", userSubject); return PICSUREResponse.success(tosService.hasUserAcceptedLatest(userSubject)); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index 64c017e12..fad32000c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -44,15 +44,16 @@ public boolean hasUserAcceptedLatest(String userId) { logger.info("Checking if user {} has accepted the latest TOS", userId); // If TOS is not enabled, then the user has accepted it if (!isToSEnabled) { + logger.info("TOS is disabled"); return true; } // If there is no TOS, then the user has accepted it if (getLatest() == null) { + logger.info("No TOS found in database"); return true; } - logger.info("Checking Terms Of Service acceptance for user with id {}", userId); return checkAgainstTOSDate(userId); } From d234360fb6b39da157b326ba061e81bc3ed2d643 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 15:11:34 -0400 Subject: [PATCH 059/222] Improve JWT filter logging and response handling Added a line in JWTFilter.java to ensure the filter chain continues after checking for valid JWT. Enhanced how logging is done, utilizing a more flexible logging syntax for warning and info logs regarding user email and deactivation status. --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 7e5cc5f73..0c3a29b06 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -148,6 +148,8 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet // Authenticate as User setSecurityContextForUser(request, response, jws.getPayload().getSubject()); } + + filterChain.doFilter(request, response); } } @@ -177,8 +179,10 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe throw new IllegalArgumentException("Cannot validate user claims, based on information stored in the JWT token."); } + logger.info("User with email: {} is found.", authenticatedUser.getEmail()); + if (!authenticatedUser.isActive()) { - logger.warn("User with ID: " + authenticatedUser.getUuid() + " is deactivated."); + logger.warn("User with ID: {} is deactivated.", authenticatedUser.getUuid()); throw new NotAuthorizedException("User is deactivated"); } From 6dd72e8ed88731e3a7d9b7318f5c0d809b2ca2da Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 22 Apr 2024 15:28:09 -0400 Subject: [PATCH 060/222] Improve JWT filter logging and response handling Added a line in JWTFilter.java to ensure the filter chain continues after checking for valid JWT. Enhanced how logging is done, utilizing a more flexible logging syntax for warning and info logs regarding user email and deactivation status. --- .../harvard/hms/dbmi/avillach/auth/rest/UserController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index f82290ea0..179e82b24 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -77,8 +77,6 @@ public ResponseEntity updateUser(List users) { * every time a user hit this endpoint /me with the query parameter ?hasToken presented, * it will refresh the long term token. * - * @param hasToken - * @return */ @Operation(description = "Retrieve information of current user") @GetMapping(produces = "application/json", path = "/me") @@ -86,6 +84,7 @@ public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, @Parameter(required = false, description = "Attribute that represents if a long term token will attach to the response") @RequestParam("hasToken") Boolean hasToken) { + logger.info("getCurrentUser() authorizationHeader: {}, hasToken {}", authorizationHeader, hasToken); return this.userService.getCurrentUser(authorizationHeader, hasToken); } @@ -94,6 +93,7 @@ public ResponseEntity getCurrentUser( public ResponseEntity getQueryTemplate( @Parameter(description = "Application Id for the returning queryTemplate") @PathVariable("applicationId") String applicationId) { + logger.info("getQueryTemplate() applicationId: {}", applicationId); Optional mergedTemplate = this.userService.getQueryTemplate(applicationId); if (mergedTemplate.isEmpty()) { From 14f9255885037562eb2293f3aad2cc9dc0044c97 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 07:45:20 -0400 Subject: [PATCH 061/222] Implement Spring Security and change authentication flow Enabled Spring Security by modifying the JWTFilter class, UserService class and other related classes. Implemented UserDetailsService interface in UserService to return a UserDetails object upon successful authentication. Modified the "setSecurityContextForUser" and "setSecurityContextForApplication" methods to adopt Spring Security's authentication object. Created CustomUserDetails and CustomApplicationDetails classes to return an authenticated object with authorities. --- .../hms/dbmi/avillach/auth/entity/User.java | 16 ++++ .../dbmi/avillach/auth/filter/JWTFilter.java | 22 +++-- .../auth/model/CustomApplicationDetails.java | 57 ++++++++++++ .../auth/model/CustomUserDetails.java | 54 +++++++++++ .../auth/security/AuthSecurityContext.java | 90 +++++++++---------- .../auth/service/impl/UserService.java | 18 +++- .../src/main/resources/application.properties | 2 +- 7 files changed, 206 insertions(+), 53 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index 03ebefbcb..c01e498aa 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -29,6 +29,18 @@ public class User extends BaseEntity implements Serializable, Principal { private String email; + public User(User user) { + this.subject = user.getSubject(); + this.roles = user.getRoles(); + this.email = user.getEmail(); + this.connection = user.getConnection(); + this.matched = user.isMatched(); + this.acceptedTOS = user.getAcceptedTOS(); + this.auth0metadata = user.getAuth0metadata(); + this.generalMetadata = user.getGeneralMetadata(); + this.active = user.isActive(); + this.token = user.getToken(); + } /** *

NOTICE

@@ -62,6 +74,10 @@ public class User extends BaseEntity implements Serializable, Principal { @Column(name = "long_term_token") private String token; + public User() { + + } + public String getSubject() { return subject; } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 0c3a29b06..07a224776 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -4,9 +4,11 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import io.jsonwebtoken.Claims; @@ -22,6 +24,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.lang.NonNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -59,16 +65,18 @@ public class JWTFilter extends OncePerRequestFilter { private final boolean tosEnabled; private final JWTUtil jwtUtil; + private final UserService userService; @Autowired public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService, - @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled, JWTUtil jwtUtil) { + @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled, JWTUtil jwtUtil, UserService userService) { this.userRepo = userRepo; this.applicationRepo = applicationRepo; this.tosService = tosService; this.userClaimId = userClaimId; this.tosEnabled = tosEnabled; this.jwtUtil = jwtUtil; + this.userService = userService; } /** @@ -156,7 +164,10 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { logger.info("Setting security context for application: {}", authenticatedApplication.getName()); - request.setAttribute("authenticatedApplication", authenticatedApplication); + UserDetails userDetails = new CustomApplicationDetails(authenticatedApplication); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); } /** @@ -211,9 +222,10 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } } - // TODO: Spring is generally expecting ROLE_ prefix for roles. We may need to add this prefix to all the user roles. - // We don't want to add this to the database, because it may break backward compatibility for the UI. - request.setAttribute("authenticatedUser", authenticatedUser); + UserDetails userDetails = userService.loadUserByUsername(authenticatedUser.getEmail()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); } } \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java new file mode 100644 index 000000000..33c2739da --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java @@ -0,0 +1,57 @@ +package edu.harvard.hms.dbmi.avillach.auth.model; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +public class CustomApplicationDetails implements UserDetails { + + private final Collection authorities; + + public CustomApplicationDetails(Application authenticatedApplication) { + if (authenticatedApplication == null) { + throw new IllegalArgumentException("Application cannot be null"); + } + + this.authorities = authenticatedApplication.getPrivileges().stream() + .map(privilege -> (GrantedAuthority) privilege::getName) + .toList(); + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + return ""; + } + + @Override + public String getUsername() { + return ""; + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java new file mode 100644 index 000000000..24bc30155 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -0,0 +1,54 @@ +package edu.harvard.hms.dbmi.avillach.auth.model; + +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +public class CustomUserDetails extends User implements UserDetails { + + private final Collection authorities; + + public CustomUserDetails(User user) { + super(user); + this.authorities = user.getRoles().stream() + .map(role -> (GrantedAuthority) role::getName) + .toList(); + } + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return super.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return false; + } + + @Override + public boolean isAccountNonLocked() { + return false; + } + + @Override + public boolean isCredentialsNonExpired() { + return false; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java index 65fe5cbe0..40093fa48 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java @@ -1,45 +1,45 @@ -//package edu.harvard.hms.dbmi.avillach.auth.security; -// -//import java.security.Principal; -// -///** -// *

Implements the SecurityContext interface for JWTFilter to use.

-// */ -//public class AuthSecurityContext implements SecurityContext { -// -// private User user; -// private Application application; -// private String scheme; -// -// public AuthSecurityContext(User user, String scheme) { -// this.user = user; -// this.scheme = scheme; -// } -// -// public AuthSecurityContext(Application application, String scheme) { -// this.application = application; -// this.scheme = scheme; -// } -// -// @Override -// public Principal getUserPrincipal() { -// return this.user == null ? this.application : this.user; -// } -// -// @Override -// public boolean isUserInRole(String role) { -// if (user.getRoles() != null) -// return user.getPrivilegeNameSet().contains(role); -// return false; -// } -// -// @Override -// public boolean isSecure() { -// return "https".equals(this.scheme); -// } -// -// @Override -// public String getAuthenticationScheme() { -// return SecurityContext.DIGEST_AUTH; -// } -//} +package edu.harvard.hms.dbmi.avillach.auth.security; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; + +import java.security.Principal; + +/** + *

Implements the SecurityContext interface for JWTFilter to use.

+ */ +public class AuthSecurityContext implements SecurityContext { + + private User user; + private Application application; + + public AuthSecurityContext(User user, String scheme) { + this.user = user; + } + + public AuthSecurityContext(Application application, String scheme) { + this.application = application; + } + + public Principal getUserPrincipal() { + return this.user == null ? this.application : this.user; + } + + public boolean isUserInRole(String role) { + if (user.getRoles() != null) + return user.getPrivilegeNameSet().contains(role); + return false; + } + + @Override + public Authentication getAuthentication() { + return null; + } + + @Override + public void setAuthentication(Authentication authentication) { + + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index cf6a20c56..995c1ee47 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -7,6 +7,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; @@ -28,6 +29,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -37,7 +41,7 @@ import java.util.*; @Service -public class UserService { +public class UserService implements UserDetailsService { private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); @@ -90,7 +94,7 @@ public HashMap getUserProfileResponse(Map claims claims.get("sub").toString(), this.tokenExpirationTime ); - logger.info("getUserProfileResponse() PSAMA JWT token has been generated. Token:" + token); + logger.info("getUserProfileResponse() PSAMA JWT token has been generated. Token:{}", token); responseMap.put("token", token); logger.info("getUserProfileResponse() .usedId field is set"); @@ -506,4 +510,14 @@ public void changeRole(User currentUser, Set roles) { currentUser.setRoles(roles); this.userRepository.save(currentUser); } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = this.userRepository.findByEmail(username); + if (user == null) { + throw new UsernameNotFoundException("User not found with email: " + username); + } + + return new CustomUserDetails(user); + } } diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 6ad7d8f48..5ef54212c 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -59,4 +59,4 @@ application.token.expiration.time=${TOKEN_EXPIRATION_TIME:3600000} application.long.term.token.expiration.time=${LONG_TERM_TOKEN_EXPIRATION_TIME:2592000000} # Logging -#logging.level.org.springframework.security=TRACE \ No newline at end of file +logging.level.org.springframework.security=TRACE \ No newline at end of file From 1f80323ea178fa92e514e1bda47667ed3c16dcd0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 08:22:05 -0400 Subject: [PATCH 062/222] Update account status return values in CustomUserDetails The code updates the return values for account status checks in the CustomUserDetails class. Previously, these methods returned false regardless of the actual account status. Now, they correctly return true, indicating a non-expired and non-locked account status. --- .../hms/dbmi/avillach/auth/model/CustomUserDetails.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index 24bc30155..93c23cf26 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -34,17 +34,17 @@ public String getUsername() { @Override public boolean isAccountNonExpired() { - return false; + return true; } @Override public boolean isAccountNonLocked() { - return false; + return true; } @Override public boolean isCredentialsNonExpired() { - return false; + return true; } @Override From e1f46ac0057d66650e2af6c66f2b69e0bf0f69e0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 08:25:52 -0400 Subject: [PATCH 063/222] Add logging for user roles in JWTFilter --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 07a224776..565968d9d 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -222,6 +222,9 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } } + assert userRoles != null; + logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles.stream().map(Role::getName).toList()); + UserDetails userDetails = userService.loadUserByUsername(authenticatedUser.getEmail()); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); From 01477fb866d5de8a545d98689081c501cc176cae Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 08:51:20 -0400 Subject: [PATCH 064/222] Add CustomUserDetails and update user authentication --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 565968d9d..76c96f7a7 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -5,6 +5,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; @@ -224,8 +225,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe assert userRoles != null; logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles.stream().map(Role::getName).toList()); - - UserDetails userDetails = userService.loadUserByUsername(authenticatedUser.getEmail()); + CustomUserDetails userDetails = new CustomUserDetails(authenticatedUser); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); From 3c3fa927c0b1cb56cf944f848688e8184aa36a79 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 09:20:44 -0400 Subject: [PATCH 065/222] Refactor security config and improve user authentication Removed `AuthSecurityContext` class and various related security files as they are no longer needed. User authentication in `UserService` and `JWTFilter` now uses the `CustomUserDetails` class, streamlining the authentication process. The `SecurityConfig` file is also moved to a new package (`edu.harvard.hms.dbmi.avillach.auth.config`) and an `ApplicationConfig` class is added which provides a common `AuthenticationProvider` bean. --- .../auth/config/ApplicationConfig.java | 33 ++++++++++++++ .../{security => config}/SecurityConfig.java | 9 +++- .../dbmi/avillach/auth/filter/JWTFilter.java | 19 +++----- .../auth/model/CustomUserDetails.java | 2 +- .../auth/security/AuthSecurityContext.java | 45 ------------------- .../avillach/auth/security/package-info.java | 4 -- .../auth/service/impl/UserService.java | 5 +-- 7 files changed, 48 insertions(+), 69 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java rename pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/{security => config}/SecurityConfig.java (82%) delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java delete mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/package-info.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java new file mode 100644 index 000000000..8a7ca7c60 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java @@ -0,0 +1,33 @@ +package edu.harvard.hms.dbmi.avillach.auth.config; + +import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; + +@Configuration +public class ApplicationConfig { + + private final UserService userService; + + @Autowired + public ApplicationConfig(UserService userService) { + this.userService = userService; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userService); + return provider; + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java similarity index 82% rename from pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java rename to pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java index 8cc4daadd..245fa7b9e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java @@ -1,9 +1,10 @@ -package edu.harvard.hms.dbmi.avillach.auth.security; +package edu.harvard.hms.dbmi.avillach.auth.config; import edu.harvard.hms.dbmi.avillach.auth.filter.JWTFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -20,15 +21,19 @@ public class SecurityConfig { private final JWTFilter jwtFilter; + private final AuthenticationProvider authenticationProvider; + @Autowired - public SecurityConfig(JWTFilter jwtFilter) { + public SecurityConfig(JWTFilter jwtFilter, AuthenticationProvider authenticationProvider) { this.jwtFilter = jwtFilter; + this.authenticationProvider = authenticationProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .sessionManagement((session) -> session.sessionCreationPolicy(STATELESS)) + .authenticationProvider(authenticationProvider) .authorizeHttpRequests((authorizeRequests) -> authorizeRequests.requestMatchers("/actuator/health", "/actuator/info", "/authentication/**", "/swagger.yaml", "/swagger.json","/authentication").permitAll() .anyRequest().authenticated() diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 76c96f7a7..4bd296814 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -2,12 +2,10 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; @@ -56,26 +54,21 @@ public class JWTFilter extends OncePerRequestFilter { private final static Logger logger = LoggerFactory.getLogger(JWTFilter.class); - private final UserRepository userRepo; - private final ApplicationRepository applicationRepo; private final TOSService tosService; private final String userClaimId; - private final boolean tosEnabled; private final JWTUtil jwtUtil; private final UserService userService; @Autowired - public JWTFilter(UserRepository userRepo, ApplicationRepository applicationRepo, TOSService tosService, - @Value("${application.user.id.claim}") String userClaimId, @Value("${application.tos.enabled}") boolean tosEnabled, JWTUtil jwtUtil, UserService userService) { - this.userRepo = userRepo; + public JWTFilter(ApplicationRepository applicationRepo, TOSService tosService, + @Value("${application.user.id.claim}") String userClaimId, JWTUtil jwtUtil, UserService userService) { this.applicationRepo = applicationRepo; this.tosService = tosService; this.userClaimId = userClaimId; - this.tosEnabled = tosEnabled; this.jwtUtil = jwtUtil; this.userService = userService; } @@ -184,7 +177,7 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic private void setSecurityContextForUser(HttpServletRequest request, HttpServletResponse response, String realClaimsSubject) { logger.info("Setting security context for user: {}", realClaimsSubject); - User authenticatedUser = userRepo.findBySubject(realClaimsSubject); + CustomUserDetails authenticatedUser = userService.loadUserByUsername(realClaimsSubject); if (authenticatedUser == null) { logger.error("Cannot validate user claims, based on information stored in the JWT token."); @@ -223,10 +216,8 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } } - assert userRoles != null; - logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles.stream().map(Role::getName).toList()); - CustomUserDetails userDetails = new CustomUserDetails(authenticatedUser); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index 93c23cf26..f784bd056 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -8,7 +8,7 @@ public class CustomUserDetails extends User implements UserDetails { - private final Collection authorities; + private Collection authorities; public CustomUserDetails(User user) { super(user); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java deleted file mode 100644 index 40093fa48..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/AuthSecurityContext.java +++ /dev/null @@ -1,45 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth.security; - -import edu.harvard.hms.dbmi.avillach.auth.entity.Application; -import edu.harvard.hms.dbmi.avillach.auth.entity.User; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; - -import java.security.Principal; - -/** - *

Implements the SecurityContext interface for JWTFilter to use.

- */ -public class AuthSecurityContext implements SecurityContext { - - private User user; - private Application application; - - public AuthSecurityContext(User user, String scheme) { - this.user = user; - } - - public AuthSecurityContext(Application application, String scheme) { - this.application = application; - } - - public Principal getUserPrincipal() { - return this.user == null ? this.application : this.user; - } - - public boolean isUserInRole(String role) { - if (user.getRoles() != null) - return user.getPrivilegeNameSet().contains(role); - return false; - } - - @Override - public Authentication getAuthentication() { - return null; - } - - @Override - public void setAuthentication(Authentication authentication) { - - } -} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/package-info.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/package-info.java deleted file mode 100644 index 0949ea237..000000000 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/security/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Contains security filters that check every incoming request to PSAMA and corresponding models. - */ -package edu.harvard.hms.dbmi.avillach.auth.security; \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 995c1ee47..339191387 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -29,7 +29,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -512,8 +511,8 @@ public void changeRole(User currentUser, Set roles) { } @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = this.userRepository.findByEmail(username); + public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = this.userRepository.findBySubject(username); if (user == null) { throw new UsernameNotFoundException("User not found with email: " + username); } From 9c371bc5ff10228843a6b03f900f2a020a8c1bc0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:16:20 -0400 Subject: [PATCH 066/222] Refactor UserController and enhance security features --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 1 + .../hms/dbmi/avillach/auth/model/CustomUserDetails.java | 2 +- .../harvard/hms/dbmi/avillach/auth/rest/UserController.java | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 4bd296814..b48357d8d 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -219,6 +219,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(authentication); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index f784bd056..93c23cf26 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -8,7 +8,7 @@ public class CustomUserDetails extends User implements UserDetails { - private Collection authorities; + private final Collection authorities; public CustomUserDetails(User user) { super(user); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 179e82b24..15310c596 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -24,7 +24,8 @@ *

Endpoint for service handling business logic for users.

*/ @Tag(name = "User Management") -@Controller("/user") +@Controller +@RequestMapping("/user") public class UserController { private final static Logger logger = LoggerFactory.getLogger(UserController.class); From 4f1f5f9abaadbb7bd5a6ac71be11cf9c5a17e12b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:33:25 -0400 Subject: [PATCH 067/222] Refactor UserController and SecurityConfig classes --- .../harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java | 2 +- .../harvard/hms/dbmi/avillach/auth/rest/UserController.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java index 245fa7b9e..17ae2a275 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java @@ -15,7 +15,7 @@ import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration -@EnableMethodSecurity +@EnableMethodSecurity() @EnableWebSecurity public class SecurityConfig { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 15310c596..493a8560a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -83,8 +83,8 @@ public ResponseEntity updateUser(List users) { @GetMapping(produces = "application/json", path = "/me") public ResponseEntity getCurrentUser( @RequestHeader("Authorization") String authorizationHeader, - @Parameter(required = false, description = "Attribute that represents if a long term token will attach to the response") - @RequestParam("hasToken") Boolean hasToken) { + @Parameter(description = "Attribute that represents if a long term token will attach to the response") + @RequestParam(name = "hasToken", required = false) Boolean hasToken) { logger.info("getCurrentUser() authorizationHeader: {}, hasToken {}", authorizationHeader, hasToken); return this.userService.getCurrentUser(authorizationHeader, hasToken); } From 3bd14301b0a919152533bd7932e19205e1b0936f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:43:07 -0400 Subject: [PATCH 068/222] Disable pre-post method security --- .../harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java index 17ae2a275..f02f491d0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/SecurityConfig.java @@ -15,7 +15,7 @@ import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; @Configuration -@EnableMethodSecurity() +@EnableMethodSecurity(prePostEnabled = false) @EnableWebSecurity public class SecurityConfig { From 542ff3e2be0528bc0e61e88fc491fd42d976622a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:45:34 -0400 Subject: [PATCH 069/222] Remove redundant SecurityContext initialization --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index b48357d8d..4bd296814 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -219,7 +219,6 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(authentication); } From b718f82ad0f7ccd1fe3083701d0cadccd46ce5f6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:47:34 -0400 Subject: [PATCH 070/222] Add UUID to User entity constructor --- .../java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index c01e498aa..aa5440620 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -30,6 +30,7 @@ public class User extends BaseEntity implements Serializable, Principal { private String email; public User(User user) { + this.uuid = user.getUuid(); this.subject = user.getSubject(); this.roles = user.getRoles(); this.email = user.getEmail(); From de30656ad6564637cb2c0c464d0fd8acaa058178 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:50:16 -0400 Subject: [PATCH 071/222] Add logging for security context in UserService --- .../hms/dbmi/avillach/auth/service/impl/UserService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 339191387..1178425a8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -310,6 +310,8 @@ private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) @Transactional public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasToken) { SecurityContext securityContext = SecurityContextHolder.getContext(); + logger.info("Security context: {}", securityContext); + Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); if (user.isEmpty() || user.get().getUuid() == null) { logger.error("Security context didn't have a user stored."); From 1d94c8425bf8bf91b0f228247c1707430939f231 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 10:58:24 -0400 Subject: [PATCH 072/222] Add SecurityContext to handle authentication in JWTFilter --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 4bd296814..27ff9f2e6 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -24,6 +24,7 @@ import org.springframework.core.annotation.Order; import org.springframework.lang.NonNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -219,7 +220,10 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } } \ No newline at end of file From 9475d049bff55a2a0225380a17f27a83a6ad4282 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 11:11:25 -0400 Subject: [PATCH 073/222] Update user service and application repository method --- .../dbmi/avillach/auth/repository/ApplicationRepository.java | 3 +++ .../hms/dbmi/avillach/auth/service/impl/UserService.java | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java index 86d82894a..15fb729d7 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/ApplicationRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; import java.util.UUID; /** @@ -17,4 +18,6 @@ public interface ApplicationRepository extends JpaRepository Application findByName(String name); + Optional findByUuid(UUID uuid); + } \ No newline at end of file diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 1178425a8..f6b80bfa7 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -363,7 +363,6 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT return PICSUREResponse.success(userForDisplay); } - @Transactional public Optional getQueryTemplate(String applicationId) { if (applicationId == null || applicationId.trim().isEmpty()) { logger.error("getQueryTemplate() input application UUID is null or empty."); @@ -383,7 +382,7 @@ public Optional getQueryTemplate(String applicationId) { return Optional.empty(); } - Optional application = this.applicationRepository.findById(UUID.fromString(applicationId)); + Optional application = this.applicationRepository.findByUuid(UUID.fromString(applicationId)); if (application.isEmpty()) { logger.error("getQueryTemplate() cannot find corresponding application by UUID: {}", applicationId); throw new IllegalArgumentException("Cannot find application by input UUID: " + applicationId); From 5a8327a543cdc14272f78ecb7917b7048e3812bb Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 13:07:38 -0400 Subject: [PATCH 074/222] Update application default UUID variable --- .../src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index 5ef54212c..d0a2be501 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -34,7 +34,7 @@ application.client.secret.base64=${CLIENT_SECRET_IS_BASE_64:false} application.user.id.claim=${USER_ID_CLAIM:sub} application.auth0.host=${AUTH0_HOST} application.tos.enabled=${TOS_ENABLED:true} -application.default.uuid=${__STACK_SPECIFIC_APPLICATION_ID__:default_uuid} +application.default.uuid=${STACK_SPECIFIC_APPLICATION_ID:default_uuid} application.system.name=${SYSTEM_NAME:PIC-SURE All-in-one} application.template.path=${TEMPLATE_PATH:/opt/jboss/wildfly/standalone/configuration/emailTemplates/} application.access.grant.email.subject=${GRANT_EMAIL_SUBJECT:email_subject} From ec4fc5fc2d7e556f2fb1a7eccbefd2693ff88815 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 13:31:52 -0400 Subject: [PATCH 075/222] Refactor PrivilegeController route mapping --- .../hms/dbmi/avillach/auth/rest/PrivilegeController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index 7924a3078..4390b8809 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -49,7 +49,7 @@ public ResponseEntity getPrivilegeById( @Operation(description = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) - @GetMapping(path = "/", produces = "application/json") + @GetMapping(produces = "application/json") public ResponseEntity getPrivilegeAll() { List privilegesAll = this.privilegeService.getPrivilegesAll(); return PICSUREResponse.success(privilegesAll); From 8fbb1b11115f9626bc3428bb1c44a3c5147a64d9 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 13:51:09 -0400 Subject: [PATCH 076/222] Remove hasToken if null check The hasToken query param is not correctly used in the UI. The application only passes "?hasToken" but fails to assign a value. --- .../auth/service/impl/UserService.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index f6b80bfa7..72b8b8e69 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -349,15 +349,12 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT userForDisplay.setQueryScopes(scopes); } - if (hasToken != null) { - - if (user.get().getToken() != null && !user.get().getToken().isEmpty()) { - userForDisplay.setToken(user.get().getToken()); - } else { - user.get().setToken(generateUserLongTermToken(authorizationHeader)); - this.userRepository.save(user.get()); - userForDisplay.setToken(user.get().getToken()); - } + if (user.get().getToken() != null && !user.get().getToken().isEmpty()) { + userForDisplay.setToken(user.get().getToken()); + } else { + user.get().setToken(generateUserLongTermToken(authorizationHeader)); + this.userRepository.save(user.get()); + userForDisplay.setToken(user.get().getToken()); } return PICSUREResponse.success(userForDisplay); @@ -382,10 +379,10 @@ public Optional getQueryTemplate(String applicationId) { return Optional.empty(); } - Optional application = this.applicationRepository.findByUuid(UUID.fromString(applicationId)); + Optional application = this.applicationRepository.findById(UUID.fromString(applicationId)); if (application.isEmpty()) { - logger.error("getQueryTemplate() cannot find corresponding application by UUID: {}", applicationId); - throw new IllegalArgumentException("Cannot find application by input UUID: " + applicationId); + logger.error("getQueryTemplate() cannot find corresponding application by UUID: {}", UUID.fromString(applicationId)); + throw new IllegalArgumentException("Cannot find application by input UUID: " + UUID.fromString(applicationId)); } return Optional.ofNullable(mergeTemplate(user.orElse(null), application.orElse(null))); From 8ba0b974d82f2ffd8166ef52e21aa028a6890513 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 14:08:15 -0400 Subject: [PATCH 077/222] Update token authentication check in JWTFilter The check for the application token prefix was adjusted to use userId instead of the authorizationHeader. Additionally, a new security context is now being generated each time an authentication is made, adding an extra layer of security. --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 27ff9f2e6..50863b4fd 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -120,7 +120,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet } - if (authorizationHeader.startsWith(AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX)) { + if (userId.startsWith(AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX)) { logger.info("User Authentication Starts with {}", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX); // Check if user is attempting to access the correct introspect endpoint. If not reject the request @@ -162,7 +162,9 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic UserDetails userDetails = new CustomApplicationDetails(authenticatedApplication); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); } /** From afe39452d4e4c6f5bae31fcf65bfabbfb523ed80 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 14:34:40 -0400 Subject: [PATCH 078/222] Logging for debugging --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 50863b4fd..111f090cc 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -130,10 +130,13 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User is deactivated"); } + String applicationId = userId.split("\\|")[1]; + logger.info("Application ID: {}", applicationId); + // Authenticate as Application - Optional authenticatedApplication = applicationRepo.findById(UUID.fromString(userId.split("\\|")[1])); + Optional authenticatedApplication = applicationRepo.findByUuid(UUID.fromString(applicationId)); if (authenticatedApplication.isEmpty()) { - logger.error("Cannot find an application by userId: {}", userId); + logger.error("Cannot find an application by userId: {}", applicationId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); return; } From 8b801b566e6dd2ebf37c7dedba60977530126df2 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 15:05:20 -0400 Subject: [PATCH 079/222] Remove authorities from CustomApplicationDetails --- .../avillach/auth/model/CustomApplicationDetails.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java index 33c2739da..e35e0e66c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java @@ -5,24 +5,19 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; +import java.util.List; public class CustomApplicationDetails implements UserDetails { - private final Collection authorities; - public CustomApplicationDetails(Application authenticatedApplication) { if (authenticatedApplication == null) { throw new IllegalArgumentException("Application cannot be null"); } - - this.authorities = authenticatedApplication.getPrivileges().stream() - .map(privilege -> (GrantedAuthority) privilege::getName) - .toList(); } @Override public Collection getAuthorities() { - return this.authorities; + return List.of(); } @Override From dab551a02799cb1118b48bc56637bfbd03d1e425 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 15:07:22 -0400 Subject: [PATCH 080/222] Add copy constructor to Application class Added a copy constructor to the Application class to provide cloned instances of authenticated applications. CustomApplicationDetails now extends the Application class for reusability and to avoid null checks. The overriding methods in CustomApplicationDetails also now return more meaningful values. --- .../dbmi/avillach/auth/entity/Application.java | 10 ++++++++++ .../auth/model/CustomApplicationDetails.java | 16 +++++++--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index b07770e6e..7a948a093 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -31,6 +31,16 @@ public class Application extends BaseEntity implements Principal { orphanRemoval = true) private Set privileges; + public Application(Application authenticatedApplication) { + this.uuid = authenticatedApplication.getUuid(); + this.name = authenticatedApplication.getName(); + this.description = authenticatedApplication.getDescription(); + this.token = authenticatedApplication.getToken(); + this.url = authenticatedApplication.getUrl(); + this.enable = authenticatedApplication.isEnable(); + this.privileges = authenticatedApplication.getPrivileges(); + } + public String getName() { return name; } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java index e35e0e66c..0553ee280 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java @@ -7,12 +7,10 @@ import java.util.Collection; import java.util.List; -public class CustomApplicationDetails implements UserDetails { +public class CustomApplicationDetails extends Application implements UserDetails { public CustomApplicationDetails(Application authenticatedApplication) { - if (authenticatedApplication == null) { - throw new IllegalArgumentException("Application cannot be null"); - } + super(authenticatedApplication); } @Override @@ -27,26 +25,26 @@ public String getPassword() { @Override public String getUsername() { - return ""; + return super.getName(); } @Override public boolean isAccountNonExpired() { - return false; + return true; } @Override public boolean isAccountNonLocked() { - return false; + return true; } @Override public boolean isCredentialsNonExpired() { - return false; + return true; } @Override public boolean isEnabled() { - return false; + return true; } } From a9f61a0831cb73d7b47386a0f53e07b2c675c4d9 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 23 Apr 2024 16:05:19 -0400 Subject: [PATCH 081/222] Add default constructor to Application.java --- .../harvard/hms/dbmi/avillach/auth/entity/Application.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 7a948a093..a40f655e1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -26,7 +26,7 @@ public class Application extends BaseEntity implements Principal { private boolean enable = true; @OneToMany(mappedBy = "application", - fetch = FetchType.LAZY, + fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) private Set privileges; @@ -41,6 +41,10 @@ public Application(Application authenticatedApplication) { this.privileges = authenticatedApplication.getPrivileges(); } + public Application() { + + } + public String getName() { return name; } From 8062ca973763bd9330e16e2fb3355c7bcb9ec83b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 08:13:18 -0400 Subject: [PATCH 082/222] Refactor @OneToMany relationship in Application.java --- .../hms/dbmi/avillach/auth/entity/Application.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index a40f655e1..ba4ab1ca0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -1,11 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.entity; import com.fasterxml.jackson.annotation.JsonInclude; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; import java.security.Principal; import java.util.Set; @@ -25,10 +21,8 @@ public class Application extends BaseEntity implements Principal { private String url; private boolean enable = true; - @OneToMany(mappedBy = "application", - fetch = FetchType.EAGER, - cascade = CascadeType.ALL, - orphanRemoval = true) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinColumn(name = "application_id", referencedColumnName = "uuid") private Set privileges; public Application(Application authenticatedApplication) { From 935a5f260529a6b9d41ff73d266b53c37a888fb0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 08:35:40 -0400 Subject: [PATCH 083/222] Refactor Application entity and adjust application retrieval logic --- .../avillach/auth/entity/Application.java | 22 +++++++++---------- .../dbmi/avillach/auth/filter/JWTFilter.java | 20 +++++++++-------- .../auth/service/impl/ApplicationService.java | 17 +++++++++++++- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index ba4ab1ca0..e0550be1d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -21,22 +21,20 @@ public class Application extends BaseEntity implements Principal { private String url; private boolean enable = true; - @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "application_id", referencedColumnName = "uuid") private Set privileges; - public Application(Application authenticatedApplication) { - this.uuid = authenticatedApplication.getUuid(); - this.name = authenticatedApplication.getName(); - this.description = authenticatedApplication.getDescription(); - this.token = authenticatedApplication.getToken(); - this.url = authenticatedApplication.getUrl(); - this.enable = authenticatedApplication.isEnable(); - this.privileges = authenticatedApplication.getPrivileges(); - } - - public Application() { + public Application() {} + public Application(Application application) { + this.uuid = application.getUuid(); + this.name = application.getName(); + this.description = application.getDescription(); + this.token = application.getToken(); + this.url = application.getUrl(); + this.enable = application.isEnable(); + this.privileges = application.getPrivileges(); } public String getName() { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 111f090cc..5a9a3b213 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -6,6 +6,7 @@ import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; @@ -55,8 +56,7 @@ public class JWTFilter extends OncePerRequestFilter { private final static Logger logger = LoggerFactory.getLogger(JWTFilter.class); - private final ApplicationRepository applicationRepo; - + private final ApplicationService applicationService; private final TOSService tosService; private final String userClaimId; @@ -65,13 +65,13 @@ public class JWTFilter extends OncePerRequestFilter { private final UserService userService; @Autowired - public JWTFilter(ApplicationRepository applicationRepo, TOSService tosService, + public JWTFilter(ApplicationService applicationService, TOSService tosService, @Value("${application.user.id.claim}") String userClaimId, JWTUtil jwtUtil, UserService userService) { - this.applicationRepo = applicationRepo; this.tosService = tosService; this.userClaimId = userClaimId; this.jwtUtil = jwtUtil; this.userService = userService; + this.applicationService = applicationService; } /** @@ -134,21 +134,23 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet logger.info("Application ID: {}", applicationId); // Authenticate as Application - Optional authenticatedApplication = applicationRepo.findByUuid(UUID.fromString(applicationId)); + Optional authenticatedApplication = applicationService.getApplicationByID(applicationId); if (authenticatedApplication.isEmpty()) { logger.error("Cannot find an application by userId: {}", applicationId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); return; } - if (!authenticatedApplication.get().getToken().equals(token)) { + Application application = authenticatedApplication.get(); + + if (!application.getToken().equals(token)) { logger.error("filter() incoming application token - {} - is not the same as record, might because the token has been refreshed. Subject: {}", token, userId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token has been inactivated, please contact admin to grab you the latest one."); } // This is the application token that is being used to authenticate the user by other applications // Set the security context for the application - setSecurityContextForApplication(request, authenticatedApplication.orElse(null)); + setSecurityContextForApplication(request, application); } else { logger.debug("UserID: {} is not a long term token and not a PSAMA application token.", userId); // Authenticate as User @@ -162,8 +164,8 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { logger.info("Setting security context for application: {}", authenticatedApplication.getName()); - UserDetails userDetails = new CustomApplicationDetails(authenticatedApplication); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authentication); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index b7afc394c..e8f91cd26 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -36,8 +36,23 @@ public class ApplicationService { * @param applicationId the ID of the entity to retrieve * @return a ResponseEntity representing the result of the operation */ + @Transactional public Optional getApplicationByID(String applicationId) { - return this.applicationRepo.findById(UUID.fromString(applicationId)); + Optional byId = this.applicationRepo.findById(UUID.fromString(applicationId)); + + if (byId.isEmpty()) { + return Optional.empty(); + } + + Application application = byId.get(); + + // This is a workaround to avoid lazy loading exception + // If the application doesn't load the privileges, it will throw an exception + // when tryin to access the privileges elsewhere. + // The @Transactional is also added to the method to avoid this issue. + application.getPrivileges().size(); + + return Optional.of(application); } public List getAllApplications() { From 1756ffd7ed3af55f4ea82a867a39274356228061 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:17:29 -0400 Subject: [PATCH 084/222] Revert changes to method --- .../auth/service/impl/ApplicationService.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index e8f91cd26..f2477f990 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -38,21 +38,7 @@ public class ApplicationService { */ @Transactional public Optional getApplicationByID(String applicationId) { - Optional byId = this.applicationRepo.findById(UUID.fromString(applicationId)); - - if (byId.isEmpty()) { - return Optional.empty(); - } - - Application application = byId.get(); - - // This is a workaround to avoid lazy loading exception - // If the application doesn't load the privileges, it will throw an exception - // when tryin to access the privileges elsewhere. - // The @Transactional is also added to the method to avoid this issue. - application.getPrivileges().size(); - - return Optional.of(application); + return this.applicationRepo.findById(UUID.fromString(applicationId)); } public List getAllApplications() { From b5ac9be7d585365e7102b5a80df50900c9b45609 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:23:44 -0400 Subject: [PATCH 085/222] Refactor UserDetails subclasses for better encapsulation Removed inheritance from User and Application classes in CustomUserDetails and CustomApplicationDetails respectively. These classes now encapsulate User and Application objects instead of extending them. This change helps in reducing coupling and making the network of class dependencies easier to handle. --- .../avillach/auth/model/CustomApplicationDetails.java | 8 +++++--- .../hms/dbmi/avillach/auth/model/CustomUserDetails.java | 7 ++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java index 0553ee280..3c53b6448 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java @@ -7,10 +7,12 @@ import java.util.Collection; import java.util.List; -public class CustomApplicationDetails extends Application implements UserDetails { +public class CustomApplicationDetails implements UserDetails { + + private final Application application; public CustomApplicationDetails(Application authenticatedApplication) { - super(authenticatedApplication); + this.application = authenticatedApplication; } @Override @@ -25,7 +27,7 @@ public String getPassword() { @Override public String getUsername() { - return super.getName(); + return this.application.getName(); } @Override diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index 93c23cf26..91d8e4e8f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -6,12 +6,13 @@ import java.util.Collection; -public class CustomUserDetails extends User implements UserDetails { +public class CustomUserDetails implements UserDetails { + private final User user; private final Collection authorities; public CustomUserDetails(User user) { - super(user); + this.user = user; this.authorities = user.getRoles().stream() .map(role -> (GrantedAuthority) role::getName) .toList(); @@ -29,7 +30,7 @@ public String getPassword() { @Override public String getUsername() { - return super.getEmail(); + return this.user.getEmail(); } @Override From a6cd2e2c8b332552c9cfc51bdc137e77ff61a603 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:25:58 -0400 Subject: [PATCH 086/222] Add getUser method to CustomUserDetails --- .../hms/dbmi/avillach/auth/model/CustomUserDetails.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index 91d8e4e8f..35873dfce 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -52,4 +52,8 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + + public User getUser() { + return user; + } } From f724fbb8701edb92ef9ebf4311081ec34f231067 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:26:11 -0400 Subject: [PATCH 087/222] Refactor JWTFilter for better user context management --- .../dbmi/avillach/auth/filter/JWTFilter.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 5a9a3b213..644d33ce5 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -27,7 +27,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -35,7 +34,6 @@ import java.io.IOException; import java.util.Optional; import java.util.Set; -import java.util.UUID; /** @@ -159,13 +157,12 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet filterChain.doFilter(request, response); } - } private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { logger.info("Setting security context for application: {}", authenticatedApplication.getName()); CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, null); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authentication); @@ -192,17 +189,17 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe throw new IllegalArgumentException("Cannot validate user claims, based on information stored in the JWT token."); } - logger.info("User with email: {} is found.", authenticatedUser.getEmail()); + logger.info("User with email: {} is found.", authenticatedUser.getUser().getEmail()); - if (!authenticatedUser.isActive()) { - logger.warn("User with ID: {} is deactivated.", authenticatedUser.getUuid()); + if (!authenticatedUser.getUser().isActive()) { + logger.warn("User with ID: {} is deactivated.", authenticatedUser.getUser().getUuid()); throw new NotAuthorizedException("User is deactivated"); } - logger.info("User with ID: {} is active.", authenticatedUser.getUuid()); + logger.info("User with ID: {} is active.", authenticatedUser.getUser().getUuid()); logger.info("Checking if user has accepted the latest terms of service."); - if (!tosService.hasUserAcceptedLatest(authenticatedUser.getSubject())) { - logger.info("User with ID: {} has not accepted the latest terms of service.", authenticatedUser.getUuid()); + if (!tosService.hasUserAcceptedLatest(authenticatedUser.getUser().getSubject())) { + logger.info("User with ID: {} has not accepted the latest terms of service.", authenticatedUser.getUser().getUuid()); //If user has not accepted terms of service and is attempted to get information other than the terms of service, don't authenticate try { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User must accept terms of service"); @@ -212,7 +209,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } // Get the user's roles - Set userRoles = authenticatedUser.getRoles(); + Set userRoles = authenticatedUser.getUser().getRoles(); // Check if the user has any roles and privileges associated with them if (userRoles == null || userRoles.isEmpty() || userRoles.stream().noneMatch(role -> role.getPrivileges() != null && !role.getPrivileges().isEmpty())) { @@ -224,7 +221,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } } - logger.info("User with email {} has roles {}.", authenticatedUser.getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); + logger.info("User with email {} has roles {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); From 0eaf869a1192aca9e356fa11602ad793be84fdb3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:46:21 -0400 Subject: [PATCH 088/222] Update JWTFilter setting of security context --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 644d33ce5..3ecc2c5e6 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -162,7 +162,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { logger.info("Setting security context for application: {}", authenticatedApplication.getName()); CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, null); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authentication); From 1c11955f848a5b2622fb8f64dec22ef2fcd85eca Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 10:57:43 -0400 Subject: [PATCH 089/222] Refactor user retrieval in UserService Removed unnecessary steps in the process of retrieving a user within the UserService. Instead of retrieving the user from the userRepository with the uuid, the code now gets the user directly from the customUserDetails, simplifying the process and reducing redundancy. This should maintain function and enhance clarity. --- .../avillach/auth/service/impl/UserService.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 72b8b8e69..34509f024 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -367,25 +367,20 @@ public Optional getQueryTemplate(String applicationId) { } SecurityContext securityContext = SecurityContextHolder.getContext(); - Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); - if (user.isEmpty() || user.get().getUuid() == null) { + Optional customUserDetails = Optional.ofNullable((CustomUserDetails) securityContext.getAuthentication().getPrincipal()); + if (customUserDetails.isEmpty() || customUserDetails.get().getUser() == null){ logger.error("Security context didn't have a user stored."); return Optional.empty(); } - user = this.userRepository.findById(user.get().getUuid()); - if (user.isEmpty()) { - logger.error("When retrieving current user, it returned null"); - return Optional.empty(); - } - + User user = customUserDetails.get().getUser(); Optional application = this.applicationRepository.findById(UUID.fromString(applicationId)); if (application.isEmpty()) { logger.error("getQueryTemplate() cannot find corresponding application by UUID: {}", UUID.fromString(applicationId)); throw new IllegalArgumentException("Cannot find application by input UUID: " + UUID.fromString(applicationId)); } - return Optional.ofNullable(mergeTemplate(user.orElse(null), application.orElse(null))); + return Optional.ofNullable(mergeTemplate(user, application.orElse(null))); } public ResponseEntity getDefaultQueryTemplate() { From 1777906598f6be5d01fc0bdd0cdb0cba78e6264a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 11:09:04 -0400 Subject: [PATCH 090/222] Replace User with CustomUserDetails in UserService --- .../hms/dbmi/avillach/auth/entity/User.java | 6 ++-- .../auth/service/impl/UserService.java | 30 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java index aa5440620..5be98d6c9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/User.java @@ -107,7 +107,7 @@ public Set getTotalPrivilege(){ return null; Set privileges = new HashSet<>(); - roles.stream().forEach(r -> privileges.addAll(r.getPrivileges())); + roles.forEach(r -> privileges.addAll(r.getPrivileges())); return privileges; } @@ -121,9 +121,7 @@ public Set getTotalAccessRule(){ return null; Set accessRules = new HashSet<>(); - roles.stream(). - forEach(r -> r.getPrivileges().stream(). - forEach(p -> accessRules.addAll(p.getAccessRules()))); + roles.forEach(r -> r.getPrivileges().forEach(p -> accessRules.addAll(p.getAccessRules()))); return accessRules; } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 34509f024..abc0a47d7 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -312,29 +312,29 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT SecurityContext securityContext = SecurityContextHolder.getContext(); logger.info("Security context: {}", securityContext); - Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); - if (user.isEmpty() || user.get().getUuid() == null) { + Optional customUserDetails = Optional.ofNullable((CustomUserDetails) securityContext.getAuthentication().getPrincipal()); + if (customUserDetails.isEmpty() || customUserDetails.get().getUser() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - user = this.userRepository.findById(user.get().getUuid()); - if (user.isEmpty()) { + User user = customUserDetails.get().getUser(); + if (user == null) { logger.error("When retrieving current user, it returned null"); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - logger.info("getCurrentUser() user found: {}", user.get().getEmail()); + logger.info("getCurrentUser() user found: {}", user.getEmail()); User.UserForDisplay userForDisplay = new User.UserForDisplay() - .setEmail(user.get().getEmail()) - .setPrivileges(user.get().getPrivilegeNameSet()) - .setUuid(user.get().getUuid().toString()) - .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.get().getSubject())); + .setEmail(user.getEmail()) + .setPrivileges(user.getPrivilegeNameSet()) + .setUuid(user.getUuid().toString()) + .setAcceptedTOS(this.tosService.hasUserAcceptedLatest(user.getSubject())); // currently, the queryScopes are simple combination of queryScope string together as a set. // We are expecting the queryScope string as plain string. If it is a JSON, we could change the // code to use JsonUtils.mergeTemplateMap(Map, Map) - Set privileges = user.get().getTotalPrivilege(); + Set privileges = user.getTotalPrivilege(); if (privileges != null && !privileges.isEmpty()) { Set scopes = new TreeSet<>(); privileges.stream().filter(privilege -> privilege.getQueryScope() != null).forEach(privilege -> { @@ -349,12 +349,12 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT userForDisplay.setQueryScopes(scopes); } - if (user.get().getToken() != null && !user.get().getToken().isEmpty()) { - userForDisplay.setToken(user.get().getToken()); + if (user.getToken() != null && !user.getToken().isEmpty()) { + userForDisplay.setToken(user.getToken()); } else { - user.get().setToken(generateUserLongTermToken(authorizationHeader)); - this.userRepository.save(user.get()); - userForDisplay.setToken(user.get().getToken()); + user.setToken(generateUserLongTermToken(authorizationHeader)); + this.userRepository.save(user); + userForDisplay.setToken(user.getToken()); } return PICSUREResponse.success(userForDisplay); From 4cd929c4c1578da5b82f594772610a759cd74832 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 13:29:21 -0400 Subject: [PATCH 091/222] Optimize authentication setting in JWTFilter The way of setting authentication in the JWTFilter has been streamlined. Instead of creating an empty security context and then setting authentication, we now directly get the existing security context and set authentication. This simplifies the code and avoids unnecessary object creation. --- .../harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 3ecc2c5e6..a2306df03 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -25,7 +25,6 @@ import org.springframework.core.annotation.Order; import org.springframework.lang.NonNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; @@ -164,9 +163,7 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); + SecurityContextHolder.getContext().setAuthentication(authentication); } /** @@ -224,9 +221,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe logger.info("User with email {} has roles {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); + SecurityContextHolder.getContext().setAuthentication(authentication); } From 8d0d22266089e275d2d159a6d1f8d0b1672f7d2b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 13:29:32 -0400 Subject: [PATCH 092/222] Add @RequestBody annotation to inputMap parameter This change was made in the inspectToken method of the TokenController class to ensure that the inputMap parameter is correctly bound from the body of the request. This allows the application to correctly handle JSON data that is sent in the request body. --- .../harvard/hms/dbmi/avillach/auth/rest/TokenController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index 1e21e9624..d8552a132 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import java.util.Map; @@ -39,7 +40,7 @@ public TokenController(TokenService tokenService) { public ResponseEntity inspectToken( @Parameter(required = true, description = "A JSON object that at least" + " include a user the token for validation") - Map inputMap) { + @RequestBody Map inputMap) { return this.tokenService.inspectToken(inputMap); } From fd3ccf9afd2b2f54d01377c2d8bd700b18850e38 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 18:55:26 -0400 Subject: [PATCH 093/222] Add logging for created authenticationToken object An info level logging statement is added to JWTFilter.java. This records the creation of a new authenticationToken object for a specific application, which will help in tracking this process and troubleshooting authentication issues. --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index a2306df03..74bf77273 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -5,7 +5,6 @@ import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; -import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; @@ -163,6 +162,7 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + logger.info("Created authenticationToken object {} for application: {}", authentication, authenticatedApplication.getName()); SecurityContextHolder.getContext().setAuthentication(authentication); } From 891171e4b984afec1910afa61d35931df9b6d8c4 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 18:55:35 -0400 Subject: [PATCH 094/222] Refactor TokenController import statements and annotations This commit simplifies TokenController's import statements by using a wildcard to import all classes within the org.springframework.web.bind.annotation package. The changes also modify the annotation of the TokenController class, replacing the @Controller annotation with @Controller and @RequestMapping annotations, specifying the '/token' path. --- .../hms/dbmi/avillach/auth/rest/TokenController.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index d8552a132..8d09cf836 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -8,10 +8,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.*; import java.util.Map; @@ -25,7 +22,8 @@ * {@link AuthorizationService} at the access rule level, but this endpoint handles token validation and pre-check at the privilege level.

*/ @Tag(name = "Token Management") -@Controller("/token") +@Controller +@RequestMapping("/token") public class TokenController { private final TokenService tokenService; From 77225b490bcbd4d02e8f7a999b5308db5b57e9b4 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 18:55:53 -0400 Subject: [PATCH 095/222] Update getPassword method and add getApplication method The getPassword() method in CustomApplicationDetails now returns null instead of an empty string. Introduced the getApplication() method to this class as well. It returns the application object associated with current instance. --- .../dbmi/avillach/auth/model/CustomApplicationDetails.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java index 3c53b6448..56c4bd6ea 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomApplicationDetails.java @@ -22,7 +22,7 @@ public Collection getAuthorities() { @Override public String getPassword() { - return ""; + return null; } @Override @@ -49,4 +49,8 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + + public Application getApplication() { + return application; + } } From f06b6ff0213823922a77f6ecfd83eb97edc9dbcb Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 18:56:02 -0400 Subject: [PATCH 096/222] Update fetch type and remove constructor in Application class The fetch type for the 'privileges' set has been updated from EAGER to LAZY in the Application entity class. This will improve performance by loading 'privileges' only when necessary. Additionally, the redundant copy constructor has been removed as it was duplicating data unnecessarily. --- .../hms/dbmi/avillach/auth/entity/Application.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index e0550be1d..38185fc10 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -21,22 +21,10 @@ public class Application extends BaseEntity implements Principal { private String url; private boolean enable = true; - @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "application_id", referencedColumnName = "uuid") private Set privileges; - public Application() {} - - public Application(Application application) { - this.uuid = application.getUuid(); - this.name = application.getName(); - this.description = application.getDescription(); - this.token = application.getToken(); - this.url = application.getUrl(); - this.enable = application.isEnable(); - this.privileges = application.getPrivileges(); - } - public String getName() { return name; } From 1d60c0cbc399a5dc86c1a592dea60f6e2c70abe5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 19:10:37 -0400 Subject: [PATCH 097/222] Add CustomUserDetailService and refactor user and app verification Introduced a new service, CustomUserDetailService, to handle user and application authentication. This led to a significant refactoring of several classes, removing user and application verification logic from JWTFilter and UserService, and placing it within the new service. This change enhances code organization, and improves maintainability by isolating user and application verification into a dedicated service. --- .../auth/config/ApplicationConfig.java | 10 ++-- .../dbmi/avillach/auth/filter/JWTFilter.java | 31 ++++++------- .../auth/service/impl/ApplicationService.java | 10 +++- .../service/impl/CustomUserDetailService.java | 46 +++++++++++++++++++ .../auth/service/impl/UserService.java | 14 ++---- 5 files changed, 76 insertions(+), 35 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java index 8a7ca7c60..ea5e43fe1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/config/ApplicationConfig.java @@ -1,6 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.config; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.CustomUserDetailService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -12,11 +12,11 @@ @Configuration public class ApplicationConfig { - private final UserService userService; + private final CustomUserDetailService customUserDetailService; @Autowired - public ApplicationConfig(UserService userService) { - this.userService = userService; + public ApplicationConfig(CustomUserDetailService customUserDetailService) { + this.customUserDetailService = customUserDetailService; } @Bean @@ -27,7 +27,7 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration a @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(userService); + provider.setUserDetailsService(customUserDetailService); return provider; } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 74bf77273..23cd2751c 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -6,6 +6,7 @@ import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; +import edu.harvard.hms.dbmi.avillach.auth.service.impl.CustomUserDetailService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; @@ -30,7 +31,6 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.Optional; import java.util.Set; @@ -52,22 +52,20 @@ public class JWTFilter extends OncePerRequestFilter { private final static Logger logger = LoggerFactory.getLogger(JWTFilter.class); - private final ApplicationService applicationService; private final TOSService tosService; private final String userClaimId; private final JWTUtil jwtUtil; - private final UserService userService; + private final CustomUserDetailService customUserDetailService; @Autowired - public JWTFilter(ApplicationService applicationService, TOSService tosService, - @Value("${application.user.id.claim}") String userClaimId, JWTUtil jwtUtil, UserService userService) { + public JWTFilter(TOSService tosService, + @Value("${application.user.id.claim}") String userClaimId, JWTUtil jwtUtil, CustomUserDetailService customUserDetailService) { this.tosService = tosService; this.userClaimId = userClaimId; this.jwtUtil = jwtUtil; - this.userService = userService; - this.applicationService = applicationService; + this.customUserDetailService = customUserDetailService; } /** @@ -130,14 +128,14 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet logger.info("Application ID: {}", applicationId); // Authenticate as Application - Optional authenticatedApplication = applicationService.getApplicationByID(applicationId); - if (authenticatedApplication.isEmpty()) { + CustomApplicationDetails customApplicationDetails = (CustomApplicationDetails) this.customUserDetailService.loadUserByUsername("application:" + applicationId); + if (customApplicationDetails.getApplication() == null) { logger.error("Cannot find an application by userId: {}", applicationId); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Your token doesn't contain valid identical information, please contact admin."); return; } - Application application = authenticatedApplication.get(); + Application application = customApplicationDetails.getApplication(); if (!application.getToken().equals(token)) { logger.error("filter() incoming application token - {} - is not the same as record, might because the token has been refreshed. Subject: {}", token, userId); @@ -146,7 +144,7 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet // This is the application token that is being used to authenticate the user by other applications // Set the security context for the application - setSecurityContextForApplication(request, application); + setSecurityContextForApplication(request, customApplicationDetails); } else { logger.debug("UserID: {} is not a long term token and not a PSAMA application token.", userId); // Authenticate as User @@ -157,12 +155,11 @@ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServlet } } - private void setSecurityContextForApplication(HttpServletRequest request, Application authenticatedApplication) { - logger.info("Setting security context for application: {}", authenticatedApplication.getName()); - CustomApplicationDetails applicationDetails = new CustomApplicationDetails(authenticatedApplication); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(applicationDetails, null, applicationDetails.getAuthorities()); + private void setSecurityContextForApplication(HttpServletRequest request, CustomApplicationDetails authenticatedApplication) { + logger.info("Setting security context for application: {}", authenticatedApplication.getApplication().getName()); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedApplication, null, authenticatedApplication.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - logger.info("Created authenticationToken object {} for application: {}", authentication, authenticatedApplication.getName()); + logger.info("Created authenticationToken object {} for application: {}", authentication, authenticatedApplication.getApplication().getName()); SecurityContextHolder.getContext().setAuthentication(authentication); } @@ -179,7 +176,7 @@ private void setSecurityContextForApplication(HttpServletRequest request, Applic private void setSecurityContextForUser(HttpServletRequest request, HttpServletResponse response, String realClaimsSubject) { logger.info("Setting security context for user: {}", realClaimsSubject); - CustomUserDetails authenticatedUser = userService.loadUserByUsername(realClaimsSubject); + CustomUserDetails authenticatedUser = (CustomUserDetails) this.customUserDetailService.loadUserByUsername(realClaimsSubject); if (authenticatedUser == null) { logger.error("Cannot validate user claims, based on information stored in the JWT token."); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index f2477f990..299225ff5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -9,14 +9,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service -public class ApplicationService { +public class ApplicationService implements UserDetailsService { private final static Logger logger = LoggerFactory.getLogger(ApplicationService.class); private final ApplicationRepository applicationRepo; @@ -138,5 +140,9 @@ public String generateApplicationToken(Application application) { AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getUuid().toString(), 365L * 1000 * 60 * 60 * 24); } + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return null; + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java new file mode 100644 index 000000000..7c513f7c5 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java @@ -0,0 +1,46 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class CustomUserDetailService implements UserDetailsService { + + private final UserService userService; + private final ApplicationService applicationService; + + @Autowired + public CustomUserDetailService(UserService userService, ApplicationService applicationService) { + this.userService = userService; + this.applicationService = applicationService; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + if (username.startsWith("application:")) { + String applicationName = username.substring(12); + Optional applicationByID = applicationService.getApplicationByID(applicationName); + if (applicationByID.isEmpty()) { + throw new UsernameNotFoundException("Application not found"); + } + + return new CustomApplicationDetails(applicationByID.get()); + } else { + User user = this.userService.findBySubject(username); + if (user == null) { + throw new UsernameNotFoundException("User not found with email: " + username); + } + + return new CustomUserDetails(user); + } + } +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index abc0a47d7..414548a65 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -29,8 +29,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,7 +38,7 @@ import java.util.*; @Service -public class UserService implements UserDetailsService { +public class UserService { private final Logger logger = LoggerFactory.getLogger(UserService.class.getName()); @@ -503,13 +501,7 @@ public void changeRole(User currentUser, Set roles) { this.userRepository.save(currentUser); } - @Override - public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = this.userRepository.findBySubject(username); - if (user == null) { - throw new UsernameNotFoundException("User not found with email: " + username); - } - - return new CustomUserDetails(user); + public User findBySubject(String username) { + return this.userRepository.findBySubject(username); } } From 7e293abbe8d9ba38cc6d0481e64366a3731d05f1 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 20:00:47 -0400 Subject: [PATCH 098/222] Refactor TokenService to use CustomApplicationDetails In the TokenService, replaced the direct retrieval of the Application principal with the use of the newly introduced CustomApplicationDetails object. This approach provides a more structured way to handle application data. Also, refactored logging statements to use placeholders, thus improving the readability of the code and efficiency of string concatenation. --- .../avillach/auth/service/impl/TokenService.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 947979bbf..5e13e672a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -4,6 +4,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.TokenInspection; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; @@ -102,14 +103,12 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg Application application; try { - application = (Application) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + CustomApplicationDetails customApplicationDetails = (CustomApplicationDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + application = customApplicationDetails.getApplication(); } catch (ClassCastException ex) { SecurityContext securityContext = SecurityContextHolder.getContext(); String principalName = securityContext.getAuthentication().getName(); - logger.error(principalName - + " - " + principalName + - " - is trying to use token introspection endpoint" + - ", but it is not an application"); + logger.error("{} - {} - is trying to use token introspection endpoint, but it is not an application", principalName, principalName); throw new IllegalAccessException("The application token does not associate with an application but " + principalName); } @@ -139,9 +138,9 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg } user = this.userRepository.findBySubject(subject); - logger.info("_inspectToken() user with subject - " + subject + " - exists in database"); + logger.info("_inspectToken() user with subject - {} - exists in database", subject); if (user == null) { - logger.error("_inspectToken() could not find user with subject " + subject); + logger.error("_inspectToken() could not find user with subject {}", subject); tokenInspection.setMessage("user doesn't exist"); return tokenInspection; } From a77ed634cc28f2b48a9303507ad295c825f89b5d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 20:42:00 -0400 Subject: [PATCH 099/222] Add Hibernate initialization to prevent lazy loading exception The code has been updated to initialize the privileges of the application proxy object. This change is aimed at preventing a lazy loading exception from occurring in the CustomUserDetailService. The initialization utilizes the Hibernate framework. --- .../avillach/auth/service/impl/CustomUserDetailService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java index 7c513f7c5..e6bd44645 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java @@ -4,6 +4,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -33,6 +34,8 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx throw new UsernameNotFoundException("Application not found"); } + // Initialize the privileges of the application proxy object to avoid lazy loading exception + Hibernate.initialize(applicationByID.get().getPrivileges()); return new CustomApplicationDetails(applicationByID.get()); } else { User user = this.userService.findBySubject(username); From 890c3b2ea4560923b7c0a63d0c03a402cf9de06f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 24 Apr 2024 21:02:53 -0400 Subject: [PATCH 100/222] Update method for retrieving application details Updated the `loadUserByUsername` method in `CustomUserDetailService` to call `getApplicationByIdWithPrivileges` instead of `getApplicationByID`. The new method was implemented in `ApplicationService` and is designed to avoid lazy loading exception by initializing application privileges during retrieval. This ensures that all relevant information about an application is fully loaded before any other operations are completed. --- .../auth/service/impl/ApplicationService.java | 19 +++++++++++++++++++ .../service/impl/CustomUserDetailService.java | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 299225ff5..eeee190ad 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -6,6 +6,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.hibernate.Hibernate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -43,6 +44,24 @@ public Optional getApplicationByID(String applicationId) { return this.applicationRepo.findById(UUID.fromString(applicationId)); } + /** + * Retrieves an entity by its ID with its privileges. This method is used to avoid lazy loading exception. + * @param applicationName + * @return + */ + @Transactional + public Optional getApplicationByIdWithPrivileges(String applicationName) { + Optional byId = this.applicationRepo.findById(UUID.fromString(applicationName)); + + if (byId.isEmpty()) { + return Optional.empty(); + } + + Application application = byId.get(); + Hibernate.initialize(application.getPrivileges()); + return Optional.of(application); + } + public List getAllApplications() { return this.applicationRepo.findAll(); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java index e6bd44645..e1d267338 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java @@ -29,7 +29,7 @@ public CustomUserDetailService(UserService userService, ApplicationService appli public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username.startsWith("application:")) { String applicationName = username.substring(12); - Optional applicationByID = applicationService.getApplicationByID(applicationName); + Optional applicationByID = applicationService.getApplicationByIdWithPrivileges(applicationName); if (applicationByID.isEmpty()) { throw new UsernameNotFoundException("Application not found"); } From 9040d3c350785203f28af9dd1faf8e2199c9c18a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 07:02:50 -0400 Subject: [PATCH 101/222] Disable SQL and security logging in application properties --- .../src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/resources/application.properties b/pic-sure-auth-services/src/main/resources/application.properties index d0a2be501..185d3d5cc 100644 --- a/pic-sure-auth-services/src/main/resources/application.properties +++ b/pic-sure-auth-services/src/main/resources/application.properties @@ -11,7 +11,7 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.driver-class-name=com.mysql.jdbc.Driver # JPA/Hibernate properties -spring.jpa.show-sql=true +#spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect #spring.jpa.hibernate.ddl-auto=create @@ -59,4 +59,4 @@ application.token.expiration.time=${TOKEN_EXPIRATION_TIME:3600000} application.long.term.token.expiration.time=${LONG_TERM_TOKEN_EXPIRATION_TIME:2592000000} # Logging -logging.level.org.springframework.security=TRACE \ No newline at end of file +#logging.level.org.springframework.security=TRACE \ No newline at end of file From b7dd7944a34cce60d2f1166bf3455e3342d2e564 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 07:12:03 -0400 Subject: [PATCH 102/222] Remove logging of client secrets --- .../java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 79dd349d1..35f6b2f21 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -43,7 +43,6 @@ public JWTUtil(@Value("${application.client.secret}") String clientSecret, } private String getDecodedClientSecret() { - logger.info("Client secret {}", clientSecret); if (clientSecretIsBase64) { return new String(Base64.decodeBase64(clientSecret)); } From cfe45743bb2473715722bdddd4e57d677881e522 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 07:30:15 -0400 Subject: [PATCH 103/222] Remove unused imports --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 23cd2751c..1d8919755 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -5,10 +5,8 @@ import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.CustomUserDetailService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TOSService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import io.jsonwebtoken.Claims; From 1b3d877272f992a786f5a3223f5036b5d4f58b4f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 08:15:03 -0400 Subject: [PATCH 104/222] Refactor refreshUserToken method for clarity It now obtains the user from a CustomUserDetails object rather than using an Optional, which simplifies the code and makes error handling more direct. User information updating mechanism has also been simplified. --- .../avillach/auth/service/impl/UserService.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 414548a65..abfc9fe46 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -435,23 +435,17 @@ private String mergeTemplate(User user, Application application) { @Transactional public ResponseEntity refreshUserToken(HttpHeaders httpHeaders) { SecurityContext securityContext = SecurityContextHolder.getContext(); - Optional user = Optional.ofNullable((User) securityContext.getAuthentication().getPrincipal()); - if (user.isEmpty() || user.get().getUuid() == null) { + CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); + if (customUserDetails == null || customUserDetails.getUser() == null || customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } - user = this.userRepository.findById(user.get().getUuid()); - if (user.isEmpty()) { - logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); - } - + User user = customUserDetails.getUser(); String authorizationHeader = httpHeaders.getFirst("Authorization"); String longTermToken = generateUserLongTermToken(authorizationHeader); - user.get().setToken(longTermToken); - - this.userRepository.save(user.get()); + user.setToken(longTermToken); + this.userRepository.save(user); return PICSUREResponse.success(Map.of("userLongTermToken", longTermToken)); } From e4e02653197a121465f1e7ede7b0170919bf769d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 08:21:09 -0400 Subject: [PATCH 105/222] Update user authentication in UserService Changed the authentication to use CustomUserDetails instead of just User in the addUsers and updateUser methods within UserService. --- .../avillach/auth/service/impl/UserService.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index abfc9fe46..310bfd21a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -196,18 +196,18 @@ private boolean allowUpdateSuperAdminRole( @Transactional public ResponseEntity addUsers(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); - User currentUser = (User) securityContext.getAuthentication().getPrincipal(); - if (currentUser == null || currentUser.getUuid() == null) { + CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); + if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } + User currentUser = customUserDetails.getUser(); checkAssociation(users); for (User user : users) { - logger.debug("Adding User " + user); - if (!allowUpdateSuperAdminRole(currentUser, user, null)) { // TODO: The allowUpdateSuperAdminRole is a private method - logger.error("updateUser() user - " + currentUser.getUuid() + " - with roles [" + currentUser.getRoleString() + "] - is not allowed to grant " - + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user."); + logger.debug("Adding User {}", user); + if (!allowUpdateSuperAdminRole(currentUser, user, null)) { + logger.error("updateUser() user - {} - with roles [{}] - is not allowed to grant " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " role when adding a user.", currentUser.getUuid(), currentUser.getRoleString()); throw new IllegalArgumentException("Not allowed to add a user with a " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege associated."); } @@ -254,12 +254,13 @@ private void checkAssociation(List users) { @Transactional public ResponseEntity updateUser(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); - User currentUser = (User) securityContext.getAuthentication().getPrincipal(); - if (currentUser == null || currentUser.getUuid() == null) { + CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); + if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); return PICSUREResponse.applicationError("Inner application error, please contact admin."); } + User currentUser = customUserDetails.getUser(); checkAssociation(users); boolean allowUpdate = true; for (User user : users) { From 6cd9b56dc2dbdd01cd021105b55a6432a63a3a7f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 08:53:51 -0400 Subject: [PATCH 106/222] Add @RequestBody annotation in addUser method --- .../edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 493a8560a..19e612e5a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -61,7 +61,7 @@ public ResponseEntity getUserAll() { @PostMapping(produces = "application/json") public ResponseEntity addUser( @Parameter(required = true, description = "A list of user in JSON format") - List users) { + @RequestBody List users) { return this.userService.addUsers(users); } From 237582a164845380b4a7f7b9e909c81f774defa9 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 09:49:40 -0400 Subject: [PATCH 107/222] Refactor UserService constructor and update user roles and connections handling --- .../avillach/auth/service/impl/UserService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 310bfd21a..6db593538 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -58,9 +58,15 @@ public class UserService { private final JWTUtil jwtUtil; @Autowired - public UserService(BasicMailService basicMailService, TOSService tosService, UserRepository userRepository, ConnectionRepository connectionRepository, ApplicationRepository applicationRepository, RoleService roleService, + public UserService(BasicMailService basicMailService, TOSService tosService, + UserRepository userRepository, + ConnectionRepository connectionRepository, + ApplicationRepository applicationRepository, + RoleService roleService, @Value("${application.token.expiration.time}") long tokenExpirationTime, - @Value("${application.default.uuid}") String applicationUUID, @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime, JWTUtil jwtUtil) { + @Value("${application.default.uuid}") String applicationUUID, + @Value("${application.long.term.token.expiration.time}") long longTermTokenExpirationTime, + JWTUtil jwtUtil) { this.basicMailService = basicMailService; this.tosService = tosService; this.userRepository = userRepository; @@ -240,12 +246,12 @@ private void checkAssociation(List users) { for (User user : users) { if (user.getRoles() != null) { Set roles = new HashSet<>(); - user.getRoles().forEach(t -> this.roleService.addObjectToSet(roles, t)); // TODO: We need to fix the exception that is thrown here + user.getRoles().forEach(t -> this.roleService.addObjectToSet(roles, t)); user.setRoles(roles); } if (user.getConnection() != null) { - Optional connection = this.connectionRepository.findById(UUID.fromString(user.getConnection().getId())); + Optional connection = this.connectionRepository.findById(user.getConnection().getId()); user.setConnection(connection.orElse(null)); } } From d4b869fbfd7724da4cf06a3456b154a750894eba Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 09:59:36 -0400 Subject: [PATCH 108/222] Add @RequestBody annotation to updateUser method --- .../harvard/hms/dbmi/avillach/auth/rest/UserController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 19e612e5a..c58d4cc47 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -69,7 +69,8 @@ public ResponseEntity addUser( @Operation(description = "Update a list of users, will only update the fields listed, requires ADMIN role") @RolesAllowed({ADMIN}) @PutMapping(produces = "application/json") - public ResponseEntity updateUser(List users) { + public ResponseEntity updateUser( + @RequestBody List users) { return this.userService.updateUser(users); } From 8a2cdf29acb50cd57d90acca88dc33ece851558c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 10:34:34 -0400 Subject: [PATCH 109/222] Add logging to CustomUserDetailService --- .../avillach/auth/service/impl/CustomUserDetailService.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java index e1d267338..93bd19022 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailService.java @@ -5,6 +5,8 @@ import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -16,6 +18,8 @@ @Service public class CustomUserDetailService implements UserDetailsService { + private final Logger logger = LoggerFactory.getLogger(CustomUserDetailService.class); + private final UserService userService; private final ApplicationService applicationService; @@ -38,6 +42,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx Hibernate.initialize(applicationByID.get().getPrivileges()); return new CustomApplicationDetails(applicationByID.get()); } else { + logger.info("Loading user by username: {}", username); User user = this.userService.findBySubject(username); if (user == null) { throw new UsernameNotFoundException("User not found with email: " + username); From a05dedc9284f48a0dc3326dbe5590b153e12ce85 Mon Sep 17 00:00:00 2001 From: TDeSain Date: Thu, 25 Apr 2024 10:47:48 -0400 Subject: [PATCH 110/222] Adding a jenkinsfile # initial jenkins file * testing a scm jenkins pipeline --- jenkinsfile | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 jenkinsfile diff --git a/jenkinsfile b/jenkinsfile new file mode 100644 index 000000000..e0d8f6dbf --- /dev/null +++ b/jenkinsfile @@ -0,0 +1,33 @@ +pipeline { + agent any + + parameters { + string(name: 'DOCKER_REGISTRY', description: 'Docker registry URL (e.g., ECR URL)', defaultValue: '') + string(name: 'REPOSITORY_NAME', description: 'Docker repository name', defaultValue: 'hms-dbmi/pic-sure-auth-microapp') + } + + environment { + DOCKER_BUILD_ARGS = "--build-arg http_proxy=$http_proxy --build-arg https_proxy=$http_proxy --build-arg no_proxy=\"$no_proxy\" --build-arg HTTP_PROXY=$http_proxy --build-arg HTTPS_PROXY=$http_proxy --build-arg NO_PROXY=\"$no_proxy\"" + GIT_BRANCH_SHORT = sh(script: 'echo ${GIT_BRANCH} | cut -d "/" -f 2', returnStdout: true).trim() + GIT_COMMIT_SHORT = sh(script: 'echo ${GIT_COMMIT} | cut -c1-7', returnStdout: true).trim() + IMAGE_TAG = "${GIT_BRANCH_SHORT}_${GIT_COMMIT_SHORT}" + LATEST_TAG = "latest" + } + + stages { + stage('build') { + steps { + dir('pic-sure-auth-services') { + sh "docker build ${DOCKER_BUILD_ARGS} -t ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ." + sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" + } + } + } + stage('deploy') { + # TODO - deploy it? + } + stage('test') { + # TODO - test it? + } + } +} From eb13a65f928a2b6637230ad6a9667d9fb9c8e43f Mon Sep 17 00:00:00 2001 From: TDeSain Date: Thu, 25 Apr 2024 10:52:05 -0400 Subject: [PATCH 111/222] correct comments.... --- jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index e0d8f6dbf..a6e56d486 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -24,10 +24,10 @@ pipeline { } } stage('deploy') { - # TODO - deploy it? + // TODO - deploy it? } stage('test') { - # TODO - test it? + // TODO - test it? } } } From aac2fe2bc37a2875adb2058f8f2aaaad75c146d2 Mon Sep 17 00:00:00 2001 From: TDeSain Date: Thu, 25 Apr 2024 10:53:23 -0400 Subject: [PATCH 112/222] Update jenkinsfile --- jenkinsfile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index a6e56d486..a562fc1b4 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -23,11 +23,5 @@ pipeline { } } } - stage('deploy') { - // TODO - deploy it? - } - stage('test') { - // TODO - test it? - } } } From b4c1200f2e4ed4416cd79ab1ce10147c4a6ee5a0 Mon Sep 17 00:00:00 2001 From: TDeSain Date: Thu, 25 Apr 2024 10:56:01 -0400 Subject: [PATCH 113/222] Update jenkinsfile --- jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index a562fc1b4..e843f3740 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -2,8 +2,8 @@ pipeline { agent any parameters { - string(name: 'DOCKER_REGISTRY', description: 'Docker registry URL (e.g., ECR URL)', defaultValue: '') - string(name: 'REPOSITORY_NAME', description: 'Docker repository name', defaultValue: 'hms-dbmi/pic-sure-auth-microapp') + string(name: 'DOCKER_REGISTRY', description: 'Docker registry URL (e.g., ECR URL)', defaultValue: 'hms-dbmi') + string(name: 'REPOSITORY_NAME', description: 'Docker repository name', defaultValue: 'pic-sure-auth-microapp') } environment { From 809d7e6d26c96a5d027715eb19f070e3d44d7c26 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:58:09 -0400 Subject: [PATCH 114/222] Update jenkinsfile --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index e843f3740..566a2bb28 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -7,7 +7,7 @@ pipeline { } environment { - DOCKER_BUILD_ARGS = "--build-arg http_proxy=$http_proxy --build-arg https_proxy=$http_proxy --build-arg no_proxy=\"$no_proxy\" --build-arg HTTP_PROXY=$http_proxy --build-arg HTTPS_PROXY=$http_proxy --build-arg NO_PROXY=\"$no_proxy\"" + DOCKER_BUILD_ARGS = "-f ./pic-sure-auth-services/Dockerfile --build-arg http_proxy=$http_proxy --build-arg https_proxy=$http_proxy --build-arg no_proxy=\"$no_proxy\" --build-arg HTTP_PROXY=$http_proxy --build-arg HTTPS_PROXY=$http_proxy --build-arg NO_PROXY=\"$no_proxy\"" GIT_BRANCH_SHORT = sh(script: 'echo ${GIT_BRANCH} | cut -d "/" -f 2', returnStdout: true).trim() GIT_COMMIT_SHORT = sh(script: 'echo ${GIT_COMMIT} | cut -c1-7', returnStdout: true).trim() IMAGE_TAG = "${GIT_BRANCH_SHORT}_${GIT_COMMIT_SHORT}" From a301ed8331a46b30408eefae37fcec9309091379 Mon Sep 17 00:00:00 2001 From: TDeSain Date: Thu, 25 Apr 2024 11:02:14 -0400 Subject: [PATCH 115/222] Update jenkinsfile --- jenkinsfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index 566a2bb28..22ad21d23 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -17,10 +17,8 @@ pipeline { stages { stage('build') { steps { - dir('pic-sure-auth-services') { - sh "docker build ${DOCKER_BUILD_ARGS} -t ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ." - sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" - } + sh "docker build ${DOCKER_BUILD_ARGS} -t ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ." + sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" } } } From 122389ed5687d6590a70297bc827825d061812ed Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:12:18 -0400 Subject: [PATCH 116/222] Update jenkinsfile Add deploy step --- jenkinsfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 22ad21d23..4b073905f 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -3,7 +3,7 @@ pipeline { parameters { string(name: 'DOCKER_REGISTRY', description: 'Docker registry URL (e.g., ECR URL)', defaultValue: 'hms-dbmi') - string(name: 'REPOSITORY_NAME', description: 'Docker repository name', defaultValue: 'pic-sure-auth-microapp') + string(name: 'REPOSITORY_NAME', description: 'Docker repository name', defaultValue: 'psama') } environment { @@ -21,5 +21,17 @@ pipeline { sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" } } + stage('deploy') { + steps { + sh "docker stop psama && docker rm psama" + sh "docker run --name=psama --restart always \ + --network=picsure \ + --env-file /usr/local/docker-config/psama/.env \ + $EMAIL_TEMPLATE_VOUME \ + $TRUSTSTORE_VOLUME \ + -e JAVA_OPTS="$PSAMA_OPTS $TRUSTSTORE_JAVA_OPTS" \ + -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}" + } + } } } From b139c88f89e6752080d69f109b980cdaf4a79508 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:15:05 -0400 Subject: [PATCH 117/222] Update jenkinsfile --- jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index 4b073905f..39617f109 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -24,13 +24,13 @@ pipeline { stage('deploy') { steps { sh "docker stop psama && docker rm psama" - sh "docker run --name=psama --restart always \ + sh """docker run --name=psama --restart always \ --network=picsure \ --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOUME \ $TRUSTSTORE_VOLUME \ -e JAVA_OPTS="$PSAMA_OPTS $TRUSTSTORE_JAVA_OPTS" \ - -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}" + -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } } From cc0b0951bc12051b64d688c21e668e57f5f07993 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:18:30 -0400 Subject: [PATCH 118/222] Update jenkinsfile --- jenkinsfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 39617f109..9bfe7d42e 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -12,6 +12,9 @@ pipeline { GIT_COMMIT_SHORT = sh(script: 'echo ${GIT_COMMIT} | cut -c1-7', returnStdout: true).trim() IMAGE_TAG = "${GIT_BRANCH_SHORT}_${GIT_COMMIT_SHORT}" LATEST_TAG = "latest" + EMAIL_TEMPLATE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/emailTemplates:/opt/jboss/wildfly/standalone/configuration/emailTemplates " + TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" + PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true $PROXY_OPTS" } stages { @@ -27,7 +30,7 @@ pipeline { sh """docker run --name=psama --restart always \ --network=picsure \ --env-file /usr/local/docker-config/psama/.env \ - $EMAIL_TEMPLATE_VOUME \ + $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ -e JAVA_OPTS="$PSAMA_OPTS $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" From 17ba94676e4ab5f7ade288a5aea4ee1017a9855f Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:20:40 -0400 Subject: [PATCH 119/222] Update jenkinsfile --- jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 9bfe7d42e..a943bf73f 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -26,7 +26,8 @@ pipeline { } stage('deploy') { steps { - sh "docker stop psama && docker rm psama" + sh "docker stop psama || true" + sh "docker rm psama || true" sh """docker run --name=psama --restart always \ --network=picsure \ --env-file /usr/local/docker-config/psama/.env \ From 7211a41e199c0896fb7145fc1415d5439d66431b Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:22:03 -0400 Subject: [PATCH 120/222] Update jenkinsfile Add truststore_java_opts --- jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/jenkinsfile b/jenkinsfile index a943bf73f..ca274c457 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -15,6 +15,7 @@ pipeline { EMAIL_TEMPLATE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/emailTemplates:/opt/jboss/wildfly/standalone/configuration/emailTemplates " TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true $PROXY_OPTS" + TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" } stages { From 0dc2ea54911afad1e80cb7cd2d9e835340b6ae87 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:33:11 -0400 Subject: [PATCH 121/222] Update jenkinsfile Set proxy --- jenkinsfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jenkinsfile b/jenkinsfile index ca274c457..268a10e79 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -25,6 +25,15 @@ pipeline { sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" } } + stage('setProxy') { + steps { + sh """ + if [ -f "/usr/local/docker-config/setProxy.sh" ]; then + . /usr/local/docker-config/setProxy.sh + fi + """ + } + } stage('deploy') { steps { sh "docker stop psama || true" From 4e3ad2cf2bc1809b7a806f3f247dabad0cf26dac Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:36:29 -0400 Subject: [PATCH 122/222] Update jenkinsfile --- jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/jenkinsfile b/jenkinsfile index 268a10e79..99119b081 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -16,6 +16,7 @@ pipeline { TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true $PROXY_OPTS" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" + PROXY_OPTS="" } stages { From d61db7615df8dddc150ef171bb6661539fd88aa9 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:38:03 -0400 Subject: [PATCH 123/222] Update jenkinsfile --- jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index 99119b081..ee7b54498 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -14,7 +14,7 @@ pipeline { LATEST_TAG = "latest" EMAIL_TEMPLATE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/emailTemplates:/opt/jboss/wildfly/standalone/configuration/emailTemplates " TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" - PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true $PROXY_OPTS" + PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" PROXY_OPTS="" } @@ -44,7 +44,7 @@ pipeline { --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ - -e JAVA_OPTS="$PSAMA_OPTS $TRUSTSTORE_JAVA_OPTS" \ + -e JAVA_OPTS="$PSAMA_OPTS $PROXY_OPTS $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } From 9716812358d99e5c6e5f6ba88050c1a3f42f895c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 14:03:18 -0400 Subject: [PATCH 124/222] Add logging to debug --- .../harvard/hms/dbmi/avillach/auth/service/impl/UserService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 6db593538..a42103387 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -260,6 +260,7 @@ private void checkAssociation(List users) { @Transactional public ResponseEntity updateUser(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); + logger.info("Security context: {}", securityContext); CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); From 7014a2aa1b9d1d2b0136656c4408490224bd1bd2 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:15:52 -0400 Subject: [PATCH 125/222] Update jenkinsfile Refactor proxy opts --- jenkinsfile | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index ee7b54498..f4d3efa39 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -16,7 +16,7 @@ pipeline { TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" - PROXY_OPTS="" + PROXY_OPTS = sh(script: "if [ -f '/usr/local/docker-config/setProxy.sh' ]; then . /usr/local/docker-config/setProxy.sh; echo $proxyOpts; else echo ''; fi", returnStdout: true).trim() } stages { @@ -26,15 +26,6 @@ pipeline { sh "docker tag ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${LATEST_TAG}" } } - stage('setProxy') { - steps { - sh """ - if [ -f "/usr/local/docker-config/setProxy.sh" ]; then - . /usr/local/docker-config/setProxy.sh - fi - """ - } - } stage('deploy') { steps { sh "docker stop psama || true" From c96e944a00494af3a722a1346d0148f16e42f4ec Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:19:58 -0400 Subject: [PATCH 126/222] Update jenkinsfile --- jenkinsfile | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index f4d3efa39..77f87be22 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -16,10 +16,28 @@ pipeline { TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" - PROXY_OPTS = sh(script: "if [ -f '/usr/local/docker-config/setProxy.sh' ]; then . /usr/local/docker-config/setProxy.sh; echo $proxyOpts; else echo ''; fi", returnStdout: true).trim() + PROXY_OPTS = "" } stages { + stage('setProxy') { + steps { + script { + // Attempt to load proxy settings from an external script + if (fileExists('/usr/local/docker-config/setProxy.sh')) { + env.PROXY_OPTS = sh(script: '. /usr/local/docker-config/setProxy.sh && echo "-Dhttp.proxyHost=$proxyServer -Dhttp.proxyPort=$proxyPort -Dhttps.proxyHost=$proxyServer -Dhttps.proxyPort=$proxyPort -Dhttp.nonProxyHosts=\'$noProxy\'"', returnStdout: true).trim() + } + + // Check if PROXY_OPTS is null or empty and reset it if necessary + if (env.PROXY_OPTS == null || env.PROXY_OPTS.isEmpty()) { + env.PROXY_OPTS = "" + echo "Proxy options are unset or empty, defaulting to no proxy settings." + } else { + echo "Proxy configuration loaded: ${env.PROXY_OPTS}" + } + } + } + } stage('build') { steps { sh "docker build ${DOCKER_BUILD_ARGS} -t ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ." From 46f114dbfd6e3dd84c79e96e039cf0ddc7b0ff9b Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:22:33 -0400 Subject: [PATCH 127/222] Update jenkinsfile --- jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index 77f87be22..aade0ba68 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -25,10 +25,9 @@ pipeline { script { // Attempt to load proxy settings from an external script if (fileExists('/usr/local/docker-config/setProxy.sh')) { - env.PROXY_OPTS = sh(script: '. /usr/local/docker-config/setProxy.sh && echo "-Dhttp.proxyHost=$proxyServer -Dhttp.proxyPort=$proxyPort -Dhttps.proxyHost=$proxyServer -Dhttps.proxyPort=$proxyPort -Dhttp.nonProxyHosts=\'$noProxy\'"', returnStdout: true).trim() + env.PROXY_OPTS = sh(script: '. /usr/local/docker-config/setProxy.sh; echo "-Dhttp.proxyHost=\$proxyServer -Dhttp.proxyPort=\$proxyPort -Dhttps.proxyHost=\$proxyServer -Dhttps.proxyPort=\$proxyPort -Dhttp.nonProxyHosts=\'\$noProxy\'"', returnStdout: true).trim() } - // Check if PROXY_OPTS is null or empty and reset it if necessary if (env.PROXY_OPTS == null || env.PROXY_OPTS.isEmpty()) { env.PROXY_OPTS = "" echo "Proxy options are unset or empty, defaulting to no proxy settings." From 331b39b736c53aee0124c6c8dddf87b41ace17f4 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:26:10 -0400 Subject: [PATCH 128/222] Update jenkinsfile --- jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/jenkinsfile b/jenkinsfile index aade0ba68..bc127a267 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -45,6 +45,7 @@ pipeline { } stage('deploy') { steps { + echo env.PROXY_OPTS sh "docker stop psama || true" sh "docker rm psama || true" sh """docker run --name=psama --restart always \ From 87802ecab938e22d89b3418e36431fef3ae64549 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:26:29 -0400 Subject: [PATCH 129/222] Update jenkinsfile --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index bc127a267..7ec87d735 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -45,7 +45,7 @@ pipeline { } stage('deploy') { steps { - echo env.PROXY_OPTS + echo "${env.PROXY_OPTS}" sh "docker stop psama || true" sh "docker rm psama || true" sh """docker run --name=psama --restart always \ From e445e298cc4a98b6eaad0c946924cac70742c813 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:29:32 -0400 Subject: [PATCH 130/222] Update jenkinsfile --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 7ec87d735..31e914a98 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -53,7 +53,7 @@ pipeline { --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ - -e JAVA_OPTS="$PSAMA_OPTS $PROXY_OPTS $TRUSTSTORE_JAVA_OPTS" \ + -e JAVA_OPTS="$PSAMA_OPTS ${env.PROXY_OPTS:-''} $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } From 5ed01b45e49575bff2ec8d1711e8cd729f055dff Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:31:05 -0400 Subject: [PATCH 131/222] Update jenkinsfile --- jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index 31e914a98..1bf8d915d 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -45,7 +45,7 @@ pipeline { } stage('deploy') { steps { - echo "${env.PROXY_OPTS}" + env.PROXY_OPTS = env.PROXY_OPTS ?: '' sh "docker stop psama || true" sh "docker rm psama || true" sh """docker run --name=psama --restart always \ @@ -53,7 +53,7 @@ pipeline { --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ - -e JAVA_OPTS="$PSAMA_OPTS ${env.PROXY_OPTS:-''} $TRUSTSTORE_JAVA_OPTS" \ + -e JAVA_OPTS="$PSAMA_OPTS ${env.PROXY_OPTS} $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } From 13a1e5ffbaccc5d1e3cc3ec8561aa82742e7ef46 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:33:13 -0400 Subject: [PATCH 132/222] Update jenkinsfile --- jenkinsfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 1bf8d915d..78dc5eb25 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -45,7 +45,9 @@ pipeline { } stage('deploy') { steps { - env.PROXY_OPTS = env.PROXY_OPTS ?: '' + script { + env.PROXY_OPTS = env.PROXY_OPTS ?: '' + } sh "docker stop psama || true" sh "docker rm psama || true" sh """docker run --name=psama --restart always \ From 0e9f56f75dd6bf36b996654b9e030e3406390953 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:34:18 -0400 Subject: [PATCH 133/222] Update jenkinsfile --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 78dc5eb25..067d7a338 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -46,7 +46,7 @@ pipeline { stage('deploy') { steps { script { - env.PROXY_OPTS = env.PROXY_OPTS ?: '' + env.PROXY_OPTS = env.PROXY_OPTS == null ?: '' } sh "docker stop psama || true" sh "docker rm psama || true" From a0177d724d0cd20379833c15e91e52546740d9e7 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:36:11 -0400 Subject: [PATCH 134/222] Update jenkinsfile --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 067d7a338..cb0598cbe 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -55,7 +55,7 @@ pipeline { --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ - -e JAVA_OPTS="$PSAMA_OPTS ${env.PROXY_OPTS} $TRUSTSTORE_JAVA_OPTS" \ + -e JAVA_OPTS="$PSAMA_OPTS $PROXY_OPTS $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } From 8d3704b753332ce90c1ce754abff5f5f0739ec74 Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:37:16 -0400 Subject: [PATCH 135/222] Update jenkinsfile --- jenkinsfile | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/jenkinsfile b/jenkinsfile index cb0598cbe..9b18eb517 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -16,27 +16,9 @@ pipeline { TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" - PROXY_OPTS = "" } stages { - stage('setProxy') { - steps { - script { - // Attempt to load proxy settings from an external script - if (fileExists('/usr/local/docker-config/setProxy.sh')) { - env.PROXY_OPTS = sh(script: '. /usr/local/docker-config/setProxy.sh; echo "-Dhttp.proxyHost=\$proxyServer -Dhttp.proxyPort=\$proxyPort -Dhttps.proxyHost=\$proxyServer -Dhttps.proxyPort=\$proxyPort -Dhttp.nonProxyHosts=\'\$noProxy\'"', returnStdout: true).trim() - } - - if (env.PROXY_OPTS == null || env.PROXY_OPTS.isEmpty()) { - env.PROXY_OPTS = "" - echo "Proxy options are unset or empty, defaulting to no proxy settings." - } else { - echo "Proxy configuration loaded: ${env.PROXY_OPTS}" - } - } - } - } stage('build') { steps { sh "docker build ${DOCKER_BUILD_ARGS} -t ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG} ." @@ -55,7 +37,7 @@ pipeline { --env-file /usr/local/docker-config/psama/.env \ $EMAIL_TEMPLATE_VOLUME \ $TRUSTSTORE_VOLUME \ - -e JAVA_OPTS="$PSAMA_OPTS $PROXY_OPTS $TRUSTSTORE_JAVA_OPTS" \ + -e JAVA_OPTS="$PSAMA_OPTS $TRUSTSTORE_JAVA_OPTS" \ -d ${params.DOCKER_REGISTRY}/${params.REPOSITORY_NAME}:${IMAGE_TAG}""" } } From 59d77841154a6f7aed103ba391f9778746799c11 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 25 Apr 2024 14:42:12 -0400 Subject: [PATCH 136/222] Update logger to display user principal --- .../hms/dbmi/avillach/auth/service/impl/UserService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index a42103387..87f879369 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -260,7 +260,7 @@ private void checkAssociation(List users) { @Transactional public ResponseEntity updateUser(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); - logger.info("Security context: {}", securityContext); + logger.info("Security context: {}", securityContext.getAuthentication().getPrincipal()); CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); From f82397a6f4be775d6f480575e8e7ab5a0b71436e Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 10:19:02 -0400 Subject: [PATCH 137/222] Update user saving method and enhance logger output --- .../auth/service/impl/OauthUserMatchingService.java | 3 +-- .../hms/dbmi/avillach/auth/service/impl/UserService.java | 9 +++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java index 581465f05..7cc63b3c8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -110,8 +110,7 @@ public User matchTokenToUser(JsonNode userInfo) { u.setAuth0metadata(userInfoString); u.setMatched(true); u.setSubject(userId); - userService.updateUser(Arrays.asList(u)); - return u; + return userService.save(u); } } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 87f879369..b93b8d0dc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -219,6 +219,7 @@ public ResponseEntity addUsers(List users) { if (user.getEmail() == null) { try { + logger.info("Parsing metadata for email address"); HashMap metadata = new HashMap(new ObjectMapper().readValue(user.getGeneralMetadata(), Map.class)); List emailKeys = metadata.keySet().stream().filter((key) -> key.toLowerCase().contains("email")).toList(); if (!emailKeys.isEmpty()) { @@ -230,7 +231,8 @@ public ResponseEntity addUsers(List users) { } } - ResponseEntity updateResponse = PICSUREResponse.success(addUser(users)); + users = addUser(users); + ResponseEntity updateResponse = PICSUREResponse.success(users); sendUserUpdateEmailsFromResponse(updateResponse); return updateResponse; } @@ -271,7 +273,6 @@ public ResponseEntity updateUser(List users) { checkAssociation(users); boolean allowUpdate = true; for (User user : users) { - Optional originalUser = this.userRepository.findById(user.getUuid()); if (!allowUpdateSuperAdminRole(currentUser, user, originalUser.orElse(null))) { allowUpdate = false; @@ -506,4 +507,8 @@ public void changeRole(User currentUser, Set roles) { public User findBySubject(String username) { return this.userRepository.findBySubject(username); } + + public User save(User user) { + return this.userRepository.save(user); + } } From b597764b261bca302abdd2ede3755b60e7d38c05 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 10:24:56 -0400 Subject: [PATCH 138/222] Reduce PSAMA_OPTS memory allocation --- jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile b/jenkinsfile index 9b18eb517..68e0d48d5 100644 --- a/jenkinsfile +++ b/jenkinsfile @@ -14,7 +14,7 @@ pipeline { LATEST_TAG = "latest" EMAIL_TEMPLATE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/emailTemplates:/opt/jboss/wildfly/standalone/configuration/emailTemplates " TRUSTSTORE_VOLUME="-v $DOCKER_CONFIG_DIR/wildfly/application.truststore:/opt/jboss/wildfly/standalone/configuration/application.truststore" - PSAMA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" + PSAMA_OPTS="-Xms1g -Xmx2g -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" TRUSTSTORE_JAVA_OPTS="-Djavax.net.ssl.trustStore=/opt/jboss/wildfly/standalone/configuration/application.truststore -Djavax.net.ssl.trustStorePassword=password" } From bcc650ff12653810b6938b53c41d62cc38a11e02 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 15:26:42 -0400 Subject: [PATCH 139/222] Add @RequestBody annotation in ApplicationController --- .../hms/dbmi/avillach/auth/rest/ApplicationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index c07581fec..3b31bb8a1 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -60,7 +60,7 @@ public ResponseEntity getApplicationAll() { @PostMapping(value = "/", consumes = "application/json", produces = "application/json") public ResponseEntity addApplication( @Parameter(required = true, description = "A list of AccessRule in JSON format") - List applications) { + @RequestBody List applications) { applications = applicationService.addNewApplications(applications); return PICSUREResponse.success(applications); } From 2995069398d8c6ff8c43cccc85b90905a0cffcab Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 15:32:28 -0400 Subject: [PATCH 140/222] Update @PostMapping in ApplicationController Removed the value attribute from @PostMapping annotation in the ApplicationController. This simplifies the code and no functionality change as the default mapping url ("/") is used. --- .../hms/dbmi/avillach/auth/rest/ApplicationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index 3b31bb8a1..a063f6588 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -57,7 +57,7 @@ public ResponseEntity getApplicationAll() { @Operation(description = "POST a list of Applications, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PostMapping(value = "/", consumes = "application/json", produces = "application/json") + @PostMapping(consumes = "application/json", produces = "application/json") public ResponseEntity addApplication( @Parameter(required = true, description = "A list of AccessRule in JSON format") @RequestBody List applications) { From 5fe74e45e957eface3c5eda66626b2372feff422 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 15:35:10 -0400 Subject: [PATCH 141/222] Remove redundant value attribute in PutMapping The value attribute which has already been set to default ("/") in the PutMapping annotation of the updateApplication method, was removed. This change simplifies the code and removes unnecessary redundancy. --- .../hms/dbmi/avillach/auth/rest/ApplicationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index a063f6588..53f62f428 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -67,7 +67,7 @@ public ResponseEntity addApplication( @Operation(description = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PutMapping(value = "/", consumes = "application/json", produces = "application/json") + @PutMapping(consumes = "application/json", produces = "application/json") public ResponseEntity updateApplication( @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") List applications) { From 6ae58b1f9cc44ed954cc000c332cffe34d971bd0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 15:41:05 -0400 Subject: [PATCH 142/222] Add @RequestBody annotation to updateApplication method The "@RequestBody" annotation was added to the "updateApplication" method in ApplicationController.java. This allows the method to correctly handle incoming JSON in the request body by parsing it into a list of "Application" objects. --- .../hms/dbmi/avillach/auth/rest/ApplicationController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index 53f62f428..2be7e41f8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -70,7 +70,7 @@ public ResponseEntity addApplication( @PutMapping(consumes = "application/json", produces = "application/json") public ResponseEntity updateApplication( @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") - List applications) { + @RequestBody List applications) { applications = applicationService.updateApplications(applications); return PICSUREResponse.success(applications); } From c5699f8bcaa70fa9e63f2c4e6b2bc70d33d8d6f7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 26 Apr 2024 15:58:52 -0400 Subject: [PATCH 143/222] Update method of setting privileges in ApplicationService Instead of clearing and re-adding privileges for an application, the code now just updates the existing privileges. This change is not only more efficient but also beneficial for Hibernate persistence. --- .../dbmi/avillach/auth/service/impl/ApplicationService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index eeee190ad..ee0d61aec 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -137,8 +137,12 @@ private void checkAssociation(List applications) { logger.error("Didn't find privilege by uuid: {}", p.getUuid()); } }); - application.setPrivileges(privileges); + // Instead of clearing the privileges and adding the new ones, we should just update the privileges + // this is for the benefit of hibernate as well. Hibernate is tracking the original privileges object + // and if we clear it and add new ones, hibernate will think that the privileges object has been changed + application.getPrivileges().clear(); + application.getPrivileges().addAll(privileges); } } } From dedb8b83533b5d196bbb4ab6e52e313b703487c7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Sat, 27 Apr 2024 10:00:35 -0400 Subject: [PATCH 144/222] Add equals and hashCode methods to Application class The equals and hashCode methods have been added to the Application class as part of the Java Object class contract. These methods allow for comparison and storage of Application objects based on their attributes. --- .../dbmi/avillach/auth/entity/Application.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 38185fc10..788a64eab 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -4,6 +4,7 @@ import jakarta.persistence.*; import java.security.Principal; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -133,4 +134,18 @@ public ApplicationForDisplay setUrl(String url) { public String toString() { return uuid.toString() + " ___ " + name + " ___ " + description + " ___ " + enable + " ___ " + url + " ___ " + (privileges==null?"NO PRIVILEGES DEFINED" : privileges.stream().map(Privilege::toString).collect(Collectors.joining(","))); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + Application that = (Application) o; + return isEnable() == that.isEnable() && Objects.equals(getName(), that.getName()) && Objects.equals(getDescription(), that.getDescription()) && Objects.equals(getToken(), that.getToken()) && Objects.equals(getUrl(), that.getUrl()) && Objects.equals(getPrivileges(), that.getPrivileges()); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getName(), getDescription(), getToken(), getUrl(), isEnable(), getPrivileges()); + } } From e7aee6f7bcd75ace3fdc3994d02aebf1d1ede623 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Sat, 27 Apr 2024 10:00:54 -0400 Subject: [PATCH 145/222] Rollback code --- .../dbmi/avillach/auth/service/impl/ApplicationService.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index ee0d61aec..8ad9597ca 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -138,11 +138,7 @@ private void checkAssociation(List applications) { } }); - // Instead of clearing the privileges and adding the new ones, we should just update the privileges - // this is for the benefit of hibernate as well. Hibernate is tracking the original privileges object - // and if we clear it and add new ones, hibernate will think that the privileges object has been changed - application.getPrivileges().clear(); - application.getPrivileges().addAll(privileges); + application.setPrivileges(privileges); } } } From 83b0f0e04521cd7d857db6108de9004f533bdc79 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Sat, 27 Apr 2024 10:13:02 -0400 Subject: [PATCH 146/222] Update setPrivileges method in Application class The setPrivileges method within the Application class has been updated to first clear the existing privileges before adding new ones. This ensures that any old privileges are removed, providing accurate and updated privilege settings. --- .../edu/harvard/hms/dbmi/avillach/auth/entity/Application.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 788a64eab..09b025943 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -63,7 +63,8 @@ public Set getPrivileges() { } public void setPrivileges(Set privileges) { - this.privileges = privileges; + this.privileges.clear(); + this.privileges.addAll(privileges); } public String getUrl() { From f699343cb69f2e2774111b5b4b806f094515bdd1 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Sat, 27 Apr 2024 10:16:09 -0400 Subject: [PATCH 147/222] Add @Transactional annotation to updateApplications method --- .../hms/dbmi/avillach/auth/service/impl/ApplicationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 8ad9597ca..7901e9df0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -96,6 +96,7 @@ public List deleteApplicationById(String applicationId) { return this.applicationRepo.findAll(); } + @Transactional public List updateApplications(List applications) { checkAssociation(applications); return this.applicationRepo.saveAll(applications); From 66a9c07305bc551a226597654615363c8337531f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 08:56:10 -0400 Subject: [PATCH 148/222] Remove @Transactional annotation in ApplicationService The @Transactional annotation was removed from the updateApplications method in ApplicationService. This change was made because the annotation was unnecessary in this context and its removal will not affect the functionality of the updateApplications method. --- .../hms/dbmi/avillach/auth/service/impl/ApplicationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 7901e9df0..8ad9597ca 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -96,7 +96,6 @@ public List deleteApplicationById(String applicationId) { return this.applicationRepo.findAll(); } - @Transactional public List updateApplications(List applications) { checkAssociation(applications); return this.applicationRepo.saveAll(applications); From f862d768dff6e9c31861d18e6974a8c1d2b79ec3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 08:58:11 -0400 Subject: [PATCH 149/222] Remove orphanRemoval attribute from OneToMany relationship The orphanRemoval attribute, initially set to true in the OneToMany relationship between Application and Privilege entities, has been removed. This change means that any Privilege that gets disassociated from an Application will not automatically be deleted from the database. --- .../edu/harvard/hms/dbmi/avillach/auth/entity/Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 09b025943..292dff2d8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -22,7 +22,7 @@ public class Application extends BaseEntity implements Principal { private String url; private boolean enable = true; - @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "application_id", referencedColumnName = "uuid") private Set privileges; From 3b0ab0aa0ac9a3730fe9e0e720b16fe3dce63ebb Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:01:31 -0400 Subject: [PATCH 150/222] Add @Transactional annotation to updateApplications method --- .../hms/dbmi/avillach/auth/service/impl/ApplicationService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 8ad9597ca..7901e9df0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -96,6 +96,7 @@ public List deleteApplicationById(String applicationId) { return this.applicationRepo.findAll(); } + @Transactional public List updateApplications(List applications) { checkAssociation(applications); return this.applicationRepo.saveAll(applications); From 12b28d29439e44dbf1f3b219a7aa96a1ab83f5f7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:02:06 -0400 Subject: [PATCH 151/222] Refactor Annotation parameters in REST controllers It simplified the routing annotations in multiple REST controllers, removing the redundant 'path = "/"' parameter where it was used. Also, for methods expecting body content, emphasized using '@RequestBody' annotation. These changes aimed to improve readability, reduce redundancy, and clarify the controller methods' expectations. --- .../dbmi/avillach/auth/rest/AccessRuleController.java | 8 ++++---- .../avillach/auth/rest/ConnectionWebController.java | 6 +++--- .../dbmi/avillach/auth/rest/PrivilegeController.java | 8 ++++---- .../avillach/auth/rest/TermsOfServiceController.java | 2 +- .../auth/rest/UserMetadataMappingWebController.java | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index 9be2fddbc..6343f4b6a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -61,10 +61,10 @@ public ResponseEntity getAccessRuleAll() { @Operation(description = "POST a list of AccessRules, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) - @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity addAccessRule( @Parameter(required = true, description = "A list of AccessRule in JSON format") - List accessRules) { + @RequestBody List accessRules) { accessRules = this.accessRuleService.addAccessRule(accessRules); if (accessRules.isEmpty()) { @@ -76,10 +76,10 @@ public ResponseEntity addAccessRule( @Operation(description = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) - @PutMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity updateAccessRule( @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") - List accessRules) { + @RequestBody List accessRules) { accessRules = this.accessRuleService.updateAccessRules(accessRules); return PICSUREResponse.success(accessRules); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index 2766bbf4f..eef116a15 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -49,7 +49,7 @@ public ResponseEntity getConnectionById( } @Operation(description = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") - @GetMapping(path = "/", produces = "application/json") + @GetMapping(produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getAllConnections() { List allConnections = connectionWebService.getAllConnections(); @@ -61,7 +61,7 @@ public ResponseEntity getAllConnections() { @PostMapping(produces = "application/json", consumes = "application/json") public ResponseEntity addConnection( @Parameter(required = true, description = "A list of Connections in JSON format") - List connections) { + @RequestBody List connections) { try { connections = connectionWebService.addConnection(connections); } catch (IllegalArgumentException e) { @@ -76,7 +76,7 @@ public ResponseEntity addConnection( @PutMapping(produces = "application/json", consumes = "application/json") public ResponseEntity updateConnection( @Parameter(required = true, description = "A list of Connection with fields to be updated in JSON format") - List connections) { + @RequestBody List connections) { List responseEntity = connectionWebService.updateConnections(connections); return ResponseEntity.ok(responseEntity); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index 4390b8809..31c58e7cf 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -57,20 +57,20 @@ public ResponseEntity getPrivilegeAll() { @Operation(description = "POST a list of privileges, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PostMapping(path = "/", consumes = "application/json", produces = "application/json") + @PostMapping(consumes = "application/json", produces = "application/json") public ResponseEntity addPrivilege( @Parameter(required = true, description = "A list of privileges in JSON format") - List privileges){ + @RequestBody List privileges){ privileges = this.privilegeService.addPrivileges(privileges); return PICSUREResponse.success(privileges); } @Operation(description = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PutMapping(path = "/", consumes = "application/json", produces = "application/json") + @PutMapping(consumes = "application/json", produces = "application/json") public ResponseEntity updatePrivilege( @Parameter(required = true, description = "A list of privilege with fields to be updated in JSON format") - List privileges){ + @RequestBody List privileges){ privileges = this.privilegeService.updatePrivileges(privileges); return ResponseEntity.ok(privileges); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java index 7126ea25b..a938298a7 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java @@ -64,7 +64,7 @@ public ResponseEntity updateTermsOfService( } @Operation(description = "GET if current user has acceptted his TOS or not") - @GetMapping(path = "/", produces = "text/plain") + @GetMapping(produces = "text/plain") public ResponseEntity hasUserAcceptedTOS(){ SecurityContext context = SecurityContextHolder.getContext(); String userSubject = context.getAuthentication().getName(); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index f6fb42a50..18f53f55c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -42,7 +42,7 @@ public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") @Operation(description = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) - @GetMapping(path = "/", produces = "application/json") + @GetMapping(produces = "application/json") public ResponseEntity getAllMappings() { List allMappings = mappingService.getAllMappings(); return PICSUREResponse.success(allMappings); @@ -50,10 +50,10 @@ public ResponseEntity getAllMappings() { @Operation(description = "POST a list of UserMetadataMappings, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PostMapping(path = "/", consumes = "application/json", produces = "application/json") + @PostMapping(consumes = "application/json", produces = "application/json") public ResponseEntity addMapping( @Parameter(required = true, description = "A list of UserMetadataMapping in JSON format") - List mappings) { + @RequestBody List mappings) { try { List userMetadataMappings = mappingService.addMappings(mappings); @@ -65,10 +65,10 @@ public ResponseEntity addMapping( @Operation(description = "Update a list of UserMetadataMappings, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) - @PutMapping(path = "/", consumes = "application/json", produces = "application/json") + @PutMapping(consumes = "application/json", produces = "application/json") public ResponseEntity updateMapping( @Parameter(required = true, description = "A list of UserMetadataMapping with fields to be updated in JSON format") - List mappings) { + @RequestBody List mappings) { List userMetadataMappings = this.mappingService.updateUserMetadataMappings(mappings); if (userMetadataMappings == null || userMetadataMappings.isEmpty()) { From 9bb2d24328a71c34063a98a43d5c2e1e03708564 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:02:22 -0400 Subject: [PATCH 152/222] Remove unused dependencies from pom.xml The commit removes commented-out dependencies from the pom.xml file for the pic-sure-auth-services. The removed dependencies include mysql's connector for Java and jjwt by io.jsonwebtoken, which is not being used in the current version of the application. --- pic-sure-auth-services/pom.xml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index cafca3824..432a8d5d4 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -81,17 +81,6 @@ mysql-connector-j 8.2.0 - - - - - - - - - - - io.jsonwebtoken From 5b0f662df0278db799cbe7b5fbda97aa1cb2b834 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:08:08 -0400 Subject: [PATCH 153/222] Update GetMapping annotation in ConnectionWebController The GetMapping annotation in the ConnectionWebController class has been modified to explicitly specify a mapping value. This alteration ensures a more precise endpoint mapping, enhancing the accuracy of the RESTful web service operation. --- .../hms/dbmi/avillach/auth/rest/ConnectionWebController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index eef116a15..b48cdc109 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -49,7 +49,7 @@ public ResponseEntity getConnectionById( } @Operation(description = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") - @GetMapping(produces = "application/json") + @GetMapping(value = "/", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) public ResponseEntity getAllConnections() { List allConnections = connectionWebService.getAllConnections(); From aa85b22e0e0cf5f7832fba473ee5bdfb11e29ec5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:08:35 -0400 Subject: [PATCH 154/222] Remove redundant comment in ConnectionWebController --- .../hms/dbmi/avillach/auth/rest/ConnectionWebController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index b48cdc109..579440d1b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -24,7 +24,7 @@ @Tag(name = "Connection Management") @Controller @RequestMapping("/connection") -public class ConnectionWebController { // TODO: This isn't a service class, it's a controller. We should refactor this so it doesn't extend BaseEntityService +public class ConnectionWebController { private final ConnectionWebService connectionWebService; From 2a0fe24f9fbab024e54471dcad6024c5189967f2 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 09:16:09 -0400 Subject: [PATCH 155/222] Add @RequestBody annotation to RoleController methods For the addRole and updateRole methods in RoleController.java, the @RequestBody annotation was added to the roles parameter. This enables these methods to correctly handle JSON input in the HTTP request's body. --- .../harvard/hms/dbmi/avillach/auth/rest/RoleController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index 825b3b1b1..98a6aa66b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -57,7 +57,7 @@ public ResponseEntity getRoleAll() { @PostMapping(produces = "application/json") public ResponseEntity addRole( @Parameter(required = true, description = "A list of Roles in JSON format") - List roles) { + @RequestBody List roles) { List savedRoles = this.roleService.addRoles(roles); return PICSUREResponse.success("All roles are added.", savedRoles); } @@ -67,7 +67,7 @@ public ResponseEntity addRole( @PutMapping(produces = "application/json") public ResponseEntity updateRole( @Parameter(required = true, description = "A list of Roles with fields to be updated in JSON format") - List roles) { + @RequestBody List roles) { List updatedRoles = this.roleService.updateRoles(roles); if (updatedRoles.isEmpty()) { return PICSUREResponse.protocolError("No Role(s) has been updated."); From b5b8c8684db869890a2dfb1f91b54cb52000a24c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 10:00:44 -0400 Subject: [PATCH 156/222] Refactor TOSService to use constructor injection --- .../hms/dbmi/avillach/auth/service/impl/TOSService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index fad32000c..c69a5c872 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -25,8 +25,8 @@ public class TOSService { private final static Logger logger = LoggerFactory.getLogger(TOSService.class); - @Value("${application.tos.enabled}") - private boolean isToSEnabled; + + private final boolean isToSEnabled; private final TermsOfServiceRepository termsOfServiceRepo; @@ -34,9 +34,11 @@ public class TOSService { @Autowired - public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo) { + public TOSService(TermsOfServiceRepository termsOfServiceRepo, UserRepository userRepo, + @Value("${application.tos.enabled}") boolean isToSEnabled) { this.termsOfServiceRepo = termsOfServiceRepo; this.userRepo = userRepo; + this.isToSEnabled = isToSEnabled; } From 3346f63ef843afa51c41312bc17ff9b328be80ef Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 11:27:36 -0400 Subject: [PATCH 157/222] Update annotation in AccessRuleController --- .../hms/dbmi/avillach/auth/rest/AccessRuleController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index 6343f4b6a..5336b33df 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -26,7 +26,8 @@ * Path: /accessRule */ @Tag(name = "Access Rule Management") -@Controller("/accessRule") +@Controller +@RequestMapping(value = "/accessRule") public class AccessRuleController { private final AccessRuleService accessRuleService; From 9895a127d103e0e351d7570aa353e902d9f1038a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:33:46 -0400 Subject: [PATCH 158/222] Refactor role names in AuthNaming class The role names within the AuthRoleNaming class have been refactored to use SecurityRoles enum values instead of hardcoded strings. This change ensures that the role names are consistent and centrally managed across the codebase. --- .../harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java index 37124eb62..bd60e8325 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java @@ -1,5 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; + import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -17,8 +19,8 @@ public class AuthNaming { *

Note: Only users with super admin access can edit these role names.

*/ public static class AuthRoleNaming { - public static final String ADMIN = "ADMIN"; - public static final String SUPER_ADMIN = "SUPER_ADMIN"; + public static final String ADMIN = SecurityRoles.ADMIN.getRole(); + public static final String SUPER_ADMIN = SecurityRoles.SUPER_ADMIN.getRole(); public static List allRoles(){ List roles = new ArrayList<>(); From 534a1f1eff4b48b9c2a951a690820c0f8aff1b8f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:37:37 -0400 Subject: [PATCH 159/222] Remove unnecessary Json exceptions in RestClientUtil The JsonMappingException and JsonParseException throws were excessive and redundant in the RestClientUtil class. The implementation has been simplified by removing these exceptions, making error handling more streamlined. --- .../harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index b067a56f9..ab6527592 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -74,8 +74,6 @@ public static T readObjectFromResponse(ResponseEntity response, Clas try { return objectMapper.readValue(response.getBody(), objectMapper.getTypeFactory().constructType(expectedElementType)); - } catch (JsonMappingException | JsonParseException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } From ce0d92c5ece11b0dd25c2d052053eae7f330aa08 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:39:48 -0400 Subject: [PATCH 160/222] Refactor JWTUtil class to use final values The code change removes the use of @Value annotations in favor of final variable declarations for "clientSecret" and "clientSecretIsBase64". This change improves the code stability by ensuring that these variables, once assigned, can't be modified. --- .../edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index 35f6b2f21..ae9244093 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -30,11 +30,9 @@ public class JWTUtil { private static final long defaultTTLMillis = 1000L * 60 * 60 * 24 * 7; - @Value("${application.client.secret}") - private String clientSecret; + private final String clientSecret; - @Value("${application.client.secret.base64}") - private boolean clientSecretIsBase64; + private final boolean clientSecretIsBase64; public JWTUtil(@Value("${application.client.secret}") String clientSecret, @Value("${application.client.secret.base64}") boolean clientSecretIsBase64) { From 8a450276a7c3ff2ee2bcdefb6e627db53f702e97 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:39:56 -0400 Subject: [PATCH 161/222] Remove unused imports in RestClientUtil.java The commit removes the JsonParseException and JsonMappingException imports from RestClientUtil.java file. These imports were detected as unused in the code, thus, making it more efficient and clean. --- .../harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index ab6527592..30729dabc 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -1,7 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 58cb1540cb62885fdef8fc8eb4d313ec18829bf2 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:52:50 -0400 Subject: [PATCH 162/222] Remove equals and hashCode methods from Application class The 'equals' and 'hashCode' methods in the Application class have been removed. They were found to be unnecessary as the superclass methods were sufficient for the class's requirements, thus improving code simplicity and readability. The removal does not impact the existing functionalities. --- .../hms/dbmi/avillach/auth/entity/Application.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 292dff2d8..8c0c3dae0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -136,17 +136,4 @@ public String toString() { return uuid.toString() + " ___ " + name + " ___ " + description + " ___ " + enable + " ___ " + url + " ___ " + (privileges==null?"NO PRIVILEGES DEFINED" : privileges.stream().map(Privilege::toString).collect(Collectors.joining(","))); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - Application that = (Application) o; - return isEnable() == that.isEnable() && Objects.equals(getName(), that.getName()) && Objects.equals(getDescription(), that.getDescription()) && Objects.equals(getToken(), that.getToken()) && Objects.equals(getUrl(), that.getUrl()) && Objects.equals(getPrivileges(), that.getPrivileges()); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), getName(), getDescription(), getToken(), getUrl(), isEnable(), getPrivileges()); - } } From 8dafc00d8e83432de72d8001ce31f0846c67c666 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 12:57:52 -0400 Subject: [PATCH 163/222] Remove unused backup files in auth services --- pic-sure-auth-services/bak/Dockerfile | 40 -- .../bak/JAXRSConfiguration.java | 374 ------------ pic-sure-auth-services/bak/README.md | 1 - pic-sure-auth-services/bak/persistence.xml | 17 - pic-sure-auth-services/bak/standalone.xml | 572 ------------------ 5 files changed, 1004 deletions(-) delete mode 100644 pic-sure-auth-services/bak/Dockerfile delete mode 100755 pic-sure-auth-services/bak/JAXRSConfiguration.java delete mode 100644 pic-sure-auth-services/bak/README.md delete mode 100644 pic-sure-auth-services/bak/persistence.xml delete mode 100644 pic-sure-auth-services/bak/standalone.xml diff --git a/pic-sure-auth-services/bak/Dockerfile b/pic-sure-auth-services/bak/Dockerfile deleted file mode 100644 index aa603a904..000000000 --- a/pic-sure-auth-services/bak/Dockerfile +++ /dev/null @@ -1,40 +0,0 @@ -FROM jboss/wildfly:17.0.0.Final AS production - -# TODO: It'd be nice if wildfly used alpine -Andre - -LABEL maintainer="avillach_lab_developers@googlegroups.com" - -# mysql database -ENV AUTH_DB_CONNECTION_USER root -ENV AUTH_MYSQLADDRESS localhost -ENV AUTH_DB_PORT 3306 -ENV AUTH_MYSQLPASS password - -# JWT Token -ENV AUTH_CLIENT_SECRET secret -ENV AUTH_USER_ID_CLAIM sub - -# root required to create default scratch directory - Andre -USER root - -# Add certs for AWS to trust store -COPY ../aws_certs/certificate.der certificate.der -RUN keytool -noprompt -import -alias root_cert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -file certificate.der - -# copy modules -COPY target/modules/system/layers/base/com/sql/mysql/main/* /modules/ -COPY ../src/main/resources/emailTemplates/accessEmail.mustache /usr/local/share/applications/ - -# install modules -RUN wildfly/bin/jboss-cli.sh --command="module add --name=com.sql.mysql \ - --resources=/modules/mysql-connector-java-5.1.38.jar --dependencies=javax.api" - -# Copy standalone.xml -COPY standalone.xml wildfly/standalone/configuration/ - -# Copy war file -COPY ../target/pic-sure-auth-services.war wildfly/standalone/deployments/pic-sure-auth-services.war - -#USER jboss - -ENTRYPOINT ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"] diff --git a/pic-sure-auth-services/bak/JAXRSConfiguration.java b/pic-sure-auth-services/bak/JAXRSConfiguration.java deleted file mode 100755 index 5e6eecd29..000000000 --- a/pic-sure-auth-services/bak/JAXRSConfiguration.java +++ /dev/null @@ -1,374 +0,0 @@ -package edu.harvard.hms.dbmi.avillach.auth; - - -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; -import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; -import edu.harvard.hms.dbmi.avillach.auth.entity.Role; -import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; -import edu.harvard.hms.dbmi.avillach.auth.rest.TokenService; -import io.swagger.jaxrs.config.BeanConfig; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import javax.ejb.Startup; -import javax.inject.Inject; -import javax.mail.Session; -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.ws.rs.ApplicationPath; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.SecurityContext; -import java.util.*; - -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; -import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.SUPER_ADMIN; - -/** - *

When you deploy the PSAMA application WAR file to a new server, this class is called to supply basic configuration information.

- */ -@Startup -@ApplicationPath("auth") -public class JAXRSConfiguration extends Application { - - private Logger logger = LoggerFactory.getLogger(JAXRSConfiguration.class); - - @Resource(mappedName = "java:global/client_id") - public static String clientId; - @Resource(mappedName = "java:global/client_secret") - public static String clientSecret; - @Resource(mappedName = "java:global/clientSecretIsBase64") - public static String clientSecretIsBase64; - - @Resource(mappedName = "java:global/user_id_claim") - public static String userIdClaim; - - @Resource(mappedName = "java:global/auth0host") - public static String auth0host; - - @Resource(mappedName = "java:global/tosEnabled") - public static String tosEnabled; - - /** - * The default application UUID assumed for all operational contexts where - * one is not supplied. - */ - @Resource(mappedName = "java:global/defaultApplicationUUID") - public static String defaultApplicationUUID; - - @Resource(mappedName = "java:global/systemName") - public static String systemName; - - @Resource(mappedName = "java:global/templatePath") - public static String templatePath; - - @Resource(mappedName = "java:global/accessGrantEmailSubject") - public static String accessGrantEmailSubject; - - @Resource(mappedName = "java:global/userActivationReplyTo") - public static String userActivationReplyTo; - - @Resource(lookup = "java:jboss/mail/gmail") - public static Session mailSession; - - @Resource(lookup = "java:global/adminUsers") - public static String adminUsers; - - @Resource(lookup = "java:global/deniedEmailEnabled") - public static String deniedEmailEnabled; - - // See checkIDPProvider method for setting these variables - public static String idp_provider; - public static String idp_provider_uri; - public static String fence_client_id; - public static String fence_client_secret; - public static String fence_redirect_url; - public static String fence_consent_group_concept_path; - public static String fence_standard_access_rules; - public static String fence_harmonized_concept_path; - - public static String defaultAdminRoleName = "PIC-SURE Top Admin"; - - public static long tokenExpirationTime; - // default expiration time is 1 hr - private static long defaultTokenExpirationTime = 1000L * 60 * 60; - - public static long longTermTokenExpirationTime; - // default long term token expiration time is 30 days - private static long defaultLongTermTokenExpirationTime = 1000L * 60 * 60 * 24 * 30; - - @Inject - RoleRepository roleRepo; - - @Inject - PrivilegeRepository privilegeRepo; - - @Inject - ConnectionRepository connectionRepo; - - public final static ObjectMapper objectMapper = new ObjectMapper(); - - public static final HttpClient client = HttpClientBuilder.create().useSystemProperties().build(); - - - @PostConstruct - public void init() { - logger.info("Starting auth micro app"); - - /* - create an admin role if there isn't one - */ - logger.info("Start initializing admin role in database"); - initializeDefaultAdminRole(); - logger.info("Finished initializing admin role."); - - logger.info("Start initializing tokens expiration time."); - initializeTokenExpirationTime(); - initializeLongTermTokenExpirationTime(); - logger.info("Finished initializing token expiration time."); - - logger.info("Determine IDP provider"); - checkIDPProvider(); - - mailSession.getProperties().put("mail.smtp.ssl.trust", "smtp.gmail.com"); - - logger.info("Auth micro app has been successfully started"); - - //Set info for the swagger.json - BeanConfig beanConfig = new BeanConfig(); - beanConfig.setVersion("1.0.0"); - beanConfig.setSchemes(new String[] { "https" }); - beanConfig.setDescription("APIs for accessing PIC-SURE-AUTH-MICROAPP - a centralized authentication/authorization micro services"); - beanConfig.setTitle("PIC-SURE-AUTH-MICROAPP"); - beanConfig.setBasePath("/psama"); - beanConfig.setResourcePackage(TokenService.class.getPackage().getName()); - beanConfig.setScan(true); - } - - /* - * Check if the IDP provider is set, and if it is, then determine additional - * settings. - * - * If flag is missing, or empty, the default is Auth0 configuration. - * - * This is currently only works for FENCE integration. - * - */ - public void checkIDPProvider() { - logger.debug("checkIDPProvider() starting...."); - - Context ctx = null; - try { - ctx = new InitialContext(); - } catch (NamingException e) { - e.printStackTrace(); - } - - try { - idp_provider = (String)ctx.lookup("java:global/idp_provider"); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - idp_provider = "default"; - } - logger.info("checkIDPProvider() idp provider is now :"+idp_provider); - - if (idp_provider.equalsIgnoreCase("fence")) { - try { - idp_provider_uri = (String)ctx.lookup("java:global/idp_provider_uri"); - - fence_client_id = (String) ctx.lookup("java:global/fence_client_id"); - fence_client_secret = (String) ctx.lookup("java:global/fence_client_secret"); - fence_redirect_url = (String) ctx.lookup("java:global/fence_redirect_url"); - - fence_consent_group_concept_path = (String) ctx.lookup("java:global/fence_consent_group_concept_path"); - if (fence_consent_group_concept_path == null) { - logger.error("checkIDPProvider() Empty consent group concept path from standalone.xml. Using default!"); - fence_consent_group_concept_path = "\\\\_Consents\\\\Short Study Accession with Consent code\\\\"; - } - - fence_standard_access_rules = (String) ctx.lookup("java:global/fence_standard_access_rules"); - if (fence_standard_access_rules.isEmpty()) { - logger.error("checkIDPProvider() Empty access rules from standalone.xml. Using defaults."); - fence_standard_access_rules = "GATE_ONLY_INFO,GATE_ONLY_QUERY,GATE_ONLY_SEARCH,GATE_FENCE_CONSENT_REQUIRED"; - } - - fence_harmonized_concept_path = (String) ctx.lookup("java:global/fence_harmonized_concept_path"); - if (fence_harmonized_concept_path.isEmpty()) { - logger.error("checkIDPProvider() Empty harmonized concept path. Not in use."); - fence_harmonized_concept_path = ""; - } - logger.debug("checkIDPProvider() idp provider FENCE is configured"); - - // Upsert FENCE connection - Connection c = connectionRepo.getUniqueResultByColumn("label","FENCE"); - if (c != null) { - logger.debug("checkIDPProvider() FENCE connection already exists."); - } else { - logger.debug("checkIDPProvider() Create new FENCE connection"); - c = new Connection(); - c.setLabel("FENCE"); - c.setId("fence"); - c.setSubPrefix("fence|"); - c.setRequiredFields("[{\"label\":\"email\",\"id\":\"email\"}]"); - connectionRepo.persist(c); - logger.debug("checkIDPProvider() New FENCE connetion has been created"); - } - - // For debugging purposes, here is a dump of most of the FENCE variables - logger.info("checkIDPProvider() fence_standard_access_rules "+fence_standard_access_rules); - logger.info("checkIDPProvider() fence_consent_group_concept_path "+fence_consent_group_concept_path); - logger.info("checkIDPProvider() fence_harmonized_concept_path "+fence_harmonized_concept_path); - - } catch (Exception ex) { - logger.error("checkIDPProvider() "+ex.getMessage()); - logger.error("checkIDPProvider() Invalid FENCE IDP Provider Setup. Mandatory fields are missing. "+ - "Check configuration in standalone.xml"); - } - } - logger.debug("checkIDPProvider() finished"); - } - - private void initializeTokenExpirationTime(){ - try { - Context ctx = new InitialContext(); - tokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/tokenExpirationTime")); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - tokenExpirationTime = defaultTokenExpirationTime; - } - - logger.info("Set token expiration time to " + tokenExpirationTime + " milliseconds"); - - } - - private void initializeLongTermTokenExpirationTime(){ - try { - Context ctx = new InitialContext(); - longTermTokenExpirationTime = Long.parseLong((String)ctx.lookup("java:global/longTermTokenExpirationTime")); - } catch (NamingException | ClassCastException | NumberFormatException ex){ - longTermTokenExpirationTime = defaultLongTermTokenExpirationTime; - } - - logger.info("Set long term token expiration time to " + longTermTokenExpirationTime + " milliseconds"); - - } - - private void initializeDefaultAdminRole(){ - - // make sure system admin and super admin privileges are added in the database - checkAndAddAdminPrivileges(); - - if (checkIfAdminRoleExists()){ - logger.info("Admin role already exists in database, no need for the creation."); - return; - } - - logger.info("Didn't find any role contains both " + ADMIN + - " and " + SUPER_ADMIN + - " in database, start to create one."); - Privilege systemAdmin = privilegeRepo.getByColumn("name", ADMIN).get(0); - Privilege superAdmin = privilegeRepo.getByColumn("name", SUPER_ADMIN).get(0); - - Role role = new Role(); - List roles = roleRepo.getByColumn("name", defaultAdminRoleName); - boolean isAdminRole = false; - if (roles != null && !roles.isEmpty()) { - logger.info("Found a role with default admin name " + defaultAdminRoleName + ", but without proper privileges associated with"); - role = roles.get(0); - isAdminRole = true; - } - role.setDescription("PIC-SURE Auth Micro App Top admin including Admin and super Admin"); - Set privileges = new HashSet<>(); - privileges.add(systemAdmin); - privileges.add(superAdmin); - role.setPrivileges(privileges); - - if(isAdminRole){ - roleRepo.merge(role); - logger.info("Finished updating the admin role, roleId: " + role.getUuid()); - } else { - role.setName(defaultAdminRoleName); - roleRepo.persist(role); - logger.info("Finished creating an admin role, roleId: " + role.getUuid()); - } - } - - private void checkAndAddAdminPrivileges(){ - logger.info("Checking if system admin and super admin privileges are added"); - List privileges = privilegeRepo.list(); - if (privileges == null) - privileges = new ArrayList<>(); - - Privilege superAdmin = null, systemAdmin = null; - - for (Privilege p : privileges) { - if (superAdmin != null && systemAdmin != null) - break; - - if (SUPER_ADMIN.equals(p.getName())){ - superAdmin = p; - continue; - } - - if (ADMIN.equals(p.getName())){ - systemAdmin = p; - continue; - } - } - - if (superAdmin == null){ - logger.info("Adding super admin"); - superAdmin = new Privilege(); - superAdmin.setName(SUPER_ADMIN); - superAdmin.setDescription("PIC-SURE Auth super admin for managing roles/privileges/application/connections"); - privilegeRepo.persist(superAdmin); - } - - if (systemAdmin == null) { - logger.info("Adding system admin"); - systemAdmin = new Privilege(); - systemAdmin.setName(ADMIN); - systemAdmin.setDescription("PIC-SURE Auth admin for managing users."); - privilegeRepo.persist(systemAdmin); - } - } - - private boolean checkIfAdminRoleExists(){ - logger.info("Checking if admin role already exists in database"); - List roles = roleRepo.list(); - if (roles == null || roles.isEmpty()) { - return false; - } - - boolean systemAdmin = false, superAdmin = false; - - for (Role role : roles) { - Set privileges = role.getPrivileges(); - if (privileges == null || privileges.isEmpty()) - continue; - - for (Privilege privilege : privileges) { - if (ADMIN.equals(privilege.getName())) { - systemAdmin = true; - } else if (SUPER_ADMIN.equals(privilege.getName())) { - superAdmin = true; - } - } - } - - return systemAdmin && superAdmin; - } - - public static String getPrincipalName(SecurityContext securityContext){ - if (securityContext.getUserPrincipal() == null) - return "No security context set, "; - - return securityContext.getUserPrincipal().getName(); - } - -} diff --git a/pic-sure-auth-services/bak/README.md b/pic-sure-auth-services/bak/README.md deleted file mode 100644 index 0b4e9d4ce..000000000 --- a/pic-sure-auth-services/bak/README.md +++ /dev/null @@ -1 +0,0 @@ -# This is a temporary location to keep files that I intend to delete, but want to keep around for a while just in case. \ No newline at end of file diff --git a/pic-sure-auth-services/bak/persistence.xml b/pic-sure-auth-services/bak/persistence.xml deleted file mode 100644 index bee8e1af6..000000000 --- a/pic-sure-auth-services/bak/persistence.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - java:jboss/datasources/AuthDS - edu.harvard.hms.dbmi.avillach.auth.entity.User - - - - - - - - - - \ No newline at end of file diff --git a/pic-sure-auth-services/bak/standalone.xml b/pic-sure-auth-services/bak/standalone.xml deleted file mode 100644 index f16f4d3cd..000000000 --- a/pic-sure-auth-services/bak/standalone.xml +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - h2 - - sa - sa - - - - jdbc:mysql://${env.AUTH_MYSQLADDRESS}:${env.AUTH_DB_PORT}/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&autoReconnectForPools=true - mysql - - 5 - 50 - true - - - ${env.AUTH_DB_CONNECTION_USER} - ${env.AUTH_MYSQLPASS} - - - - SELECT 1 - true - false - - - - - - com.mysql.jdbc.Driver - - - org.h2.jdbcx.JdbcDataSource - - - - - - - - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${jboss.bind.address:127.0.0.1} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From e124f5af70e55ee7371b30d1b21e2944aa5715ba Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 29 Apr 2024 13:01:08 -0400 Subject: [PATCH 164/222] Revert --- .../harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java index bd60e8325..37124eb62 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/AuthNaming.java @@ -1,7 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; - import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -19,8 +17,8 @@ public class AuthNaming { *

Note: Only users with super admin access can edit these role names.

*/ public static class AuthRoleNaming { - public static final String ADMIN = SecurityRoles.ADMIN.getRole(); - public static final String SUPER_ADMIN = SecurityRoles.SUPER_ADMIN.getRole(); + public static final String ADMIN = "ADMIN"; + public static final String SUPER_ADMIN = "SUPER_ADMIN"; public static List allRoles(){ List roles = new ArrayList<>(); From 24a2c9b51771dd51a71d475a6b18467c9c4bedce Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 30 Apr 2024 14:39:16 -0400 Subject: [PATCH 165/222] Removed 'development' scope in pom.xml --- pic-sure-auth-services/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/pom.xml b/pic-sure-auth-services/pom.xml index 432a8d5d4..3ace06328 100644 --- a/pic-sure-auth-services/pom.xml +++ b/pic-sure-auth-services/pom.xml @@ -66,7 +66,6 @@ org.springframework.boot spring-boot-devtools 3.2.4 - development true
From 35392be4863deba197eb5c8a1aabedaa829e6256 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 1 May 2024 16:01:28 -0400 Subject: [PATCH 166/222] Repair Unit Test --- .../auth/service/impl/ApplicationService.java | 2 +- .../impl/OauthUserMatchingService.java | 14 +++-- .../dbmi/avillach/ApplicationServiceTest.java | 21 ++++++- .../avillach/Auth0MatchingServiceTest.java | 61 ++++++++++++------- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 7901e9df0..40461636f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -27,7 +27,7 @@ public class ApplicationService implements UserDetailsService { private final JWTUtil jwtUtil; @Autowired - ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, JWTUtil jwtUtil) { + public ApplicationService(ApplicationRepository applicationRepo, PrivilegeRepository privilegeRepo, JWTUtil jwtUtil) { this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; this.jwtUtil = jwtUtil; diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java index 7cc63b3c8..e37007a70 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -1,9 +1,7 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; -import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,10 +63,15 @@ public User matchTokenToUser(JsonNode userInfo) { Object parsedInfo = conf.jsonProvider().parse(userInfoString); //Return lists or null so that we don't have to worry about whether it's a single object or an array, or catch errors List connections = JsonPath.using(conf).parse(parsedInfo).read("$.identities[0].connection"); - String connectionId = connections.get(0); + String connectionId = connections.getFirst(); Optional connection = connectionRepo.findById(connectionId); + if (connection.isEmpty()) { + //We don't have a connection for this user + logger.warn("Unable to find connection with id {}", connectionId); + return null; + } - List mappings = mappingService.getAllMappingsForConnection(connection.orElse(null)); + List mappings = mappingService.getAllMappingsForConnection(connection.get()); if (mappings == null || mappings.isEmpty()) { //We don't have any mappings for this connection yet @@ -110,7 +113,8 @@ public User matchTokenToUser(JsonNode userInfo) { u.setAuth0metadata(userInfoString); u.setMatched(true); u.setSubject(userId); - return userService.save(u); + userService.save(u); + return u; } } } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java index 0019bd640..74ebd3af2 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/ApplicationServiceTest.java @@ -3,8 +3,8 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.PrivilegeService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.ApplicationService; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -13,8 +13,12 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.security.SecureRandom; +import java.util.Base64; import java.util.UUID; +import static org.mockito.Mockito.spy; + @RunWith(MockitoJUnitRunner.class) public class ApplicationServiceTest { @@ -29,10 +33,12 @@ public class ApplicationServiceTest { @Before public void init() { + JWTUtil jwtUtil = spy(new JWTUtil(generate256Base64Secret(), false)); + applicationService = new ApplicationService(applicationRepository, privilegeRepository, jwtUtil); } @Test - public void testGenerateToken(){ + public void testGenerateToken() { Application application = new Application(); application.setUuid(UUID.randomUUID()); application.setName("Testing Application"); @@ -43,4 +49,15 @@ public void testGenerateToken(){ Assert.assertNotNull("Token is null, given application: " + application.getUuid(), token); Assert.assertTrue("Token is too short",token.length() > 10); } + + /** + * Do not use this method in production code. This is only for testing purposes. + * @return a 256-bit base64 encoded secret + */ + private static String generate256Base64Secret() { + SecureRandom random = new SecureRandom(); + byte[] secret = new byte[32]; + random.nextBytes(secret); + return Base64.getEncoder().encodeToString(secret); + } } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java index 659f7398e..31292eca9 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java @@ -14,38 +14,46 @@ import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; public class Auth0MatchingServiceTest { + private static final Logger log = LoggerFactory.getLogger(Auth0MatchingServiceTest.class); + + @Mock + UserRepository userRepo; + @Mock - UserRepository userRepo = mock(UserRepository.class); + UserMetadataMappingService mappingService; @Mock - UserMetadataMappingService mappingService = mock(UserMetadataMappingService.class); + UserService userService; @Mock - UserService userService = mock(UserService.class); + ConnectionRepository connectionRepo; @InjectMocks - OauthUserMatchingService cut = new OauthUserMatchingService(userRepo, userService, mappingService, mock(ConnectionRepository.class)); + OauthUserMatchingService cut; User persistedUser; ObjectMapper mapper = new ObjectMapper(); @@ -55,17 +63,21 @@ public void setUp() throws Auth0Exception { MockitoAnnotations.initMocks(this); //Instead of calling the database doAnswer(invocation -> (listUnmatchedByConnectionIdMock(invocation.getArgument(0)))). - when(userRepo).findByConnectionAndMatched(any(), any()); + when(userRepo).findByConnectionAndMatched(any(Connection.class), anyBoolean()); doAnswer(invocation -> (getAllMappingsForConnectionMock(invocation.getArgument(0)))). - when(mappingService).getAllMappingsForConnection((Connection) any()); + when(mappingService).getAllMappingsForConnection(any(Connection.class)); + + doAnswer(invocation -> { + String connectionId = invocation.getArgument(0); + log.info("Mocking connection with id: {}", connectionId); + return mockConnection(connectionId); + }).when(connectionRepo).findById(anyString()); + //So we can check that the user is persisted - doAnswer(new Answer() { - public Void answer(InvocationOnMock invocation) { - List userList = invocation.getArgument(0); - persistedUser = userList.get(0); - return null; - } - }).when(userService).updateUser(any(List.class)); + doAnswer(invocation -> { + persistedUser = invocation.getArgument(0); + return null; + }).when(userService).save(any(User.class)); } @Test @@ -79,6 +91,7 @@ public void testMatchTokenToUser() { //Test when everything works fine User result = cut.matchTokenToUser(userInfo); + log.info("Result: " + result); assertNotNull(result); assertNotNull(result.getAuth0metadata()); assertNotNull(result.getSubject()); @@ -147,7 +160,7 @@ public void testMatchTokenToUser() { } } - private List listUnmatchedByConnectionIdMock(String connectionId) { + private List listUnmatchedByConnectionIdMock(Connection connectionId) { List allMappings = List.of( new User().setConnection(new Connection().setId("ldap-connector")).setGeneralMetadata("{ \"email\": \"foo@childrens.harvard.edu\", \"fullName\" : \"Bruce Banner\"}"), new User().setConnection(new Connection().setId("ldap-connector")).setGeneralMetadata("{ \"email\": \"foobar@childrens.harvard.edu\", \"fullName\" : \"Scott Lang\"}"), @@ -158,11 +171,12 @@ private List listUnmatchedByConnectionIdMock(String connectionId) { new User().setConnection(new Connection().setId("no-mapping-connection")).setGeneralMetadata("{ \"email\": \"foo@bar.com\", \"fullName\" : \"Luke Cage\"}") ); return allMappings.stream().filter((User user) -> { - return user.getConnection().getId().equalsIgnoreCase(connectionId); + return user.getConnection().getId().equalsIgnoreCase(connectionId.getId()); }).collect(Collectors.toList()); } - private List getAllMappingsForConnectionMock(String connectionId) { + private List getAllMappingsForConnectionMock(Connection connection) { + log.info("Mocking mappings for connection with id: " + connection.getId()); List allMappings = List.of( new UserMetadataMapping().setConnection(new Connection().setId("ldap-connector")).setGeneralMetadataJsonPath("$.email").setAuth0MetadataJsonPath("$.email"), new UserMetadataMapping().setConnection(new Connection().setId("nih-gov-prod")).setGeneralMetadataJsonPath("$.nih-userid").setAuth0MetadataJsonPath("$.identities[0].user_id"), @@ -173,7 +187,7 @@ private List getAllMappingsForConnectionMock(String connect ); return allMappings.stream().filter((UserMetadataMapping mapping) -> { - return mapping.getConnection().getId().equalsIgnoreCase(connectionId); + return mapping.getConnection().getId().equalsIgnoreCase(connection.getId()); }).collect(Collectors.toList()); } @@ -193,4 +207,9 @@ public JsonNode mockAuthAPIUserInfo(String accessToken) throws IOException { return mapper.valueToTree(jsonMap); } + public Optional mockConnection(String id) { + log.info("Mocking connection with id: " + id); + return Optional.of(new Connection().setId(id)); + } + } From 2d9e145c3747aaac1d0202e2e39d87bd1c945ef7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 10:02:49 -0400 Subject: [PATCH 167/222] Remove authentication test file --- pic-sure-auth-services/AuthenicationTest.http | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 pic-sure-auth-services/AuthenicationTest.http diff --git a/pic-sure-auth-services/AuthenicationTest.http b/pic-sure-auth-services/AuthenicationTest.http deleted file mode 100644 index 2f9845631..000000000 --- a/pic-sure-auth-services/AuthenicationTest.http +++ /dev/null @@ -1,7 +0,0 @@ -POST http://localhost:8090/auth/authentication -Content-Type: application/json - -# JSON Encode request parameters -{ - "test": "test" -} \ No newline at end of file From af93ed97cd540ec0a37029daa84005af89181a19 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 12:49:53 -0400 Subject: [PATCH 168/222] Refactor code and add timeout configuration for requests --- .../service/impl/AuthenticationService.java | 33 ++++++++----------- .../avillach/auth/utils/RestClientUtil.java | 18 ++++++++++ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 3c56df6d6..9cabad8b3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -21,6 +21,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Service; /** @@ -81,7 +83,6 @@ public ResponseEntity getToken(Map authRequest) throws IOExce String userId = userIdNode.asText(); logger.info("Successfully retrieved userId, {}, from the provided code and redirectURI", userId); - String connectionId; try { connectionId = userInfo.get("identities").get(0).get("connection").asText(); @@ -108,34 +109,30 @@ public ResponseEntity getToken(Map authRequest) throws IOExce } } - HashMap claims = new HashMap(); + HashMap claims = new HashMap<>(); claims.put("sub", userId); claims.put("name", user.getName()); claims.put("email", user.getEmail()); HashMap responseMap = userService.getUserProfileResponse(claims); - logger.info("LOGIN SUCCESS ___ " + user.getEmail() + ":" + user.getUuid().toString() + " ___ Authorization will expire at ___ " + responseMap.get("expirationDate") + "___"); - + logger.info("LOGIN SUCCESS ___ {}:{} ___ Authorization will expire at ___ {}___", user.getEmail(), user.getUuid().toString(), responseMap.get("expirationDate")); return PICSUREResponse.success(responseMap); } private JsonNode retrieveUserInfo(String accessToken) throws IOException { - // TODO: Remove this after debugging - logger.info("accessToken: {}", accessToken); - logger.info("auth0host: {}", this.auth0host); String auth0UserInfoURI = this.auth0host + "/userinfo"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + accessToken); JsonNode auth0Response = null; -// RequestConfig requestConfig = createRequestConfigWithCustomTimeout(); // TODO: How can we do this with the Spring rest client? for (int i = 1; i <= AUTH_RETRY_LIMIT && auth0Response == null; i++) { try { - ResponseEntity response = RestClientUtil.retrieveGetResponse( + ResponseEntity response = RestClientUtil.retrieveGetResponseWithRequestConfiguration( auth0UserInfoURI, - headers + headers, + createRequestConfigWithCustomTimeout(2000) ); auth0Response = objectMapper.readTree(response.getBody()); @@ -150,13 +147,11 @@ private JsonNode retrieveUserInfo(String accessToken) throws IOException { } return auth0Response; } -// TODO : This method is not used. we need to investigate if it is needed or not -// private RequestConfig createRequestConfigWithCustomTimeout() { -// int timeoutMs = 2000; // 2 seconds, default is 3 seconds -// return RequestConfig.custom() -// .setConnectionRequestTimeout(timeoutMs) -// .setConnectTimeout(timeoutMs) -// .setSocketTimeout(timeoutMs) -// .build(); -// } + + public ClientHttpRequestFactory createRequestConfigWithCustomTimeout(int timeoutMs) { + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + requestFactory.setConnectTimeout(timeoutMs); + requestFactory.setReadTimeout(timeoutMs); + return requestFactory; + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index 30729dabc..cb78f677b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.*; +import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; @@ -39,6 +40,23 @@ public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders } } + // Implement: The ability to set the timeout on the rest template for a given request. + public static ResponseEntity retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) { + logger.info("HttpClientUtilSpring retrieveGetResponse()"); + logger.info("uri: {}", uri); + RestTemplate localRestTemplate = new RestTemplate(requestFactory); + + try { + logger.debug("HttpClientUtilSpring retrieveGetResponse()"); + HttpEntity entity = new HttpEntity<>("parameters", headers); + // Pass custom configuration to the RestTemplate + return localRestTemplate.exchange(uri, HttpMethod.GET, entity, String.class); + } catch (HttpClientErrorException ex) { + logger.error("HttpClientErrorException: " + ex.getMessage()); + throw ex; + } + } + public static String composeURL(String baseURL, String pathName, String query) { try { URI uri = new URI(baseURL); From 9a1f86841db6429a49d6ebe1faf74efd0530660d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 12:53:20 -0400 Subject: [PATCH 169/222] Refactor RestClientUtil, remove unused methods and improve logging --- .../avillach/auth/utils/RestClientUtil.java | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index cb78f677b..160aa96c9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.utils; -import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.*; @@ -8,7 +7,6 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; -import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -18,41 +16,30 @@ public class RestClientUtil { - private static final ObjectMapper objectMapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(RestClientUtil.class); private static final RestTemplate restTemplate = new RestTemplate(); - public static boolean is2xx(ResponseEntity response) { - return response.getStatusCode().is2xxSuccessful(); - } - public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders headers) { - logger.info("HttpClientUtilSpring retrieveGetResponse()"); - logger.info("uri: {}", uri); try { - logger.debug("HttpClientUtilSpring retrieveGetResponse()"); HttpEntity entity = new HttpEntity<>("parameters", headers); // Pass custom configuration to the RestTemplate return restTemplate.exchange(uri, HttpMethod.GET, entity, String.class); } catch (HttpClientErrorException ex) { - logger.error("HttpClientErrorException: " + ex.getMessage()); + logger.error("HttpClientErrorException: {}", ex.getMessage()); throw ex; } } // Implement: The ability to set the timeout on the rest template for a given request. public static ResponseEntity retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) { - logger.info("HttpClientUtilSpring retrieveGetResponse()"); - logger.info("uri: {}", uri); RestTemplate localRestTemplate = new RestTemplate(requestFactory); try { - logger.debug("HttpClientUtilSpring retrieveGetResponse()"); HttpEntity entity = new HttpEntity<>("parameters", headers); // Pass custom configuration to the RestTemplate return localRestTemplate.exchange(uri, HttpMethod.GET, entity, String.class); } catch (HttpClientErrorException ex) { - logger.error("HttpClientErrorException: " + ex.getMessage()); + logger.error("HttpClientErrorException: {}", ex.getMessage()); throw ex; } } @@ -85,14 +72,4 @@ public static ResponseEntity retrievePostResponse(String uri, HttpHeader } } - public static T readObjectFromResponse(ResponseEntity response, Class expectedElementType) { - logger.debug("HttpClientUtilSpring readObjectFromResponse()"); - - try { - return objectMapper.readValue(response.getBody(), objectMapper.getTypeFactory().constructType(expectedElementType)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } From f2b1c238da077d271ba25db2946f7ceecc26c0c7 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 13:01:39 -0400 Subject: [PATCH 170/222] Updated docker-compose.dev.yml and added auth-flyway service --- docker-compose.dev.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 000185cf3..4fba9d733 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -6,12 +6,13 @@ services: dockerfile: ./pic-sure-auth-services/dev.Dockerfile environment: JAVA_OPTS: "-Xmx1024m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + # Note: To use this you will need to set ENVIRONMENT variables for the application.properties ports: - "8090:8090" # Application port - "5005:5005" # Debug port depends_on: - pic-sure-auth-db: - condition: service_healthy + auth-flyway: + condition: service_started picsure-db: build: @@ -22,8 +23,30 @@ services: MYSQL_ROOT_PASSWORD: password ports: - "3306:3306" # MySQL port + command: + - --default-authentication-plugin=mysql_native_password # Required for MySQL 8.0 healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] interval: 10s timeout: 5s - retries: 5 \ No newline at end of file + retries: 5 + + auth-flyway: + image: flyway/flyway:latest + volumes: + - ./pic-sure-auth-db/db/sql:/flyway/sql + # linux/amd64 + platform: linux/amd64 + environment: + - FLYWAY_URL=jdbc:mysql://picsure-db:3306/auth?useSSL=false + - FLYWAY_USER=root + - FLYWAY_PASSWORD=password + - FLYWAY_SCHEMAS=auth + - FLYWAY_LOCATIONS=filesystem:/flyway/sql + - FLYWAY_IGNORE_MISSING_MIGRATIONS=true + - FLYWAY_BASELINE_ON_MIGRATE=true + command: + - migrate + depends_on: + picsure-db: + condition: service_healthy \ No newline at end of file From b52c82cb2d2266de1e2b2c8f972ea450dec919bc Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 13:04:32 -0400 Subject: [PATCH 171/222] Add Maven dependencies pre-fetching in Dockerfile --- pic-sure-auth-services/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pic-sure-auth-services/Dockerfile b/pic-sure-auth-services/Dockerfile index ed2598cc7..ce0ced4f9 100644 --- a/pic-sure-auth-services/Dockerfile +++ b/pic-sure-auth-services/Dockerfile @@ -1,5 +1,9 @@ FROM maven:3.9.6-amazoncorretto-21 as build +COPY ./pom.xml /app/pom.xml + +RUN mvn -f /app/pom.xml dependency:go-offline + # Copy the source code into the container COPY ./ /app From 69f0af6f94e5e9480ca7612031c7a00b3404442d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 13:08:54 -0400 Subject: [PATCH 172/222] Refactor RoleController getRoleById Response logic has been moved out of the service. --- .../hms/dbmi/avillach/auth/rest/RoleController.java | 6 +++++- .../hms/dbmi/avillach/auth/service/RoleService.java | 10 ++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index 98a6aa66b..1688e1413 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -41,7 +41,11 @@ public RoleController(RoleService roleService) { public ResponseEntity getRoleById( @Parameter(description = "The UUID of the Role to fetch information about") @PathVariable("roleId") String roleId) { - return this.roleService.getRoleById(roleId); + Optional optionalRole = this.roleService.getRoleById(roleId); + if (optionalRole.isEmpty()) { + return PICSUREResponse.protocolError("Role is not found by given role ID: " + roleId); + } + return PICSUREResponse.success(optionalRole.get()); } @Operation(description = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index d0c41a9f5..0dec11b67 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -3,11 +3,9 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -32,12 +30,8 @@ protected RoleService(RoleRepository roleRepository, PrivilegeRepository privile this.privilegeRepo = privilegeRepo; } - public ResponseEntity getRoleById(String roleId) { - Optional optionalRole = roleRepository.findById(UUID.fromString(roleId)); - if (optionalRole.isEmpty()) { - return PICSUREResponse.protocolError("Role is not found by given role ID: " + roleId); - } - return PICSUREResponse.success(optionalRole.get()); + public Optional getRoleById(String roleId) { + return roleRepository.findById(UUID.fromString(roleId)); } public List getAllRoles() { From b95978bcd5cb176591bdaca8a5e02df14550c436 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 2 May 2024 13:38:59 -0400 Subject: [PATCH 173/222] Update response handling in UserController and UserService --- .../hms/dbmi/avillach/auth/rest/UserController.java | 12 +++++++++--- .../dbmi/avillach/auth/service/impl/UserService.java | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index c58d4cc47..562bae2c2 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -45,7 +45,7 @@ public ResponseEntity getUserById( @Parameter(required = true, description = "The UUID of the user to fetch information about") @PathVariable("userId") String userId) { User userById = this.userService.getUserById(userId); - return ResponseEntity.ok(userById); + return PICSUREResponse.success(userById); } @Operation(description = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") @@ -53,7 +53,7 @@ public ResponseEntity getUserById( @GetMapping(produces = "application/json") public ResponseEntity getUserAll() { List entityAll = this.userService.getAllUsers(); - return ResponseEntity.ok(entityAll); + return PICSUREResponse.success(entityAll); } @Operation(description = "POST a list of users, requires ADMIN role") @@ -109,7 +109,13 @@ public ResponseEntity getQueryTemplate( @Operation(description = "Retrieve the queryTemplate of default application") @GetMapping(path = "/me/queryTemplate", produces = "application/json") public ResponseEntity getQueryTemplate() { - return this.userService.getDefaultQueryTemplate(); + Map defaultQueryTemplate = userService.getDefaultQueryTemplate(); + + if (defaultQueryTemplate == null) { + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + return PICSUREResponse.success(defaultQueryTemplate); } /** diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index b93b8d0dc..b9072631a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -390,15 +390,15 @@ public Optional getQueryTemplate(String applicationId) { return Optional.ofNullable(mergeTemplate(user, application.orElse(null))); } - public ResponseEntity getDefaultQueryTemplate() { + public Map getDefaultQueryTemplate() { Optional mergedTemplate = getQueryTemplate(this.applicationUUID); if (mergedTemplate.isEmpty()) { logger.error("getDefaultQueryTemplate() cannot find corresponding application by UUID: {}", this.applicationUUID); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } - return PICSUREResponse.success(Map.of("queryTemplate", mergedTemplate.orElse(null))); + return Map.of("queryTemplate", mergedTemplate.orElse(null)); } private String mergeTemplate(User user, Application application) { From a4b255659d97ed61ea34ba78555fe73313fc592a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 3 May 2024 10:06:13 -0400 Subject: [PATCH 174/222] Refactor response handling in various services and controllers Instead of returning ResponseEntity within User and Token services, they now return the respective data directly, shifting the responsibility of wrapping the response into the controllers. --- .../auth/rest/PrivilegeController.java | 2 +- .../avillach/auth/rest/TokenController.java | 12 ++++- .../avillach/auth/rest/UserController.java | 44 ++++++++++++++----- .../UserMetadataMappingWebController.java | 4 +- .../service/impl/AuthorizationService.java | 3 -- .../auth/service/impl/TokenService.java | 19 ++++---- .../impl/UserMetadataMappingService.java | 6 +-- .../auth/service/impl/UserService.java | 42 +++++++----------- 8 files changed, 75 insertions(+), 57 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index 31c58e7cf..1a78c6b3d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -71,7 +71,7 @@ public ResponseEntity addPrivilege( public ResponseEntity updatePrivilege( @Parameter(required = true, description = "A list of privilege with fields to be updated in JSON format") @RequestBody List privileges){ - privileges = this.privilegeService.updatePrivileges(privileges); + privileges = this.privilegeService.updatePrivileges(privileges); return ResponseEntity.ok(privileges); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index 8d09cf836..a938a5f29 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -1,5 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.TokenService; import io.swagger.v3.oas.annotations.Operation; @@ -39,13 +40,20 @@ public ResponseEntity inspectToken( @Parameter(required = true, description = "A JSON object that at least" + " include a user the token for validation") @RequestBody Map inputMap) { - return this.tokenService.inspectToken(inputMap); + Map stringObjectMap = this.tokenService.inspectToken(inputMap); + return PICSUREResponse.success(stringObjectMap); } @Operation(description = "To refresh current user's token if the user is an active user") @GetMapping(path = "/refresh", produces = "application/json") public ResponseEntity refreshToken(@RequestHeader("Authorization") String authorizationHeader) { - return this.tokenService.refreshToken(authorizationHeader); + Map stringStringMap = this.tokenService.refreshToken(authorizationHeader); + + if (stringStringMap.containsKey("error")) { + return PICSUREResponse.protocolError(stringStringMap.get("error")); + } + + return PICSUREResponse.success(stringStringMap); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index 562bae2c2..b48279e65 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -62,8 +62,17 @@ public ResponseEntity getUserAll() { public ResponseEntity addUser( @Parameter(required = true, description = "A list of user in JSON format") @RequestBody List users) { - return this.userService.addUsers(users); + List addedUsers = this.userService.addUsers(users); + if (addedUsers == null) { + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + String message = this.userService.sendUserUpdateEmailsFromResponse(addedUsers); + if (message != null) { + return PICSUREResponse.success(message, addedUsers); + } + return PICSUREResponse.success(addedUsers); } @Operation(description = "Update a list of users, will only update the fields listed, requires ADMIN role") @@ -71,7 +80,17 @@ public ResponseEntity addUser( @PutMapping(produces = "application/json") public ResponseEntity updateUser( @RequestBody List users) { - return this.userService.updateUser(users); + List updatedUsers = this.userService.updateUser(users); + if (updatedUsers == null) { + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + String message = this.userService.sendUserUpdateEmailsFromResponse(updatedUsers); + if (message != null) { + return PICSUREResponse.success(message, updatedUsers); + } + + return PICSUREResponse.success(updatedUsers); } /** @@ -87,7 +106,13 @@ public ResponseEntity getCurrentUser( @Parameter(description = "Attribute that represents if a long term token will attach to the response") @RequestParam(name = "hasToken", required = false) Boolean hasToken) { logger.info("getCurrentUser() authorizationHeader: {}, hasToken {}", authorizationHeader, hasToken); - return this.userService.getCurrentUser(authorizationHeader, hasToken); + User.UserForDisplay currentUser = this.userService.getCurrentUser(authorizationHeader, hasToken); + + if (currentUser == null) { + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } + + return PICSUREResponse.success(currentUser); } @Operation(description = "Retrieve the queryTemplate of certain application by given application Id for the currentUser ") @@ -131,13 +156,12 @@ public ResponseEntity getQueryTemplate() { @GetMapping(path = "/me/refresh_long_term_token", produces = "application/json") public ResponseEntity refreshUserToken( @RequestHeader HttpHeaders httpHeaders) { - return this.userService.refreshUserToken(httpHeaders); - } - - - - - + Map stringStringMap = this.userService.refreshUserToken(httpHeaders); + if (stringStringMap != null) { + return PICSUREResponse.success(stringStringMap); + } + return PICSUREResponse.applicationError("Inner application error, please contact admin."); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index 18f53f55c..237ba8e13 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -1,5 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; @@ -37,7 +38,8 @@ public UserMetadataMappingWebController(UserMetadataMappingService mappingServic @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "{connectionId}", produces = "application/json") public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { - return this.mappingService.getAllMappingsForConnection(connection); + Connection allMappingsForConnection = this.mappingService.getAllMappingsForConnection(connection); + return PICSUREResponse.success(allMappingsForConnection); } @Operation(description = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java index 683979226..c31617296 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java @@ -422,7 +422,6 @@ private boolean extractAndCheckRule(AccessRule accessRule, Object parsedRequestB } - // TODO: Look into this more // AccessRule type IS_EMPTY is very special, needs to be checked in front of any others // in type IS_EMPTY, it doens't matter if the value is null or anything if (accessRuleType == AccessRule.TypeNaming.IS_EMPTY @@ -476,7 +475,6 @@ private boolean evaluateNode(Object requestBodyValue, AccessRule accessRule){ * 12 = "\demographics\AGE\" */ - // TODO: Clean this up. We can use java 21 pattern matching to make this more readable if (requestBodyValue instanceof String){ return decisionMaker(accessRule, (String)requestBodyValue); } else if (requestBodyValue instanceof Collection) { @@ -641,7 +639,6 @@ private boolean _decisionMaker(AccessRule accessRule, String requestBodyValue, S logger.debug(requestBodyValue); logger.debug(value); - // TODO: Clean this up. We can use java 21 pattern matching to make this more readable switch (accessRule.getType()){ case AccessRule.TypeNaming.NOT_CONTAINS: if (!requestBodyValue.contains(value)) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 5e13e672a..05d5460f8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -40,7 +39,7 @@ public class TokenService { private final long tokenExpirationTime; - private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour TODO: Move to a global configuration or enum? + private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour private final JWTUtil jwtUtil; @Autowired @@ -52,7 +51,7 @@ public TokenService(AuthorizationService authorizationService, UserRepository us this.jwtUtil = jwtUtil; } - public ResponseEntity inspectToken(Map inputMap) { + public Map inspectToken(Map inputMap) { logger.info("TokenInspect starting..."); TokenInspection tokenInspection; try { @@ -65,7 +64,7 @@ public ResponseEntity inspectToken(Map inputMap) { } logger.info("Finished token introspection."); - return PICSUREResponse.success(tokenInspection.getResponseMap()); + return tokenInspection.getResponseMap(); } private TokenInspection _inspectToken(Map inputMap) throws IllegalAccessException { @@ -209,7 +208,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg return tokenInspection; } - public ResponseEntity refreshToken(String authorizationHeader) { + public Map refreshToken(String authorizationHeader) { logger.debug("RefreshToken starting..."); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); @@ -225,7 +224,7 @@ public ResponseEntity refreshToken(String authorizationHeader) { if (user.getUuid() == null) { logger.error("refreshToken() Stored user doesn't have a uuid."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return Map.of("error", "Inner application error, please contact admin."); } Optional loadUser = this.userRepository.findById(user.getUuid()); @@ -251,7 +250,7 @@ public ResponseEntity refreshToken(String authorizationHeader) { jws = this.jwtUtil.parseToken(token); } catch (NotAuthorizedException ex) { - return PICSUREResponse.protocolError("Cannot parse original token"); + return Map.of("error", "Cannot parse original token"); } Claims claims = jws.getPayload(); @@ -260,7 +259,7 @@ public ResponseEntity refreshToken(String authorizationHeader) { // just in case something has changed in middle if (StringUtils.isNotBlank(subject) && !subject.equals(claims.getSubject())) { logger.error("refreshToken() user subject is not the same as the subject of the input token"); - return PICSUREResponse.applicationError("Inner application error, try again or contact admin."); + return Map.of("error", "Inner application error, try again or contact admin."); } Date expirationDate = new Date(Calendar.getInstance().getTimeInMillis() + this.tokenExpirationTime); @@ -272,10 +271,10 @@ public ResponseEntity refreshToken(String authorizationHeader) { this.tokenExpirationTime); logger.debug("Finished RefreshToken and new token has been generated."); - return PICSUREResponse.success(Map.of( + return Map.of( "token", refreshedToken, "expirationDate", ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneOffset.UTC).toString() - )); + ); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index c5b9ebdae..131f4887b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -6,7 +6,6 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -59,9 +58,8 @@ public List getAllMappings() { return userMetadataMappingRepo.findAll(); } - public ResponseEntity getAllMappingsForConnection(String connectionId) { - Connection connection = this.connectionRepo.findById(connectionId).orElseThrow(() -> new IllegalArgumentException("Connection not found")); - return PICSUREResponse.success(getAllMappingsForConnection(connection)); + public Connection getAllMappingsForConnection(String connectionId) { + return this.connectionRepo.findById(connectionId).orElseThrow(() -> new IllegalArgumentException("Connection not found")); } public List updateUserMetadataMappings(List mappings) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index b9072631a..081f4cf74 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -8,7 +8,6 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; @@ -26,7 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; @@ -200,12 +198,12 @@ private boolean allowUpdateSuperAdminRole( } @Transactional - public ResponseEntity addUsers(List users) { + public List addUsers(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } User currentUser = customUserDetails.getUser(); @@ -232,9 +230,7 @@ public ResponseEntity addUsers(List users) { } users = addUser(users); - ResponseEntity updateResponse = PICSUREResponse.success(users); - sendUserUpdateEmailsFromResponse(updateResponse); - return updateResponse; + return users; } /** @@ -260,13 +256,13 @@ private void checkAssociation(List users) { } @Transactional - public ResponseEntity updateUser(List users) { + public List updateUser(List users) { SecurityContext securityContext = SecurityContextHolder.getContext(); logger.info("Security context: {}", securityContext.getAuthentication().getPrincipal()); CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); if (customUserDetails == null || customUserDetails.getUser() == null && customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } User currentUser = customUserDetails.getUser(); @@ -282,53 +278,47 @@ public ResponseEntity updateUser(List users) { if (allowUpdate) { users = this.userRepository.saveAll(users); - ResponseEntity updateResponse = PICSUREResponse.success(users); - sendUserUpdateEmailsFromResponse(updateResponse); - return updateResponse; + return users; } else { logger.error("updateUser() user - {} - with roles [{}] - is not allowed to grant or remove " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege.", currentUser.getUuid(), currentUser.getRoleString()); throw new IllegalArgumentException("Not allowed to update a user with changes associated to " + AuthNaming.AuthRoleNaming.SUPER_ADMIN + " privilege."); } } - private void sendUserUpdateEmailsFromResponse(ResponseEntity updateResponse) { + public String sendUserUpdateEmailsFromResponse(List addedUsers) { logger.debug("Sending email"); try { - Object entity = updateResponse.getBody(); // TODO: Determine how to replicate this given the new approach - if (entity instanceof HashMap okResponse) { - List addedUsers = (List) okResponse.get("content"); - String message = okResponse.get("message") != null ? okResponse.get("message").toString() : ""; for (User user : addedUsers) { try { basicMailService.sendUsersAccessEmail(user); } catch (MessagingException e) { logger.error("Failed to send email! {}", e.getLocalizedMessage()); logger.debug("Exception Trace: ", e); - okResponse.put("message", message + " WARN - could not send email to user " + user.getEmail() + " see logs for more info"); + return " WARN - could not send email to user " + user.getEmail() + " see logs for more info"; } } - } } catch (Exception e) { logger.error("Failed to send email - unhandled exception: ", e); } logger.debug("finished email sending method"); + return null; } @Transactional - public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasToken) { + public User.UserForDisplay getCurrentUser(String authorizationHeader, Boolean hasToken) { SecurityContext securityContext = SecurityContextHolder.getContext(); logger.info("Security context: {}", securityContext); Optional customUserDetails = Optional.ofNullable((CustomUserDetails) securityContext.getAuthentication().getPrincipal()); if (customUserDetails.isEmpty() || customUserDetails.get().getUser() == null) { logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } User user = customUserDetails.get().getUser(); if (user == null) { logger.error("When retrieving current user, it returned null"); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } logger.info("getCurrentUser() user found: {}", user.getEmail()); @@ -364,7 +354,7 @@ public ResponseEntity getCurrentUser(String authorizationHeader, Boolean hasT userForDisplay.setToken(user.getToken()); } - return PICSUREResponse.success(userForDisplay); + return userForDisplay; } public Optional getQueryTemplate(String applicationId) { @@ -442,12 +432,12 @@ private String mergeTemplate(User user, Application application) { } @Transactional - public ResponseEntity refreshUserToken(HttpHeaders httpHeaders) { + public Map refreshUserToken(HttpHeaders httpHeaders) { SecurityContext securityContext = SecurityContextHolder.getContext(); CustomUserDetails customUserDetails = (CustomUserDetails) securityContext.getAuthentication().getPrincipal(); if (customUserDetails == null || customUserDetails.getUser() == null || customUserDetails.getUser().getUuid() == null) { logger.error("Security context didn't have a user stored."); - return PICSUREResponse.applicationError("Inner application error, please contact admin."); + return null; } User user = customUserDetails.getUser(); @@ -456,7 +446,7 @@ public ResponseEntity refreshUserToken(HttpHeaders httpHeaders) { user.setToken(longTermToken); this.userRepository.save(user); - return PICSUREResponse.success(Map.of("userLongTermToken", longTermToken)); + return Map.of("userLongTermToken", longTermToken); } /** From 36156d4b0f7677ea2ba02d7b2ccd1423b44a78f8 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 09:12:18 -0400 Subject: [PATCH 175/222] Add tests for AccessRuleService --- .../service/impl/AccessRuleServiceTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleServiceTest.java new file mode 100644 index 000000000..0bef2c088 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AccessRuleServiceTest.java @@ -0,0 +1,128 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.repository.AccessRuleRepository; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +public class AccessRuleServiceTest { + + @Mock + private AccessRuleRepository accessRuleRepo; + + @InjectMocks + private AccessRuleService accessRuleService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetAccessRuleById_found() { + UUID id = UUID.randomUUID(); + AccessRule accessRule = new AccessRule(); + when(accessRuleRepo.findById(id)).thenReturn(Optional.of(accessRule)); + + Optional result = accessRuleService.getAccessRuleById(id.toString()); + assertTrue(result.isPresent()); + Assert.assertSame(accessRule, result.get()); + } + + @Test + public void testGetAccessRuleById_notFound() { + UUID id = UUID.randomUUID(); + when(accessRuleRepo.findById(id)).thenReturn(Optional.empty()); + + Optional result = accessRuleService.getAccessRuleById(id.toString()); + assertFalse(result.isPresent()); + } + + @Test + public void testGetAllAccessRules_empty() { + when(accessRuleRepo.findAll()).thenReturn(Collections.emptyList()); + + List result = accessRuleService.getAllAccessRules(); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetAllAccessRules_nonEmpty() { + List rules = Arrays.asList(new AccessRule(), new AccessRule()); + when(accessRuleRepo.findAll()).thenReturn(rules); + + List result = accessRuleService.getAllAccessRules(); + assertEquals(2, result.size()); + } + + + @Test + public void testAddAccessRule_withNullFields() { + AccessRule rule = new AccessRule(); // fields are null + List rules = Collections.singletonList(rule); + when(accessRuleRepo.saveAll(anyList())).thenAnswer(invocation -> invocation.getArgument(0)); + + List result = accessRuleService.addAccessRule(rules); + assertFalse(result.getFirst().getEvaluateOnlyByGates()); + assertFalse(result.getFirst().getCheckMapKeyOnly()); + assertFalse(result.getFirst().getCheckMapNode()); + assertFalse(result.getFirst().getGateAnyRelation()); + } + + @Test + public void testAddAccessRule_noNullFields() { + AccessRule rule = new AccessRule(); + rule.setEvaluateOnlyByGates(true); + rule.setCheckMapKeyOnly(true); + rule.setCheckMapNode(true); + rule.setGateAnyRelation(true); + List rules = Collections.singletonList(rule); + when(accessRuleRepo.saveAll(anyList())).thenReturn(rules); + + List result = accessRuleService.addAccessRule(rules); + assertTrue(result.getFirst().getEvaluateOnlyByGates()); + assertTrue(result.getFirst().getCheckMapKeyOnly()); + assertTrue(result.getFirst().getCheckMapNode()); + assertTrue(result.getFirst().getGateAnyRelation()); + } + + + @Test + public void testUpdateAccessRules() { + AccessRule rule = new AccessRule(); + List rules = Collections.singletonList(rule); + when(accessRuleRepo.saveAll(anyList())).thenReturn(rules); + + List result = accessRuleService.updateAccessRules(rules); + assertSame(rules, result); + } + + + @Test + public void testRemoveAccessRuleById() { + UUID id = UUID.randomUUID(); + List remainingRules = List.of(new AccessRule()); + doNothing().when(accessRuleRepo).deleteById(id); + when(accessRuleRepo.findAll()).thenReturn(remainingRules); + + List result = accessRuleService.removeAccessRuleById(id.toString()); + assertEquals(1, result.size()); + } + +} \ No newline at end of file From 423e343c657898205d49017977f650d779774088 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 10:02:44 -0400 Subject: [PATCH 176/222] Remove catch block Unit test coverage has shown that this statement is unreachable. applicationRepo.saveAll() will never contain a "null" application in the list. --- .../auth/service/impl/ApplicationService.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 40461636f..19b794152 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -41,11 +41,12 @@ public ApplicationService(ApplicationRepository applicationRepo, PrivilegeReposi */ @Transactional public Optional getApplicationByID(String applicationId) { - return this.applicationRepo.findById(UUID.fromString(applicationId)); + return applicationRepo.findById(UUID.fromString(applicationId)); } /** * Retrieves an entity by its ID with its privileges. This method is used to avoid lazy loading exception. + * * @param applicationName * @return */ @@ -71,13 +72,9 @@ public List addNewApplications(List applications) { checkAssociation(applications); List appEntities = this.applicationRepo.saveAll(applications); for (Application application : appEntities) { - try { - application.setToken( - generateApplicationToken(application) - ); - } catch (Exception e) { - logger.error("", e); - } + application.setToken( + generateApplicationToken(application) + ); } return this.applicationRepo.saveAll(appEntities); From 05a819f8a68edbcb81fc0cead7d393cd904d9d83 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 10:02:55 -0400 Subject: [PATCH 177/222] Update setPrivileges method in Application class An adjustment has been made to the setPrivileges method of the Application class to include a null check. Now, it prevents NullPointerException by directly setting privileges when the existing set is null or clearing and re-adding when it isn't null. --- .../hms/dbmi/avillach/auth/entity/Application.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java index 8c0c3dae0..a58856dee 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/entity/Application.java @@ -63,8 +63,12 @@ public Set getPrivileges() { } public void setPrivileges(Set privileges) { - this.privileges.clear(); - this.privileges.addAll(privileges); + if (this.privileges != null) { + this.privileges.clear(); + this.privileges.addAll(privileges); + } else { + this.privileges = privileges; + } } public String getUrl() { From e591404350853d432fda3030e2ed5706289610e6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 10:17:39 -0400 Subject: [PATCH 178/222] Update exception handling in refreshApplicationToken method The method 'refreshApplicationToken' has been updated to throw 'NullPointerException' and 'IllegalArgumentException' directly instead of encapsulating them in a try-catch block. It now also includes more informative error logging if the new application token generation fails. This change enhances readability of the code and makes error handling more straightforward. --- .../auth/service/impl/ApplicationService.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java index 19b794152..d00858654 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationService.java @@ -99,7 +99,7 @@ public List updateApplications(List applications) { return this.applicationRepo.saveAll(applications); } - public String refreshApplicationToken(String applicationId) { + public String refreshApplicationToken(String applicationId) throws NullPointerException, IllegalArgumentException { Optional application = applicationRepo.findById(UUID.fromString(applicationId)); if (application.isEmpty()) { @@ -108,16 +108,13 @@ public String refreshApplicationToken(String applicationId) { } String newApplicationToken = generateApplicationToken(application.orElse(null)); - try { - application.get().setToken( - newApplicationToken - ); - - this.applicationRepo.save(application.get()); - } catch (Exception e) { - logger.error("", e); + if (newApplicationToken == null) { + logger.error("refreshApplicationToken() failed to generate new application token for applicationId: {}", applicationId); + throw new NullPointerException("Failed to generate new application token for applicationId: " + applicationId); } + application.get().setToken(newApplicationToken); + this.applicationRepo.save(application.get()); return newApplicationToken; } From e1b3ece06cbbb60b69f83af32c64db79ed5ad951 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 10:17:49 -0400 Subject: [PATCH 179/222] Add ApplicationService unit tests This commit introduces unit tests for the ApplicationService class, verifying all main functionalities of the service including retrieving, adding, updating and deleting applications, as well as refreshing application tokens. These tests use mock repositories and utilities to isolate the behaviors of the service. --- .../service/impl/ApplicationServiceTest.java | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationServiceTest.java new file mode 100644 index 000000000..670e24a22 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ApplicationServiceTest.java @@ -0,0 +1,174 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.*; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +public class ApplicationServiceTest { + + @Mock + private ApplicationRepository applicationRepo; + + @Mock + private PrivilegeRepository privilegeRepo; + + @Mock + private JWTUtil jwtUtil; + + @InjectMocks + private ApplicationService applicationService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testGetApplicationByID_found() { + UUID id = UUID.randomUUID(); + Application app = new Application(); + app.setUuid(id); + when(applicationRepo.findById(id)).thenReturn(Optional.of(app)); + + Optional result = applicationService.getApplicationByID(id.toString()); + assertTrue(result.isPresent()); + assertSame(app, result.get()); + } + + @Test + public void testGetApplicationByID_notFound() { + UUID id = UUID.randomUUID(); + when(applicationRepo.findById(id)).thenReturn(Optional.empty()); + + Optional result = applicationService.getApplicationByID(id.toString()); + assertFalse(result.isPresent()); + } + + @Test + public void testGetApplicationByIdWithPrivileges_foundWithPrivileges() { + UUID id = UUID.randomUUID(); + Application app = new Application(); + app.setPrivileges(new HashSet<>()); + when(applicationRepo.findById(id)).thenReturn(Optional.of(app)); + when(privilegeRepo.findById(any())).thenReturn(Optional.of(new Privilege())); + + Optional result = applicationService.getApplicationByIdWithPrivileges(id.toString()); + assertTrue(result.isPresent()); + assertNotNull(result.get().getPrivileges()); + } + + @Test + public void testGetApplicationByIdWithPrivileges_notFound() { + UUID id = UUID.randomUUID(); + when(applicationRepo.findById(id)).thenReturn(Optional.empty()); + + Optional result = applicationService.getApplicationByIdWithPrivileges(id.toString()); + assertFalse(result.isPresent()); + } + + @Test + public void testGetAllApplications_empty() { + when(applicationRepo.findAll()).thenReturn(Collections.emptyList()); + + List result = applicationService.getAllApplications(); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetAllApplications_nonEmpty() { + when(applicationRepo.findAll()).thenReturn(Arrays.asList(new Application(), new Application())); + + List result = applicationService.getAllApplications(); + assertEquals(2, result.size()); + } + + @Test + public void testAddNewApplications_successfulWithToken() { + Application application = new Application(); + application.setUuid(UUID.randomUUID()); + List applications = Collections.singletonList(application); + when(applicationRepo.saveAll(applications)).thenReturn(applications); + when(jwtUtil.createJwtToken(any(), any(), any(), anyString(), anyLong())).thenReturn("token"); + + List savedApps = applicationService.addNewApplications(applications); + assertNotNull(savedApps); + assertFalse(savedApps.isEmpty()); + assertEquals("token", savedApps.getFirst().getToken()); + } + + @Test + public void testDeleteApplicationById_existing() { + UUID id = UUID.randomUUID(); + Application application = new Application(); + when(applicationRepo.findById(id)).thenReturn(Optional.of(application)); + + List remainingApps = applicationService.deleteApplicationById(id.toString()); + verify(applicationRepo, times(1)).delete(application); + assertNotNull(remainingApps); + } + + @Test(expected = IllegalArgumentException.class) + public void testDeleteApplicationById_notFound() { + UUID id = UUID.randomUUID(); + when(applicationRepo.findById(id)).thenReturn(Optional.empty()); + + applicationService.deleteApplicationById(id.toString()); + } + + @Test + public void testUpdateApplications() { + Application application = new Application(); + application.setUuid(UUID.randomUUID()); + List applications = Collections.singletonList(application); + when(applicationRepo.saveAll(anyList())).thenReturn(applications); + + List updatedApps = applicationService.updateApplications(applications); + assertNotNull(updatedApps); + assertEquals(applications.size(), updatedApps.size()); + } + + @Test + public void testRefreshApplicationToken_successful() { + UUID id = UUID.randomUUID(); + Application application = new Application(); + application.setUuid(id); + application.setName("App"); + when(applicationRepo.findById(id)).thenReturn(Optional.of(application)); + when(jwtUtil.createJwtToken(any(), any(), any(), anyString(), anyLong())).thenReturn("newToken"); + + String token = applicationService.refreshApplicationToken(id.toString()); + assertEquals("newToken", token); + } + + @Test(expected = IllegalArgumentException.class) + public void testRefreshApplicationToken_notFound() { + UUID id = UUID.randomUUID(); + when(applicationRepo.findById(id)).thenReturn(Optional.empty()); + + applicationService.refreshApplicationToken(id.toString()); + } + + @Test(expected = NullPointerException.class) + public void testRefreshApplicationToken_failedToGenerateToken() { + UUID id = UUID.randomUUID(); + Application application = new Application(); + application.setUuid(id); + when(applicationRepo.findById(id)).thenReturn(Optional.of(application)); + when(jwtUtil.createJwtToken(any(), any(), any(), anyString(), anyLong())).thenReturn(null); + + applicationService.refreshApplicationToken(id.toString()); + } +} From a51262f719838d0a125070f98f5804e58e953637 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 11:58:04 -0400 Subject: [PATCH 180/222] Refactor RestClientUtil to component The RestClientUtil class has been refactored as a Spring component. All static methods have been changed to instance methods to allow for Spring-managed dependency injection. A null check was added when receiving the ClientHttpRequestFactory in retrieveGetResponseWithRequestConfiguration method to fallback to the default method if no custom configuration is provided. --- .../dbmi/avillach/auth/utils/RestClientUtil.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java index 160aa96c9..219389b83 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/RestClientUtil.java @@ -4,6 +4,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.*; import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.stereotype.Component; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; @@ -14,12 +15,13 @@ import java.util.List; import java.util.function.Predicate; +@Component public class RestClientUtil { private static final Logger logger = LoggerFactory.getLogger(RestClientUtil.class); private static final RestTemplate restTemplate = new RestTemplate(); - public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders headers) { + public ResponseEntity retrieveGetResponse(String uri, HttpHeaders headers) { try { HttpEntity entity = new HttpEntity<>("parameters", headers); // Pass custom configuration to the RestTemplate @@ -31,7 +33,10 @@ public static ResponseEntity retrieveGetResponse(String uri, HttpHeaders } // Implement: The ability to set the timeout on the rest template for a given request. - public static ResponseEntity retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) { + public ResponseEntity retrieveGetResponseWithRequestConfiguration(String uri, HttpHeaders headers, ClientHttpRequestFactory requestFactory) { + if (requestFactory == null) { + return retrieveGetResponse(uri, headers); + } RestTemplate localRestTemplate = new RestTemplate(requestFactory); try { @@ -44,7 +49,7 @@ public static ResponseEntity retrieveGetResponseWithRequestConfiguration } } - public static String composeURL(String baseURL, String pathName, String query) { + public String composeURL(String baseURL, String pathName, String query) { try { URI uri = new URI(baseURL); List basePathComponents = Arrays.asList(uri.getPath().split("/")); @@ -60,7 +65,7 @@ public static String composeURL(String baseURL, String pathName, String query) { } } - public static ResponseEntity retrievePostResponse(String uri, HttpHeaders headers, String body) { + public ResponseEntity retrievePostResponse(String uri, HttpHeaders headers, String body) { try { logger.debug("HttpClientUtilSpring retrievePostResponse()"); headers.setContentType(MediaType.APPLICATION_JSON); From 1a24a14bdd3da234139cc861a6a5a56c8e0a2514 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 11:58:14 -0400 Subject: [PATCH 181/222] Refactor logging in BasicMailService Simplified logging in the BasicMailService class for sending access denied emails. Instead of concatenating information manually, the email is now simply fetched with userInfo.get("email").asText(). This change contributes to cleaner and more easily maintainable code. --- .../dbmi/avillach/auth/service/impl/BasicMailService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java index 2a4093e16..d596ba6cf 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java @@ -93,10 +93,8 @@ public void sendDeniedAccessEmail(JsonNode userInfo) throws MessagingException { if(deniedTemplate == null) { logger.debug("No template for Access Denied email, not sending"); } else { - logger.info("Sending 'Access Denied' email to " - + this.adminUsers - + ". User: " - + userInfo.get("email") != null ? userInfo.get("email").asText() : userInfo.get("user_id").asText()); + userInfo.get("email"); + logger.info(userInfo.get("email").asText()); ObjectMapper mapper = new ObjectMapper(); Map scope = mapper.convertValue(userInfo, Map.class); scope.put("systemName", this.systemName); From 610aa865e2035dbd5dabab8e125d502aa195018d Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 11:58:35 -0400 Subject: [PATCH 182/222] Inject RestClientUtil into FENCEAuthenticationService --- .../auth/service/impl/FENCEAuthenticationService.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java index fb30a0c29..7e1254911 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java @@ -42,6 +42,8 @@ public class FENCEAuthenticationService { private final UserService userService; + private final RestClientUtil restClientUtil; + private Application picSureApp; private Connection fenceConnection; private Map fenceMapping; @@ -61,7 +63,7 @@ public class FENCEAuthenticationService { @Autowired public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRepo, ConnectionRepository connectionRepo, AccessRuleRepository accessRuleRepo, ApplicationRepository applicationRepo, - PrivilegeRepository privilegeRepo, UserService userService, + PrivilegeRepository privilegeRepo, UserService userService, RestClientUtil restClientUtil, @Value("${application.idp.provider}") String idpProviderUri, @Value("${application.fence.client.id") String fenceClientId, @Value("${application.fence.client.secret}") String fenceClientSecret, @@ -77,6 +79,7 @@ public FENCEAuthenticationService(UserRepository userRepo, RoleRepository roleRe this.applicationRepo = applicationRepo; this.privilegeRepo = privilegeRepo; this.userService = userService; + this.restClientUtil = restClientUtil; idp_provider_uri = idpProviderUri; fence_client_id = fenceClientId; fence_client_secret = fenceClientSecret; @@ -103,7 +106,7 @@ private JsonNode getFENCEUserProfile(String access_token) { JsonNode fence_user_profile_response = null; try { - ResponseEntity response = RestClientUtil.retrieveGetResponse( + ResponseEntity response = this.restClientUtil.retrieveGetResponse( this.idp_provider_uri + "/user/user", headers ); @@ -139,7 +142,7 @@ private JsonNode getFENCEAccessToken(String fence_code) { JsonNode resp = null; try { - ResponseEntity response = RestClientUtil.retrievePostResponse( + ResponseEntity response = this.restClientUtil.retrievePostResponse( fence_token_url, headers, query_string From 79801d3e3eae089a42bc87e1e02b296766b9d4f6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 11:58:48 -0400 Subject: [PATCH 183/222] Update AuthenticationService and refactor RestClient The AuthenticationService class has been updated with some refactoring for better encapsulation and testing. The RestClientUtil is now an injected dependency, and the previously final string `deniedEmailEnabled` has been made mutable with an associated setter. The method retrieveUserInfo, which used RestClientUtil in a static context, now uses an instance method. --- .../auth/service/impl/AuthenticationService.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 9cabad8b3..934043394 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -47,16 +47,17 @@ public class AuthenticationService { private final UserService userService; private static final int AUTH_RETRY_LIMIT = 3; - private final String deniedEmailEnabled; + private String deniedEmailEnabled; private final String auth0host; private final ObjectMapper objectMapper = new ObjectMapper(); private final ConnectionRepository connectionRepository; + private final RestClientUtil restClientUtil; @Autowired public AuthenticationService(OauthUserMatchingService matchingService, UserRepository userRepository, BasicMailService basicMailService, UserService userService, - @Value("${application.denied.email.enabled}") String deniedEmailEnabled, @Value("${application.auth0.host}") String auth0host, ConnectionRepository connectionRepository) { + @Value("${application.denied.email.enabled}") String deniedEmailEnabled, @Value("${application.auth0.host}") String auth0host, ConnectionRepository connectionRepository, RestClientUtil restClientUtil) { this.matchingService = matchingService; this.userRepository = userRepository; this.basicMailService = basicMailService; @@ -64,6 +65,7 @@ public AuthenticationService(OauthUserMatchingService matchingService, UserRepos this.deniedEmailEnabled = deniedEmailEnabled; this.auth0host = auth0host; this.connectionRepository = connectionRepository; + this.restClientUtil = restClientUtil; } public ResponseEntity getToken(Map authRequest) throws IOException { @@ -119,7 +121,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce return PICSUREResponse.success(responseMap); } - private JsonNode retrieveUserInfo(String accessToken) throws IOException { + JsonNode retrieveUserInfo(String accessToken) throws IOException { String auth0UserInfoURI = this.auth0host + "/userinfo"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); @@ -129,10 +131,10 @@ private JsonNode retrieveUserInfo(String accessToken) throws IOException { for (int i = 1; i <= AUTH_RETRY_LIMIT && auth0Response == null; i++) { try { - ResponseEntity response = RestClientUtil.retrieveGetResponseWithRequestConfiguration( + ResponseEntity response = this.restClientUtil.retrieveGetResponseWithRequestConfiguration( auth0UserInfoURI, headers, - createRequestConfigWithCustomTimeout(2000) + this.createRequestConfigWithCustomTimeout(2000) ); auth0Response = objectMapper.readTree(response.getBody()); @@ -154,4 +156,8 @@ public ClientHttpRequestFactory createRequestConfigWithCustomTimeout(int timeout requestFactory.setReadTimeout(timeoutMs); return requestFactory; } + + public void setDeniedEmailEnabled(String deniedEmailEnabled) { + this.deniedEmailEnabled = deniedEmailEnabled; + } } From 576c7c17337367dbacb0d8e97594789ee72ea04c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 11:59:04 -0400 Subject: [PATCH 184/222] Add AuthenticationService test cases --- .../impl/AuthenticationServiceTest.java | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java new file mode 100644 index 000000000..0c9c743b1 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java @@ -0,0 +1,175 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +public class AuthenticationServiceTest { + + @Mock + private OauthUserMatchingService matchingService; + @Mock + private UserRepository userRepository; + @Mock + private BasicMailService basicMailService; + @Mock + private UserService userService; + @Mock + private ConnectionRepository connectionRepository; + @Mock + private RestClientUtil restClientUtil; + + @InjectMocks + private AuthenticationService authenticationService; + + private final String accessToken = "dummyAccessToken"; + private final String redirectURI = "http://dummyRedirectUri.com"; + private final String userId = "user123"; + private final String connectionId = "conn123"; + private Map authRequest; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + authRequest = new HashMap<>(); + authRequest.put("access_token", accessToken); + authRequest.put("redirectURI", redirectURI); + } + + // Tests missing parameters in the authentication request + @Test(expected = IllegalArgumentException.class) + public void testGetToken_MissingParameters() throws IOException { + authenticationService.getToken(new HashMap<>()); // Empty map should trigger the exception + } + + // Tests the failure in retrieving user information, expecting an IOException to be converted into a NotAuthorizedException + @Test(expected = NotAuthorizedException.class) + public void testGetToken_UserInfoRetrievalFails() throws IOException { + when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class))) + .thenThrow(new NotAuthorizedException("Failed to retrieve user info")); + authenticationService.getToken(authRequest); + } + + // Tests the scenario where the user ID is not found in the user info retrieved + @Test(expected = NotAuthorizedException.class) + public void testGetToken_NoUserIdInUserInfo() throws IOException { + when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any())) + .thenReturn(new ResponseEntity<>("{}", HttpStatus.OK)); + authenticationService.getToken(authRequest); + } + + // Tests a successful token retrieval scenario + @Test + public void testGetToken_Successful() throws Exception { + setupSuccessfulTokenRetrievalScenario(); + + // return null for matching user + when(matchingService.matchTokenToUser(any())).thenReturn(null); + + ResponseEntity response = authenticationService.getToken(authRequest); + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + } + + // Additional test to handle retries in user info retrieval + @Test + public void testRetrieveUserInfo_WithRetries() throws Exception { + when(this.restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(), any())) + .thenThrow(new RuntimeException("Network error")) + .thenReturn(new ResponseEntity<>("{}", HttpStatus.OK)); + // Assuming retrieveUserInfo is accessible, or using reflection if it is private + JsonNode result = authenticationService.retrieveUserInfo(accessToken); + assertNotNull(result); + } + + // Tests matching a token to a user when no existing user is found and an attempt to create a user fails + @Test(expected = NotAuthorizedException.class) + public void testGetToken_NoUserMatchingAndCreationFails() throws Exception { + setupNoUserMatchScenario(); + authenticationService.getToken(authRequest); + } + + // Test scenario where denied access email is triggered + @Test + public void testGetToken_SendDeniedAccessEmail() throws Exception { + setupDeniedEmailScenario(); + this.authenticationService.setDeniedEmailEnabled("true"); + try { + authenticationService.getToken(authRequest); + } catch (Exception e) { + verify(basicMailService).sendDeniedAccessEmail(any()); + } + } + + private void setupSuccessfulTokenRetrievalScenario() throws IOException { + this.authenticationService.setDeniedEmailEnabled("false"); + JsonNode mockUserInfo = mock(JsonNode.class); + when(mockUserInfo.get("user_id")).thenReturn(mock(JsonNode.class)); + when(mockUserInfo.get("user_id").asText()).thenReturn(userId); + when(mockUserInfo.get("identities")).thenReturn(mock(JsonNode.class)); + when(mockUserInfo.get("identities").get(0)).thenReturn(mock(JsonNode.class)); + when(mockUserInfo.get("identities").get(0).get("connection")).thenReturn(mock(JsonNode.class)); + when(mockUserInfo.get("identities").get(0).get("connection").asText()).thenReturn(connectionId); + + String validJson = "{" + + "\"user_id\": \"" + userId + "\"," + + "\"identities\": [" + + " {\"connection\": \"" + connectionId + "\"}" + + "]" + + "}"; + + when(restClientUtil.retrieveGetResponseWithRequestConfiguration(anyString(), any(HttpHeaders.class), any(ClientHttpRequestFactory.class))) + .thenReturn(new ResponseEntity<>(validJson, HttpStatus.OK)); + + // Create a test user + Connection connection = new Connection(); + connection.setId(connectionId); + + User user = new User(); + user.setSubject(userId); + user.setConnection(connection); + user.setUuid(UUID.randomUUID()); + + when(connectionRepository.findById(anyString())).thenReturn(Optional.of(connection)); + when(matchingService.matchTokenToUser(any())).thenReturn(user); + when(userRepository.findBySubjectAndConnection(anyString(), any(Connection.class))).thenReturn(user); + when(userService.getUserProfileResponse(any())).thenReturn(new HashMap<>()); + } + + private void setupNoUserMatchScenario() throws IOException { + setupSuccessfulTokenRetrievalScenario(); + + when(matchingService.matchTokenToUser(any())).thenReturn(null); + when(userRepository.findBySubjectAndConnection(anyString(), any(Connection.class))).thenReturn(null); + } + + private void setupDeniedEmailScenario() throws Exception { + setupNoUserMatchScenario(); + doThrow(new jakarta.mail.MessagingException("Failed sending email")).when(basicMailService).sendDeniedAccessEmail(any()); + } + +} \ No newline at end of file From ea1ca4fe4d0f796e7d8f449a825664dcb4d64496 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 12:05:23 -0400 Subject: [PATCH 185/222] Add tests for ConnectionWebService --- .../impl/ConnectionWebServiceTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebServiceTest.java new file mode 100644 index 000000000..b1a0f108e --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/ConnectionWebServiceTest.java @@ -0,0 +1,114 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.*; + +public class ConnectionWebServiceTest { + + @Mock + private ConnectionRepository connectionRepo; + + @InjectMocks + private ConnectionWebService service; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + private Connection createMockConnection(String id) { + Connection conn = new Connection(); + conn.setId(id); + conn.setLabel("Test Label"); + conn.setSubPrefix("Test SubPrefix"); + conn.setRequiredFields(String.valueOf(Arrays.asList("field1", "field2"))); + return conn; + } + + @Test + public void testAddConnection_Success() { + Connection conn = createMockConnection(UUID.randomUUID().toString()); + when(connectionRepo.findById(anyString())).thenReturn(Optional.empty()); + when(connectionRepo.saveAll(anyList())).thenReturn(Collections.singletonList(conn)); + + List result = service.addConnection(Collections.singletonList(conn)); + assertFalse(result.isEmpty()); + assertEquals(1, result.size()); + verify(connectionRepo, times(1)).saveAll(anyList()); + } + + @Test + public void testAddConnection_FailureDueToNullFields() { + Connection conn = new Connection(); // missing required fields + assertThrows(IllegalArgumentException.class, () -> service.addConnection(Collections.singletonList(conn))); + } + + @Test + public void testAddConnection_FailureDueToDuplicateId() { + Connection conn = createMockConnection("123"); + when(connectionRepo.findById("123")).thenReturn(Optional.of(conn)); + + assertThrows(IllegalArgumentException.class, () -> service.addConnection(Collections.singletonList(conn))); + } + + @Test + public void testGetConnectionById_Success() { + Connection conn = createMockConnection("123"); + when(connectionRepo.findById("123")).thenReturn(Optional.of(conn)); + + Connection result = service.getConnectionById("123"); + assertNotNull(result); + assertEquals("123", result.getId()); + } + + @Test + public void testGetConnectionById_NotFound() { + when(connectionRepo.findById("123")).thenReturn(Optional.empty()); + assertThrows(IllegalArgumentException.class, () -> service.getConnectionById("123")); + } + + @Test + public void testGetAllConnections() { + Connection conn1 = createMockConnection("123"); + Connection conn2 = createMockConnection("124"); + when(connectionRepo.findAll()).thenReturn(Arrays.asList(conn1, conn2)); + + List results = service.getAllConnections(); + assertNotNull(results); + assertEquals(2, results.size()); + } + + @Test + public void testUpdateConnections() { + Connection conn1 = createMockConnection("123"); + when(connectionRepo.saveAll(anyList())).thenReturn(Collections.singletonList(conn1)); + + List results = service.updateConnections(Collections.singletonList(conn1)); + assertNotNull(results); + assertEquals(1, results.size()); + assertEquals("123", results.get(0).getId()); + } + + @Test + public void testRemoveConnectionById() { + doNothing().when(connectionRepo).deleteById("123"); + when(connectionRepo.findAll()).thenReturn(Collections.emptyList()); + + List results = service.removeConnectionById("123"); + assertTrue(results.isEmpty()); + verify(connectionRepo, times(1)).deleteById("123"); + verify(connectionRepo, times(1)).findAll(); + } + +} \ No newline at end of file From d5dc053b99e9e6c0e6d38e5534bc4b951acb5097 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 12:12:11 -0400 Subject: [PATCH 186/222] Add CustomUserDetailService unit tests --- .../impl/CustomUserDetailServiceTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailServiceTest.java new file mode 100644 index 000000000..b4cb33579 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/CustomUserDetailServiceTest.java @@ -0,0 +1,82 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import java.util.Optional; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class CustomUserDetailServiceTest { + + @Mock + private UserService userService; + + @Mock + private ApplicationService applicationService; + + @InjectMocks + private CustomUserDetailService customUserDetailService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void loadUserByUsername_withApplication_success() { + String applicationName = "testApp"; + Application application = new Application(); + application.setPrivileges(null); // Set the required fields as needed + when(applicationService.getApplicationByIdWithPrivileges(applicationName)).thenReturn(Optional.of(application)); + + UserDetails userDetails = customUserDetailService.loadUserByUsername("application:" + applicationName); + assertNotNull(userDetails); + assertTrue(userDetails instanceof CustomApplicationDetails); + } + + @Test + public void loadUserByUsername_withApplication_notFound() { + when(applicationService.getApplicationByIdWithPrivileges("invalidApp")).thenReturn(Optional.empty()); + + assertThrows(UsernameNotFoundException.class, () -> + customUserDetailService.loadUserByUsername("application:invalidApp")); + } + + @Test + public void loadUserByUsername_withUser_success() { + String username = "user@example.com"; + User user = new User(); + // Set roles for the user + Role role = new Role(); + role.setName(SecurityRoles.ADMIN.getRole()); + user.setRoles(Set.of(role)); + + when(userService.findBySubject(username)).thenReturn(user); + + UserDetails userDetails = customUserDetailService.loadUserByUsername(username); + assertNotNull(userDetails); + assertTrue(userDetails instanceof CustomUserDetails); + } + + @Test + public void loadUserByUsername_withUser_notFound() { + when(userService.findBySubject("nonexistent@example.com")).thenReturn(null); + + assertThrows(UsernameNotFoundException.class, () -> + customUserDetailService.loadUserByUsername("nonexistent@example.com")); + } +} From ef66fc711f915a4ba30f2f1bb50364f1200d99b0 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 14:39:53 -0400 Subject: [PATCH 187/222] Refactor Auth0MatchingServiceTest class location and minor cleanup --- .../service/impl}/Auth0MatchingServiceTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) rename pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/{ => auth/service/impl}/Auth0MatchingServiceTest.java (96%) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java similarity index 96% rename from pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java rename to pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java index 31292eca9..043c9674d 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java @@ -1,4 +1,4 @@ -package edu.harvard.hms.dbmi.avillach; +package edu.harvard.hms.dbmi.avillach.auth.service.impl; import com.auth0.exception.Auth0Exception; import com.fasterxml.jackson.core.type.TypeReference; @@ -9,17 +9,11 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.OauthUserMatchingService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserMetadataMappingService; -import edu.harvard.hms.dbmi.avillach.auth.service.impl.UserService; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.junit.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,7 +53,7 @@ public class Auth0MatchingServiceTest { ObjectMapper mapper = new ObjectMapper(); @Before - public void setUp() throws Auth0Exception { + public void setUp() { MockitoAnnotations.initMocks(this); //Instead of calling the database doAnswer(invocation -> (listUnmatchedByConnectionIdMock(invocation.getArgument(0)))). @@ -208,7 +202,7 @@ public JsonNode mockAuthAPIUserInfo(String accessToken) throws IOException { } public Optional mockConnection(String id) { - log.info("Mocking connection with id: " + id); + log.info("Mocking connection with id: {}", id); return Optional.of(new Connection().setId(id)); } From d137006b85a5e54f732d3acb7173b274126e3266 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 8 May 2024 17:11:04 -0400 Subject: [PATCH 188/222] Add setters for unit testing --- .../harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java index ae9244093..9611baaab 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/utils/JWTUtil.java @@ -118,4 +118,14 @@ public static Optional getTokenFromAuthorizationHeader(String authorizat return Optional.of(authorizationHeader.substring("Bearer".length()).trim()); } + + public String setClientSecret(String clientSecret) { + return clientSecret; + } + + public boolean setClientSecretIsBase64(boolean clientSecretIsBase64) { + return clientSecretIsBase64; + } + + } From 68bdeb26de80104121028f123f6a1f7bdbcbd6af Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 9 May 2024 13:30:34 -0400 Subject: [PATCH 189/222] Add TokenService unit tests A new file, TokenServiceTest.java, has been created inside the test directory for the authentication service. This class includes various unit tests that validate the functionality of the TokenService class. It ensures the correct behavior under multiple scenarios like valid token, null token, empty token, valid token with no associated user, and more. --- .../auth/service/impl/TokenServiceTest.java | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java new file mode 100644 index 000000000..b9e05e312 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java @@ -0,0 +1,314 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; + +import java.security.SecureRandom; +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TokenServiceTest { + + @Mock + private AuthorizationService authorizationService; + + @Mock + private UserRepository userRepository; + + private JWTUtil jwtUtil; + + @Mock + private SecurityContext securityContext; + + private TokenService tokenService; + + private static long testTokenExpiration = 1000L * 60 * 60; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Authentication authentication = mock(Authentication.class); + + SecurityContextHolder.setContext(securityContext); + when(securityContext.getAuthentication()).thenReturn(authentication); + jwtUtil = new JWTUtil(generate256Base64Secret(), true); + tokenService = new TokenService(authorizationService, userRepository, 1000L * 60 * 60, jwtUtil); + } + + @Test + public void testInspectToken_withValidToken() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + System.out.println(token); + inputMap.put("token", token); + + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + Map response = tokenService.inspectToken(inputMap); + assertNull(response.get("message")); + assertTrue((Boolean) response.get("active")); + assertEquals(user.getSubject(), response.get("sub")); + assertEquals(application.getPrivileges(), response.get("privileges")); + } + + @Test + public void testInspectToken_withNullToken() { + Map inputMap = new HashMap<>(); + inputMap.put("token", null); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("Token not found", response.get("message")); + } + + @Test + public void testInspectToken_withEmptyToken() { + Map inputMap = new HashMap<>(); + inputMap.put("token", ""); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("Token not found", response.get("message")); + } + + @Test(expected = RuntimeException.class) + public void testInspectToken_withUserPrincipal() { + User user = createTestUser(); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + inputMap.put("token", token); + + configureUserSecurityContext(); + Map tokenInspection = tokenService.inspectToken(inputMap); + assertNotNull(tokenInspection.get("message")); + } + + + @Test + public void testInspectToken_withValidToken_withNoAssociatedUser() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + System.out.println(token); + inputMap.put("token", token); + + when(userRepository.findBySubject(user.getSubject())).thenReturn(null); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("user doesn't exist", response.get("message")); + } + + @Test(expected = NullPointerException.class) + public void testInspectToken_withNoApplicationInSecurityContext() { + configureApplicationSecurityContext(null); + + User user = createTestUser(); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + System.out.println(token); + inputMap.put("token", token); + + when(userRepository.findBySubject(user.getSubject())).thenReturn(null); + + tokenService.inspectToken(inputMap); + } + + /* + TODO: Investigate if this is a scenario we should be stopping. This test is successful, but the user has + no token associated with it. The code should at least verify hat the user has the token associated with it + that is being sent + */ + @Test + public void testLongTermInspectToken() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), + testTokenExpiration + ); + + inputMap.put("token", token); + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + Map response = tokenService.inspectToken(inputMap); + assertNull(response.get("message")); + assertTrue((Boolean) response.get("active")); + assertEquals(AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), response.get("sub")); + assertEquals(application.getPrivileges(), response.get("privileges")); + } + + @Test + public void testLongTermInspectToken_withUserTokenNotMatching() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + application.setPrivileges(user.getTotalPrivilege()); + inputMap.put("token", token); + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("Cannot find matched long term token, your token might have been refreshed.", response.get("message")); + } + + private User createTestUser() { + User user = new User(); + user.setUuid(UUID.randomUUID()); + user.setRoles(Collections.singleton(createTestRole())); + user.setSubject("TEST_SUBJECT"); + user.setEmail("test@email.com"); + user.setAcceptedTOS(new Date()); + user.setActive(true); + + return user; + } + + private Role createTestRole() { + Role role = new Role(); + role.setName(SecurityRoles.ADMIN.name()); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createTestPrivilege())); + return role; + } + + private Privilege createTestPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName("TEST_PRIVILEGE"); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + + private Application createTestApplication() { + Application application = new Application(); + application.setUuid(UUID.randomUUID()); + application.setName("TEST_APPLICATION"); + application.setToken(createValidApplicationTestToken(application)); + application.setPrivileges(new HashSet<>()); + return application; + } + + private void configureUserSecurityContext() { + User user = createTestUser(); + CustomUserDetails customUserDetails = new CustomUserDetails(user); + + // configure security context + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + private void configureApplicationSecurityContext(Application application) { + CustomApplicationDetails customApplicationDetails = new CustomApplicationDetails(application); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customApplicationDetails, null, customApplicationDetails.getAuthorities()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(mock(jakarta.servlet.http.HttpServletRequest.class))); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + private String createValidApplicationTestToken(Application application) { + return this.jwtUtil.createJwtToken( + null, null, + new HashMap<>( + Map.of( + "user_id", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getName() + ) + ), + AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getUuid().toString(), 365L * 1000 * 60 * 60 * 24); + } + + /** + * Do not use this method in production code. This is only for testing purposes. + * + * @return a 256-bit base64 encoded secret + */ + private String generate256Base64Secret() { + SecureRandom random = new SecureRandom(); + byte[] secret = new byte[32]; + random.nextBytes(secret); + return Base64.getEncoder().encodeToString(secret); + } +} \ No newline at end of file From 9000a7dffc88aacb47c90c9719b3b24dc1b3a7b9 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Thu, 9 May 2024 13:31:18 -0400 Subject: [PATCH 190/222] Remove unnecessary if statement --- .../avillach/auth/service/impl/TokenService.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 05d5460f8..39955bff5 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -6,7 +6,6 @@ import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.TokenInspection; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; @@ -76,7 +75,7 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg TokenInspection tokenInspection = new TokenInspection(); String token = (String) inputMap.get("token"); if (token == null || token.isEmpty()) { - logger.error("Token - " + token + " is blank"); + logger.error("Token - {} is blank", token); tokenInspection.setMessage("Token not found"); return tokenInspection; } @@ -108,12 +107,11 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg SecurityContext securityContext = SecurityContextHolder.getContext(); String principalName = securityContext.getAuthentication().getName(); logger.error("{} - {} - is trying to use token introspection endpoint, but it is not an application", principalName, principalName); - throw new IllegalAccessException("The application token does not associate with an application but " - + principalName); + throw new IllegalAccessException("The application token does not associate with an application but " + principalName); } // application null check should be finished when application token goes through the JWTFilter authentication process, - // here we just double check it to prevent a null application object goes further. + // here we just double-check it to prevent a null application object goes further. if (application == null) { logger.error("_inspectToken() There is no application in securityContext, which shall not be."); throw new NullPointerException("Inner application error, please ask admin to check the log."); @@ -137,14 +135,13 @@ private TokenInspection _inspectToken(Map inputMap) throws Illeg } user = this.userRepository.findBySubject(subject); - logger.info("_inspectToken() user with subject - {} - exists in database", subject); + logger.info("_inspectToken() does user with subject - {} - exists in database", subject); if (user == null) { logger.error("_inspectToken() could not find user with subject {}", subject); tokenInspection.setMessage("user doesn't exist"); return tokenInspection; } - //Essentially we want to return jws.getBody() with an additional active: true field //only under certain circumstances, the token will return active boolean isAuthorizationPassed = false; @@ -213,10 +210,6 @@ public Map refreshToken(String authorizationHeader) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); - if (!(principal instanceof User)) { - logger.error("refreshToken() Security context didn't have a user stored."); - } - if (!(principal instanceof User user)) { logger.error("refreshToken() Principal is not an instance of User."); throw new NotAuthorizedException("User not found"); From 469b4bd678f30a858163a3a78991ece76f9b9aa5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Fri, 10 May 2024 08:47:20 -0400 Subject: [PATCH 191/222] Update token handling and associated tests --- .../auth/service/impl/TokenService.java | 4 +- .../auth/service/impl/TokenServiceTest.java | 107 +++++++++++++++++- 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 39955bff5..3026a1a2b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -5,6 +5,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.model.TokenInspection; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; @@ -210,11 +211,12 @@ public Map refreshToken(String authorizationHeader) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); - if (!(principal instanceof User user)) { + if (!(principal instanceof CustomUserDetails customUserDetails)) { logger.error("refreshToken() Principal is not an instance of User."); throw new NotAuthorizedException("User not found"); } + User user = customUserDetails.getUser(); if (user.getUuid() == null) { logger.error("refreshToken() Stored user doesn't have a uuid."); return Map.of("error", "Inner application error, please contact admin."); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java index b9e05e312..d527a3de7 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java @@ -118,7 +118,7 @@ public void testInspectToken_withUserPrincipal() { ); inputMap.put("token", token); - configureUserSecurityContext(); + configureUserSecurityContext(user); Map tokenInspection = tokenService.inspectToken(inputMap); assertNotNull(tokenInspection.get("message")); } @@ -209,7 +209,7 @@ public void testLongTermInspectToken() { } @Test - public void testLongTermInspectToken_withUserTokenNotMatching() { + public void testLongTermInspectToken_withUserTokenCompromised() { Application application = createTestApplication(); configureApplicationSecurityContext(application); @@ -228,6 +228,15 @@ public void testLongTermInspectToken_withUserTokenNotMatching() { testTokenExpiration ); + String userToken = jwtUtil.createJwtToken( + "whatever1", // Different id + "edu.harvard.hms.dbmi.psama", + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), + testTokenExpiration + ); + user.setToken(userToken); + // User privileges should be a subset of the application's privileges application.setPrivileges(user.getTotalPrivilege()); inputMap.put("token", token); @@ -237,6 +246,96 @@ public void testLongTermInspectToken_withUserTokenNotMatching() { assertEquals("Cannot find matched long term token, your token might have been refreshed.", response.get("message")); } + @Test + public void testLongTermInspectToken_withUserRolesNull() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), + testTokenExpiration + ); + user.setToken(token); + + // User privileges should be a subset of the application's privileges + application.setPrivileges(user.getTotalPrivilege()); + user.setRoles(null); + inputMap.put("token", token); + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("User doesn't have enough privileges.", response.get("message")); + } + + @Test + public void testLongTermInspectToken_withUserTokenCompromised_() { + Application application = createTestApplication(); + configureApplicationSecurityContext(application); + + User user = createTestUser(); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + Map inputMap = new HashMap<>(); + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + AuthNaming.LONG_TERM_TOKEN_PREFIX + "|" + claims.get("sub").toString(), + testTokenExpiration + ); + + user.setToken(token); + + // User privileges should be a subset of the application's privileges + application.setPrivileges(user.getTotalPrivilege()); + user.setRoles(null); + inputMap.put("token", token); + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + Map response = tokenService.inspectToken(inputMap); + assertEquals("User doesn't have enough privileges.", response.get("message")); + } + + @Test + public void testRefreshToken() { + User user = createTestUser(); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + Map response = tokenService.refreshToken(authorizationHeader); + assertNotNull(response.get("token")); + assertNotNull(response.get("expirationDate")); + } + private User createTestUser() { User user = new User(); user.setUuid(UUID.randomUUID()); @@ -273,10 +372,8 @@ private Application createTestApplication() { return application; } - private void configureUserSecurityContext() { - User user = createTestUser(); + private void configureUserSecurityContext(User user) { CustomUserDetails customUserDetails = new CustomUserDetails(user); - // configure security context UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); when(securityContext.getAuthentication()).thenReturn(authentication); From f622c954c7b3b09e101ab9068010ebe5fbe87ab5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 10:12:21 -0400 Subject: [PATCH 192/222] Add additional token refresh tests and improve error handling Added several test cases centered around the token refresh method in the TokenService class. Additionally, the error handling in TokenService has been improved for a multitude of conditions including when user details are missing or undefined. Other conditions accounted for involve discrepancies between loaded users versus present claims, and issues around malformed or invalid tokens. --- .../auth/model/CustomUserDetails.java | 11 +- .../auth/service/impl/TokenService.java | 9 +- .../auth/service/impl/TokenServiceTest.java | 185 +++++++++++++++++- 3 files changed, 195 insertions(+), 10 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index 35873dfce..e99bcb40a 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -4,6 +4,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; import java.util.Collection; public class CustomUserDetails implements UserDetails { @@ -13,9 +14,13 @@ public class CustomUserDetails implements UserDetails { public CustomUserDetails(User user) { this.user = user; - this.authorities = user.getRoles().stream() - .map(role -> (GrantedAuthority) role::getName) - .toList(); + if (user != null) { + this.authorities = user.getRoles().stream() + .map(role -> (GrantedAuthority) role::getName) + .toList(); + } else { + this.authorities = new ArrayList<>(); + } } @Override diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 3026a1a2b..96cfc9be2 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -217,7 +217,7 @@ public Map refreshToken(String authorizationHeader) { } User user = customUserDetails.getUser(); - if (user.getUuid() == null) { + if (user == null || user.getUuid() == null) { logger.error("refreshToken() Stored user doesn't have a uuid."); return Map.of("error", "Inner application error, please contact admin."); } @@ -236,6 +236,7 @@ public Map refreshToken(String authorizationHeader) { String subject = loadUser.get().getSubject(); if (subject == null || subject.isEmpty()) { logger.error("refreshToken() subject doesn't exist in the user."); + return Map.of("error", "Inner application error, please contact admin."); } // parse origin token @@ -243,16 +244,12 @@ public Map refreshToken(String authorizationHeader) { try { String token = JWTUtil.getTokenFromAuthorizationHeader(authorizationHeader).orElseThrow(() -> new NotAuthorizedException("Token not found")); jws = this.jwtUtil.parseToken(token); - } catch (NotAuthorizedException ex) { return Map.of("error", "Cannot parse original token"); } Claims claims = jws.getPayload(); - - // just check if the subject is along with the database record, - // just in case something has changed in middle - if (StringUtils.isNotBlank(subject) && !subject.equals(claims.getSubject())) { + if (!subject.equals(claims.getSubject())) { logger.error("refreshToken() user subject is not the same as the subject of the input token"); return Map.of("error", "Inner application error, try again or contact admin."); } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java index d527a3de7..8d7bb3bb2 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenServiceTest.java @@ -5,6 +5,7 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; import edu.harvard.hms.dbmi.avillach.auth.model.CustomApplicationDetails; import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; @@ -170,7 +171,6 @@ public void testInspectToken_withNoApplicationInSecurityContext() { inputMap.put("token", token); when(userRepository.findBySubject(user.getSubject())).thenReturn(null); - tokenService.inspectToken(inputMap); } @@ -336,6 +336,189 @@ public void testRefreshToken() { assertNotNull(response.get("expirationDate")); } + @Test(expected = NotAuthorizedException.class) + public void testRefreshToken_withoutCustomUserDetailsInAsPrincipal() { + User user = createTestUser(); +// configureUserSecurityContext(user); // This is commented out to simulate the absence of CustomUserDetails + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + tokenService.refreshToken(authorizationHeader); + } + + @Test + public void testRefreshToken_withoutUserInCustomUserDetails() { + User user = createTestUser(); + configureUserSecurityContext(null); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + Map response = tokenService.refreshToken(authorizationHeader); + assertEquals("Inner application error, please contact admin.", response.get("error")); + } + + @Test + public void testRefreshToken_withUserWithoutUUID_InCustomUserDetails() { + User user = createTestUser(); + user.setUuid(null); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + Map response = tokenService.refreshToken(authorizationHeader); + assertEquals("Inner application error, please contact admin.", response.get("error")); + } + + @Test(expected = NotAuthorizedException.class) + public void testRefreshToken_whereUserNotExists() { + User user = createTestUser(); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.empty()); + + String authorizationHeader = "Bearer " + token; + tokenService.refreshToken(authorizationHeader); + } + + @Test(expected = NotAuthorizedException.class) + public void testRefreshToken_whereUserNotActive() { + User user = createTestUser(); + user.setActive(false); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + tokenService.refreshToken(authorizationHeader); + } + + @Test + public void testRefreshToken_whereUserSubjectNull() { + User user = createTestUser(); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + user.setSubject(null); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + Map response = tokenService.refreshToken(authorizationHeader); + assertEquals("Inner application error, please contact admin.", response.get("error")); + } + + @Test + public void testRefreshToken_whereUserSubjectHasChanged() { + User user = createTestUser(); + configureUserSecurityContext(user); + user.setSubject(user.getSubject()); + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + testTokenExpiration + ); + user.setSubject("CHANGED_SUBJECT"); + + // User privileges should be a subset of the application's privileges + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + String authorizationHeader = "Bearer " + token; + Map response = tokenService.refreshToken(authorizationHeader); + assertEquals("Inner application error, try again or contact admin.", response.get("error")); + } + private User createTestUser() { User user = new User(); user.setUuid(UUID.randomUUID()); From 4584485bbc667cf730238127910fba8d371305b4 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 13:04:51 -0400 Subject: [PATCH 193/222] Add UserServiceTest for auth service testing --- .../auth/service/impl/UserServiceTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java new file mode 100644 index 000000000..09d954c38 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -0,0 +1,82 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; +import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +public class UserServiceTest { + + + @Mock + private BasicMailService basicMailService; + @Mock + private TOSService tosService; + @Mock + private UserRepository userRepository; + @Mock + private ConnectionRepository connectionRepository; + @Mock + private ApplicationRepository applicationRepository; + @Mock + private RoleService roleService; + @Mock + private JWTUtil jwtUtil; + + private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour + private String applicationUUID; + private final long longTermTokenExpirationTime = 2592000000L; + + private UserService userService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + applicationUUID = UUID.randomUUID().toString(); + userService = new UserService( + basicMailService, + tosService, + userRepository, + connectionRepository, + applicationRepository, + roleService, + defaultTokenExpirationTime, + applicationUUID, + longTermTokenExpirationTime, + jwtUtil); + } + + @Test + public void testGetUserById_found() { + UUID testId = UUID.randomUUID(); + User mockUser = new User(); + mockUser.setUuid(testId); + + when(userRepository.findById(testId)).thenReturn(Optional.of(mockUser)); + + User result = userService.getUserById(testId.toString()); + assertNotNull(result); + assertEquals(testId, result.getUuid()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetUserById_notFound() { + UUID testId = UUID.randomUUID(); + when(userRepository.findById(testId)).thenReturn(Optional.empty()); + + userService.getUserById(testId.toString()); + } + +} \ No newline at end of file From 4bc6e5395a26434c87fdf1103707e2fd5f87cc94 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 15:58:31 -0400 Subject: [PATCH 194/222] Refactor UserService to handle absent roles --- .../hms/dbmi/avillach/auth/service/impl/UserService.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index 081f4cf74..dc15d73b9 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -244,7 +244,14 @@ private void checkAssociation(List users) { for (User user : users) { if (user.getRoles() != null) { Set roles = new HashSet<>(); - user.getRoles().forEach(t -> this.roleService.addObjectToSet(roles, t)); + user.getRoles().forEach(t -> { + Optional role = this.roleService.getRoleById(t.getUuid()); + if (role.isEmpty()) { + throw new RuntimeException("Role not found - uuid: " + t.getUuid().toString()); + } + roles.add(role.get()); + }); + user.setRoles(roles); } From 19e9ec632933afa929ad99629c42929168354ea2 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 15:58:46 -0400 Subject: [PATCH 195/222] Add getRoleById method in RoleService --- .../harvard/hms/dbmi/avillach/auth/service/RoleService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index 0dec11b67..7f54cc625 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -34,6 +34,10 @@ public Optional getRoleById(String roleId) { return roleRepository.findById(UUID.fromString(roleId)); } + public Optional getRoleById(UUID roleId) { + return roleRepository.findById(roleId); + } + public List getAllRoles() { return roleRepository.findAll(); } From 16267b0db959ea6121be68c9bd5caa5d39af152e Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 15:59:09 -0400 Subject: [PATCH 196/222] Update UserServiceTest with additional test methods --- .../auth/service/impl/UserServiceTest.java | 326 +++++++++++++++++- 1 file changed, 320 insertions(+), 6 deletions(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index 09d954c38..86c77dd9d 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -1,24 +1,37 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; import edu.harvard.hms.dbmi.avillach.auth.repository.ApplicationRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import jakarta.mail.MessagingException; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpHeaders; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import static org.junit.Assert.*; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; public class UserServiceTest { + @Mock + private SecurityContext securityContext; @Mock private BasicMailService basicMailService; @@ -34,6 +47,8 @@ public class UserServiceTest { private RoleService roleService; @Mock private JWTUtil jwtUtil; + @Mock + private RoleRepository roleRepository; private static final long defaultTokenExpirationTime = 1000L * 60 * 60; // 1 hour private String applicationUUID; @@ -44,6 +59,11 @@ public class UserServiceTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + + Authentication authentication = mock(Authentication.class); + SecurityContextHolder.setContext(securityContext); + when(securityContext.getAuthentication()).thenReturn(authentication); + applicationUUID = UUID.randomUUID().toString(); userService = new UserService( basicMailService, @@ -58,13 +78,26 @@ public void setUp() { jwtUtil); } + @Test + public void testGetUserProfileResponse() { + User user = createTestUser(); + when(userRepository.findByEmail(user.getEmail())).thenReturn(user); + + HashMap claims = new HashMap<>(); + claims.put("email", user.getEmail()); + claims.put("sub", user.getSubject()); + + HashMap result = userService.getUserProfileResponse(claims); + assertNotNull(result); + } + @Test public void testGetUserById_found() { UUID testId = UUID.randomUUID(); - User mockUser = new User(); - mockUser.setUuid(testId); + User user = createTestUser(); + user.setUuid(testId); - when(userRepository.findById(testId)).thenReturn(Optional.of(mockUser)); + when(userRepository.findById(testId)).thenReturn(Optional.of(user)); User result = userService.getUserById(testId.toString()); assertNotNull(result); @@ -79,4 +112,285 @@ public void testGetUserById_notFound() { userService.getUserById(testId.toString()); } + @Test + public void testGetAllUsers() { + User user = createTestUser(); + when(userRepository.findAll()).thenReturn(List.of(user)); + + Iterable result = userService.getAllUsers(); + assertNotNull(result); + assertTrue(result.iterator().hasNext()); + } + + @Test + public void testAddUser() { + User user = createTestUser(); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + List result = userService.addUser(List.of(user)); + assertNotNull(result); + } + + @Test + public void testAddUsers() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + List result = userService.addUsers(List.of(user)); + System.out.println(result); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(user, result.getFirst()); + } + + @Test + public void testAddUsers_SuperAdminRole() { + User user = createTestUser(); + user.getRoles().add(createSuperAdminRole()); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + List result = userService.addUsers(List.of(user)); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(user, result.getFirst()); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddUsers_SuperAdminRole_withoutNecessaryPrivileges() { + User user = createTestUser(); + Set roles = user.getRoles(); + roles.add(createSuperAdminRole()); + user.setRoles(roles); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + System.out.println(user.getRoles()); + User loggedInUser = createTestUser(); + configureUserSecurityContext(loggedInUser); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + userService.addUsers(List.of(user)); + } + + @Test(expected = RuntimeException.class) + public void testAddUsers_UserRoleNotExisting() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + List result = userService.addUsers(List.of(user)); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(user, result.getFirst()); + } + + @Test + public void testAddUsers_UserEmailNull_AndBadMetadata() { + User user = createTestUser(); + configureUserSecurityContext(user); + + // set email to null + user.setEmail(null); + // set bad metadata + user.setGeneralMetadata("bad metadata"); + + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + userService.addUsers(List.of(user)); + } + + @Test + public void testAddUsers_UserEmailNull_AndValidMetadata() { + User user = createTestUser(); + configureUserSecurityContext(user); + + // set email to null + user.setEmail(null); + // set bad metadata + user.setGeneralMetadata("{\"email\":\" " + user.getEmail() + "\"}"); + + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + userService.addUsers(List.of(user)); + } + + @Test + public void testUpdateUser() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + List result = userService.updateUser(List.of(user)); + assertNotNull(result); + } + + @Test + public void testUpdateUser_SuperAdminRole() { + User user = createTestUser(); + user.getRoles().add(createSuperAdminRole()); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + + List result = userService.updateUser(List.of(user)); + assertNotNull(result); + } + + @Test(expected = IllegalArgumentException.class) + public void testUpdateUser_SuperAdminRole_withoutNecessaryPrivileges() { + User user = createTestUser(); + Set roles = user.getRoles(); + roles.add(createSuperAdminRole()); + user.setRoles(roles); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + User loggedInUser = createTestUser(); + configureUserSecurityContext(loggedInUser); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + User userToFindByID = new User(); + userToFindByID.setUuid(user.getUuid()); + userToFindByID.setRoles(new HashSet<>()); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(userToFindByID)); + + userService.updateUser(List.of(user)); + } + + @Test(expected = NullPointerException.class) + public void testGetUserProfileResponse_missingClaims() { + HashMap incompleteClaims = new HashMap<>(); + incompleteClaims.put("email", "test@example.com"); + // Missing "sub" which is mandatory for the method logic + userService.getUserProfileResponse(incompleteClaims); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetUserById_invalidUUID() { + userService.getUserById("not-a-real-uuid"); + } + + @Test + public void testSendUserUpdateEmails_success() throws MessagingException { + User user = createTestUser(); + List users = List.of(user); + when(userRepository.saveAll(users)).thenReturn(users); + configureUserSecurityContext(user); + + try { + userService.sendUserUpdateEmailsFromResponse(users); + } catch (Exception e) { + fail("Should not throw an exception when sending emails"); + } + + verify(basicMailService).sendUsersAccessEmail(user); + } + + @Test + public void testGetCurrentUser_noUserInContext() { + when(securityContext.getAuthentication()).thenReturn(null); + + CustomUserDetails customUserDetails = new CustomUserDetails(null); + // configure security context + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + when(securityContext.getAuthentication()).thenReturn(authentication); + + assertNull(userService.getCurrentUser("Bearer some-token", true)); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetQueryTemplate_invalidApplicationId() { + userService.getQueryTemplate(null); + } + + private User createTestUser() { + User user = new User(); + user.setUuid(UUID.randomUUID()); + user.setRoles(new HashSet<>(Collections.singleton(createTestRole()))); + user.setSubject("TEST_SUBJECT"); + user.setEmail("test@email.com"); + user.setAcceptedTOS(new Date()); + user.setActive(true); + + return user; + } + + private Role createTestRole() { + Role role = new Role(); + role.setName("TEST_ROLE"); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createTestPrivilege())); + return role; + } + + private Privilege createTestPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName("TEST_PRIVILEGE"); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + + private void configureUserSecurityContext(User user) { + CustomUserDetails customUserDetails = new CustomUserDetails(user); + // configure security context + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + private Role createSuperAdminRole() { + Role role = new Role(); + role.setName(SecurityRoles.SUPER_ADMIN.name()); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createSuperAdminPrivilege())); + return role; + } + + private Privilege createSuperAdminPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName(AuthNaming.AuthRoleNaming.SUPER_ADMIN); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + } \ No newline at end of file From eaa7c3a923f7dbdd089faa41c946ef8c6947da34 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Mon, 13 May 2024 15:59:29 -0400 Subject: [PATCH 197/222] Remove unused import in UserServiceTest --- .../hms/dbmi/avillach/auth/service/impl/UserServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index 86c77dd9d..de7a3b2ce 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -17,7 +17,6 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; From f812fdaa87c2991f50722f487722eda8729bbe45 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 08:50:27 -0400 Subject: [PATCH 198/222] Update UserServiceTest with additional test methods --- .../auth/service/impl/UserServiceTest.java | 139 +++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index de7a3b2ce..41b4ed097 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -1,5 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; import edu.harvard.hms.dbmi.avillach.auth.entity.Role; import edu.harvard.hms.dbmi.avillach.auth.entity.User; @@ -12,6 +13,8 @@ import edu.harvard.hms.dbmi.avillach.auth.service.RoleService; import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; import jakarta.mail.MessagingException; import org.junit.Before; import org.junit.Test; @@ -22,6 +25,7 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import java.security.SecureRandom; import java.util.*; import static org.junit.Assert.*; @@ -44,8 +48,12 @@ public class UserServiceTest { private ApplicationRepository applicationRepository; @Mock private RoleService roleService; - @Mock + private JWTUtil jwtUtil; + + @Mock + private JWTUtil mockJwtUtil; + @Mock private RoleRepository roleRepository; @@ -63,6 +71,7 @@ public void setUp() { SecurityContextHolder.setContext(securityContext); when(securityContext.getAuthentication()).thenReturn(authentication); + jwtUtil = new JWTUtil(generate256Base64Secret(), true); applicationUUID = UUID.randomUUID().toString(); userService = new UserService( basicMailService, @@ -74,7 +83,7 @@ public void setUp() { defaultTokenExpirationTime, applicationUUID, longTermTokenExpirationTime, - jwtUtil); + mockJwtUtil); } @Test @@ -326,6 +335,35 @@ public void testSendUserUpdateEmails_success() throws MessagingException { verify(basicMailService).sendUsersAccessEmail(user); } + @Test + public void testGetCurrentUser() { + User user = createTestUser(); + configureUserSecurityContext(user); + + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + longTermTokenExpirationTime + ); + + Jws claimsJws = this.jwtUtil.parseToken(token); + System.out.println(claimsJws); + + when(mockJwtUtil.parseToken(anyString())).thenReturn(claimsJws); + + user.setToken(token); + when(tosService.hasUserAcceptedLatest(any())).thenReturn(true); + User.UserForDisplay currentUser = userService.getCurrentUser("Bearer " + token, true); + assertNotNull(currentUser); + assertEquals(user.getToken(), currentUser.getToken()); + } + @Test public void testGetCurrentUser_noUserInContext() { when(securityContext.getAuthentication()).thenReturn(null); @@ -343,6 +381,47 @@ public void testGetQueryTemplate_invalidApplicationId() { userService.getQueryTemplate(null); } + @Test + public void testGetUserProfileResponse_withoutAcceptedTOS() { + User user = createTestUser(); + when(tosService.hasUserAcceptedLatest(anyString())).thenReturn(false); + + HashMap claims = new HashMap<>(); + claims.put("email", user.getEmail()); + claims.put("sub", user.getSubject()); + + HashMap result = userService.getUserProfileResponse(claims); + assertEquals("false", result.get("acceptedTOS")); + } + + @Test + public void testGetUserProfileResponse_withAcceptedTOS() { + User user = createTestUser(); + when(tosService.hasUserAcceptedLatest(anyString())).thenReturn(true); + + HashMap claims = new HashMap<>(); + claims.put("email", user.getEmail()); + claims.put("sub", user.getSubject()); + + HashMap result = userService.getUserProfileResponse(claims); + assertEquals("true", result.get("acceptedTOS")); + } + + @Test + public void testGetQueryTemplate_validApplicationId() { + User user = createTestUser(); + configureUserSecurityContext(user); + Application application = createTestApplication(); + + + when(applicationRepository.findById(any(UUID.class))).thenReturn(Optional.of(application)); + when(applicationRepository.findById(application.getUuid())).thenReturn(Optional.of(application)); + + Optional result = userService.getQueryTemplate(application.getUuid().toString()); + assertTrue(result.isPresent()); + } + + private User createTestUser() { User user = new User(); user.setUuid(UUID.randomUUID()); @@ -355,6 +434,26 @@ private User createTestUser() { return user; } + private Application createTestApplication() { + Application application = new Application(); + application.setUuid(UUID.randomUUID()); + application.setName("TEST_APPLICATION"); + application.setToken(createValidApplicationTestToken(application)); + application.setPrivileges(new HashSet<>()); + return application; + } + + private String createValidApplicationTestToken(Application application) { + return this.jwtUtil.createJwtToken( + null, null, + new HashMap<>( + Map.of( + "user_id", AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getName() + ) + ), + AuthNaming.PSAMA_APPLICATION_TOKEN_PREFIX + "|" + application.getUuid().toString(), 365L * 1000 * 60 * 60 * 24); + } + private Role createTestRole() { Role role = new Role(); role.setName("TEST_ROLE"); @@ -367,6 +466,8 @@ private Privilege createTestPrivilege() { Privilege privilege = new Privilege(); privilege.setName("TEST_PRIVILEGE"); privilege.setUuid(UUID.randomUUID()); + privilege.setQueryTemplate(createQueryTemplate("consent_concept_path_"+privilege.getUuid(), "project_name_"+privilege.getUuid(), "consent_group_"+privilege.getUuid())); + return privilege; } @@ -392,4 +493,38 @@ private Privilege createSuperAdminPrivilege() { return privilege; } + /** + * Do not use this method in production code. This is only for testing purposes. + * + * @return a 256-bit base64 encoded secret + */ + private String generate256Base64Secret() { + SecureRandom random = new SecureRandom(); + byte[] secret = new byte[32]; + random.nextBytes(secret); + return Base64.getEncoder().encodeToString(secret); + } + +// String queryTemplateText = "{\"categoryFilters\": {\"" +// + consent_concept_path +// + "\":\"" +// + project_name + "." + consent_group +// + "\"}," +// + "\"numericFilters\":{},\"requiredFields\":[]," +// + "\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," +// + "\"expectedResultType\": \"COUNT\"" +// + "}"; + + private String createQueryTemplate(String consent_concept_path, String project_name, String consent_group) { + return "{\"categoryFilters\": {\"" + + consent_concept_path + + "\":\"" + + project_name + "." + consent_group + + "\"}," + + "\"numericFilters\":{},\"requiredFields\":[]," + + "\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," + + "\"expectedResultType\": \"COUNT\"" + + "}"; + } + } \ No newline at end of file From b57048a5a2573721fdb6ef6248514d84ff74edc4 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 09:34:32 -0400 Subject: [PATCH 199/222] Add additional tests and modify CustomUserDetails class --- .../auth/model/CustomUserDetails.java | 2 +- .../auth/service/impl/UserServiceTest.java | 167 +++++++++++++++++- 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java index e99bcb40a..707d1504b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/CustomUserDetails.java @@ -14,7 +14,7 @@ public class CustomUserDetails implements UserDetails { public CustomUserDetails(User user) { this.user = user; - if (user != null) { + if (user != null && user.getRoles() != null) { this.authorities = user.getRoles().stream() .map(role -> (GrantedAuthority) role::getName) .toList(); diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index 41b4ed097..854e52777 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -338,7 +339,7 @@ public void testSendUserUpdateEmails_success() throws MessagingException { @Test public void testGetCurrentUser() { User user = createTestUser(); - configureUserSecurityContext(user); + Map claims = new HashMap<>(); claims.put("sub", user.getSubject()); @@ -351,13 +352,39 @@ public void testGetCurrentUser() { claims.get("sub").toString(), longTermTokenExpirationTime ); + user.setToken(token); + configureUserSecurityContext(user); Jws claimsJws = this.jwtUtil.parseToken(token); System.out.println(claimsJws); when(mockJwtUtil.parseToken(anyString())).thenReturn(claimsJws); + when(tosService.hasUserAcceptedLatest(any())).thenReturn(true); + User.UserForDisplay currentUser = userService.getCurrentUser("Bearer " + token, true); + assertNotNull(currentUser); + assertEquals(user.getToken(), currentUser.getToken()); + } - user.setToken(token); + @Test + public void testGetCurrentUser_withNoToken() { + User user = createTestUser(); + configureUserSecurityContext(user); + + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + longTermTokenExpirationTime + ); + + Jws claimsJws = this.jwtUtil.parseToken(token); + System.out.println(claimsJws); + when(mockJwtUtil.parseToken(anyString())).thenReturn(claimsJws); when(tosService.hasUserAcceptedLatest(any())).thenReturn(true); User.UserForDisplay currentUser = userService.getCurrentUser("Bearer " + token, true); assertNotNull(currentUser); @@ -376,6 +403,34 @@ public void testGetCurrentUser_noUserInContext() { assertNull(userService.getCurrentUser("Bearer some-token", true)); } + @Test + public void testGetCurrentUser_withNoPrivileges() { + User user = createTestUser(); + user.setRoles(new HashSet<>()); + configureUserSecurityContext(user); + + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + longTermTokenExpirationTime + ); + + Jws claimsJws = this.jwtUtil.parseToken(token); + + when(mockJwtUtil.parseToken(anyString())).thenReturn(claimsJws); + user.setToken(token); + when(tosService.hasUserAcceptedLatest(any())).thenReturn(true); + User.UserForDisplay currentUser = userService.getCurrentUser("Bearer " + token, true); + assertNotNull(currentUser); + assertEquals(user.getToken(), currentUser.getToken()); + } + @Test(expected = IllegalArgumentException.class) public void testGetQueryTemplate_invalidApplicationId() { userService.getQueryTemplate(null); @@ -421,6 +476,114 @@ public void testGetQueryTemplate_validApplicationId() { assertTrue(result.isPresent()); } + @Test + public void testGetDefaultQueryTemplate() { + User user = createTestUser(); + configureUserSecurityContext(user); + Application application = createTestApplication(); + + when(applicationRepository.findById(any(UUID.class))).thenReturn(Optional.of(application)); + when(applicationRepository.findById(application.getUuid())).thenReturn(Optional.of(application)); + + Map result = userService.getDefaultQueryTemplate(); + assertTrue(result.containsKey("queryTemplate")); + assertNotNull(result.get("queryTemplate")); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddUsers_withUserHavingNullRoleSet() { + User user = createTestUser(); + user.setRoles(null); + + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + List result = userService.addUsers(List.of(user)); + assertNull(result.getFirst().getRoles()); + } + + @Test + public void testUpdateUser_withNoChanges() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + doAnswer(invocation -> { + UUID argument = invocation.getArgument(0); + return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); + }).when(roleService).getRoleById(any(UUID.class)); + + List result = userService.updateUser(List.of(user)); + assertEquals(user, result.getFirst()); + } + + @Test + public void testRefreshUserToken() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + Map claims = new HashMap<>(); + claims.put("sub", user.getSubject()); + + // Application Long term token + String token = jwtUtil.createJwtToken( + "whatever", + "edu.harvard.hms.dbmi.psama", + claims, + claims.get("sub").toString(), + longTermTokenExpirationTime + ); + + String authorizationHeader = "Bearer " + token; + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", authorizationHeader); + + Jws claimsJws = this.jwtUtil.parseToken(token); + when(mockJwtUtil.parseToken(anyString())).thenReturn(claimsJws); + when(mockJwtUtil.createJwtToken(anyString(), anyString(), anyMap(), anyString(), anyLong())).thenReturn(token); + + Map result = userService.refreshUserToken(headers); + assertNotNull(result); + assertEquals(token, result.get("userLongTermToken")); + } + + @Test + public void testChangeRole() { + User user = createTestUser(); + configureUserSecurityContext(user); + when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); + + // new roles + Role role = createTestRole(); + role.setName("NEW_ROLE"); + + Set newRoles = new HashSet<>(); + newRoles.add(role); + + userService.changeRole(user, newRoles); + } + + @Test + public void testFindUserBySubject() { + User user = createTestUser(); + when(userRepository.findBySubject(user.getSubject())).thenReturn(user); + + User result = userService.findBySubject(user.getSubject()); + assertNotNull(result); + assertEquals(user, result); + } + + @Test + public void testSaveUser() { + User user = createTestUser(); + when(userRepository.save(user)).thenReturn(user); + + User result = userService.save(user); + assertNotNull(result); + assertEquals(user, result); + } private User createTestUser() { User user = new User(); From 3bc777dcd04bde02d60810c1f7d6c505771465e2 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 09:59:36 -0400 Subject: [PATCH 200/222] Add unit tests for Terms of Service (TOS) service --- .../auth/service/impl/TOSServiceTest.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSServiceTest.java new file mode 100644 index 000000000..b4c2ad820 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSServiceTest.java @@ -0,0 +1,199 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.TermsOfService; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.repository.TermsOfServiceRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Date; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +public class TOSServiceTest { + + @Mock + private TermsOfServiceRepository termsOfServiceRepo; + + @Mock + private UserRepository userRepo; + + private TOSService tosService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + tosService = new TOSService(termsOfServiceRepo, userRepo, true); + } + + @Test + public void testHasUserAcceptedLatest_TOSDisabled() { + TOSService service = new TOSService(termsOfServiceRepo, userRepo, false); + + boolean result = service.hasUserAcceptedLatest("user-id"); + assertTrue(result); + } + + @Test + public void testHasUserAcceptedLatest_NoTOSFound() { + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.empty()); + + boolean result = tosService.hasUserAcceptedLatest("user-id"); + assertTrue(result); + } + + @Test + public void testHasUserAcceptedLatest_UserNotAcceptedLatestTOS() { + User user = new User(); + user.setAcceptedTOS(new Date(0)); // Set an old date + + TermsOfService latestTOS = new TermsOfService(); + latestTOS.setDateUpdated(new Date()); + latestTOS.setContent("Latest TOS content"); + + when(userRepo.findById(any(UUID.class))).thenReturn(Optional.of(user)); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(latestTOS)); + + boolean result = tosService.hasUserAcceptedLatest(UUID.randomUUID().toString()); + assertFalse(result); + } + + @Test + public void testHasUserAcceptedLatest_UserAcceptedLatestTOS() { + User user = new User(); + user.setAcceptedTOS(new Date()); + + TermsOfService latestTOS = new TermsOfService(); + latestTOS.setDateUpdated(new Date(0)); // Set an old date + + when(userRepo.findById(any(UUID.class))).thenReturn(Optional.of(user)); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(latestTOS)); + + boolean result = tosService.hasUserAcceptedLatest(UUID.randomUUID().toString()); + assertTrue(result); + } + + @Test + public void testUpdateTermsOfService() { + String html = "

New TOS content

"; + + TermsOfService updatedTOS = new TermsOfService(); + updatedTOS.setContent(html); + when(termsOfServiceRepo.save(any(TermsOfService.class))).thenReturn(updatedTOS); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(updatedTOS)); + + Optional result = tosService.updateTermsOfService(html); + assertTrue(result.isPresent()); + assertEquals(html, result.get().getContent()); + } + + @Test + public void testGetLatest_TOSFound() { + String content = "

Latest TOS content

"; + + TermsOfService tos = new TermsOfService(); + tos.setContent(content); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(tos)); + + String result = tosService.getLatest(); + assertEquals(content, result); + } + + @Test + public void testGetLatest_NoTOSFound() { + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.empty()); + + String result = tosService.getLatest(); + assertNull(result); + } + + @Test + public void testAcceptTermsOfService_UserExists() { + User user = new User(); + user.setSubject("user-id"); + + TermsOfService tos = new TermsOfService(); + tos.setDateUpdated(new Date()); + + when(userRepo.findBySubject(anyString())).thenReturn(user); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(tos)); + + User result = tosService.acceptTermsOfService("user-id"); + + assertNotNull(result); + assertNotNull(result.getAcceptedTOS()); + } + + @Test(expected = RuntimeException.class) + public void testAcceptTermsOfService_UserDoesNotExist() { + when(userRepo.findBySubject(anyString())).thenReturn(null); + + tosService.acceptTermsOfService("user-id"); + } + + @Test(expected = RuntimeException.class) + public void testAcceptTermsOfService_NoTOSInDatabase() { + User user = new User(); + user.setSubject("user-id"); + + when(userRepo.findBySubject(anyString())).thenReturn(user); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.empty()); + + tosService.acceptTermsOfService("user-id"); + } + + @Test + public void testCheckAgainstTOSDate_UserAcceptedLatestTOS() { + User user = new User(); + user.setAcceptedTOS(new Date()); + + TermsOfService tos = new TermsOfService(); + tos.setDateUpdated(new Date(0)); // Set an old date + + when(userRepo.findById(any(UUID.class))).thenReturn(Optional.of(user)); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(tos)); + + boolean result = tosService.hasUserAcceptedLatest(UUID.randomUUID().toString()); + assertTrue(result); + } + + @Test + public void testCheckAgainstTOSDate_UserNotAcceptedLatestTOS() { + User user = new User(); + user.setAcceptedTOS(new Date(0)); + + TermsOfService tos = new TermsOfService(); + tos.setDateUpdated(new Date()); + tos.setContent("Latest TOS content"); + + when(userRepo.findById(any(UUID.class))).thenReturn(Optional.of(user)); + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(tos)); + + boolean result = tosService.hasUserAcceptedLatest(UUID.randomUUID().toString()); + assertFalse(result); + } + + @Test + public void testCheckAgainstTOSDate_UserNotFound() { + when(userRepo.findById(any(UUID.class))).thenReturn(Optional.empty()); + + TermsOfService tos = new TermsOfService(); + tos.setDateUpdated(new Date()); + tos.setContent("Latest TOS content"); + + when(termsOfServiceRepo.findTopByOrderByDateUpdatedDesc()).thenReturn(Optional.of(tos)); + + boolean result = tosService.hasUserAcceptedLatest(UUID.randomUUID().toString()); + assertFalse(result); + } + +} \ No newline at end of file From 8b0b41196b5669a2ec0f7aee14535c628a2d1fa5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 10:23:30 -0400 Subject: [PATCH 201/222] Update BasicMailService with setters and implement tests --- .../auth/service/impl/BasicMailService.java | 14 ++- .../service/impl/BasicMailServiceTest.java | 92 +++++++++++++++++++ 2 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailServiceTest.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java index d596ba6cf..64848210c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailService.java @@ -33,8 +33,8 @@ public class BasicMailService implements MailService { private static final Logger logger = LoggerFactory.getLogger(BasicMailService.class); private static final MustacheFactory mf = new DefaultMustacheFactory(); - private final Mustache accessTemplate = compileTemplate("accessEmail.mustache"); - private final Mustache deniedTemplate = compileTemplate("deniedAccessEmail.mustache"); + private Mustache accessTemplate = compileTemplate("accessEmail.mustache"); + private Mustache deniedTemplate = compileTemplate("deniedAccessEmail.mustache"); private final JavaMailSender mailSender; private final String templatePath; private final String systemName; @@ -55,7 +55,7 @@ public BasicMailService(JavaMailSender mailSender, @Value("${application.templat /** * Compile mustache template from templateFile */ - private Mustache compileTemplate(String templateFile) { + Mustache compileTemplate(String templateFile) { try { FileReader reader = new FileReader(templatePath + templateFile); return mf.compile(reader, templateFile); @@ -125,4 +125,12 @@ public void sendEmail(Mustache emailTemplate, String to, String subject, Object mailSender.send(message); logger.debug("sendEmail() finished"); } + + public void setDeniedTemplate(Mustache deniedTemplate) { + this.deniedTemplate = deniedTemplate; + } + + public void setAccessTemplate(Mustache accessTemplate) { + this.accessTemplate = accessTemplate; + } } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailServiceTest.java new file mode 100644 index 000000000..bca3917e3 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/BasicMailServiceTest.java @@ -0,0 +1,92 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.github.mustachejava.Mustache; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class BasicMailServiceTest { + + @Mock + private JavaMailSender mailSender; + + @Mock + private Mustache accessTemplate; + + @Mock + private Mustache deniedTemplate; + + @InjectMocks + private BasicMailService basicMailService; + + @Value("${application.template.path}") + private String templatePath = "src/test/resources/templates/"; + + @Value("${application.system.name}") + private String systemName = "TestSystem"; + + @Value("${application.access.grant.email.subject}") + private String accessGrantEmailSubject = "Access Granted"; + + @Value("${application.admin.users}") + private String adminUsers = "admin@test.com"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + basicMailService = new BasicMailService(mailSender, templatePath, systemName, accessGrantEmailSubject, adminUsers); + } + + @Test + public void testCompileTemplate_FileNotFound() { + Mustache result = basicMailService.compileTemplate("nonExistent.mustache"); + assertNull(result); + } + + @Test + public void testSendUsersAccessEmail_NoTemplate() throws MessagingException { + User user = new User(); + user.setEmail("test@test.com"); + basicMailService.setAccessTemplate(null); + + basicMailService.sendUsersAccessEmail(user); + verify(mailSender, never()).send(any(MimeMessage.class)); + } + + @Test + public void testSendUsersAccessEmail_NoEmail() throws MessagingException { + User user = new User(); + user.setEmail(null); + + basicMailService.sendUsersAccessEmail(user); + verify(mailSender, never()).send(any(MimeMessage.class)); + } + + @Test + public void testSendDeniedAccessEmail_NoTemplate() throws MessagingException { + JsonNode userInfo = mock(JsonNode.class); + basicMailService.setDeniedTemplate(null); + + basicMailService.sendDeniedAccessEmail(userInfo); + verify(mailSender, never()).send(any(MimeMessage.class)); + } + + @Test + public void testSendEmail_EmptyParams() throws MessagingException { + basicMailService.sendEmail(accessTemplate, "", "", new Object()); + verify(mailSender, never()).send(any(MimeMessage.class)); + } + +} \ No newline at end of file From 131dd78065c572d647477ef1a0bc5ea8cdf002e3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 10:24:29 -0400 Subject: [PATCH 202/222] Remove unused Auth0Exception import --- .../avillach/auth/service/impl/Auth0MatchingServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java index 043c9674d..1772c8dbc 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/Auth0MatchingServiceTest.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.service.impl; -import com.auth0.exception.Auth0Exception; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; From 89655a8e3ea0706f9e35980e0ba87a69a7010bb5 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 10:29:09 -0400 Subject: [PATCH 203/222] Add testing for UserMetadataMappingService and remove unnecessary import --- .../impl/UserMetadataMappingService.java | 1 - .../impl/UserMetadataMappingServiceTest.java | 146 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingServiceTest.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java index 131f4887b..1b98a95db 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingService.java @@ -2,7 +2,6 @@ import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; import org.springframework.beans.factory.annotation.Autowired; diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingServiceTest.java new file mode 100644 index 000000000..6f380f7b9 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserMetadataMappingServiceTest.java @@ -0,0 +1,146 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; +import edu.harvard.hms.dbmi.avillach.auth.entity.UserMetadataMapping; +import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.UserMetadataMappingRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class UserMetadataMappingServiceTest { + + @Mock + private UserMetadataMappingRepository userMetadataMappingRepo; + + @Mock + private ConnectionRepository connectionRepo; + + @InjectMocks + private UserMetadataMappingService userMetadataMappingService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testGetAllMappingsForConnection() { + Connection connection = new Connection(); + connection.setId("testConnectionId"); + List mappings = Arrays.asList(new UserMetadataMapping(), new UserMetadataMapping()); + + when(userMetadataMappingRepo.findByConnection(connection)).thenReturn(mappings); + + List result = userMetadataMappingService.getAllMappingsForConnection(connection); + + assertEquals(mappings, result); + verify(userMetadataMappingRepo, times(1)).findByConnection(connection); + } + + @Test + public void testAddMappings_Success() { + Connection connection = new Connection(); + connection.setId("testConnectionId"); + + UserMetadataMapping mapping1 = new UserMetadataMapping(); + mapping1.setConnection(connection); + UserMetadataMapping mapping2 = new UserMetadataMapping(); + mapping2.setConnection(connection); + + List mappings = Arrays.asList(mapping1, mapping2); + + when(connectionRepo.findById("testConnectionId")).thenReturn(Optional.of(connection)); + when(userMetadataMappingRepo.saveAll(mappings)).thenReturn(mappings); + + List result = userMetadataMappingService.addMappings(mappings); + + assertEquals(mappings, result); + verify(connectionRepo, times(2)).findById("testConnectionId"); + verify(userMetadataMappingRepo, times(1)).saveAll(mappings); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddMappings_ConnectionNotFound() { + Connection connection = new Connection(); + connection.setId("invalidConnectionId"); + + UserMetadataMapping mapping = new UserMetadataMapping(); + mapping.setConnection(connection); + + List mappings = List.of(mapping); + + when(connectionRepo.findById("invalidConnectionId")).thenReturn(Optional.empty()); + + userMetadataMappingService.addMappings(mappings); + } + + @Test + public void testGetAllMappings() { + List mappings = Arrays.asList(new UserMetadataMapping(), new UserMetadataMapping()); + + when(userMetadataMappingRepo.findAll()).thenReturn(mappings); + + List result = userMetadataMappingService.getAllMappings(); + + assertEquals(mappings, result); + verify(userMetadataMappingRepo, times(1)).findAll(); + } + + @Test + public void testGetAllMappingsForConnectionById_Success() { + Connection connection = new Connection(); + connection.setId("testConnectionId"); + + when(connectionRepo.findById("testConnectionId")).thenReturn(Optional.of(connection)); + + Connection result = userMetadataMappingService.getAllMappingsForConnection("testConnectionId"); + + assertEquals(connection, result); + verify(connectionRepo, times(1)).findById("testConnectionId"); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetAllMappingsForConnectionById_NotFound() { + when(connectionRepo.findById("invalidConnectionId")).thenReturn(Optional.empty()); + + userMetadataMappingService.getAllMappingsForConnection("invalidConnectionId"); + } + + @Test + public void testUpdateUserMetadataMappings() { + List mappings = Arrays.asList(new UserMetadataMapping(), new UserMetadataMapping()); + + when(userMetadataMappingRepo.saveAll(mappings)).thenReturn(mappings); + + List result = userMetadataMappingService.updateUserMetadataMappings(mappings); + + assertEquals(mappings, result); + verify(userMetadataMappingRepo, times(1)).saveAll(mappings); + } + + @Test + public void testRemoveMetadataMappingByIdAndRetrieveAll() { + UUID mappingId = UUID.randomUUID(); + List mappings = Arrays.asList(new UserMetadataMapping(), new UserMetadataMapping()); + + doNothing().when(userMetadataMappingRepo).deleteById(mappingId); + when(userMetadataMappingRepo.findAll()).thenReturn(mappings); + + List result = userMetadataMappingService.removeMetadataMappingByIdAndRetrieveAll(mappingId.toString()); + + assertEquals(mappings, result); + verify(userMetadataMappingRepo, times(1)).deleteById(mappingId); + verify(userMetadataMappingRepo, times(1)).findAll(); + } +} From 749f099edc692ca686000bbe3bc37b227ba2f91f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 13:38:49 -0400 Subject: [PATCH 204/222] Remove commented-out code in UserServiceTest.java --- .../avillach/auth/service/impl/UserServiceTest.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index 854e52777..86b01b82c 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -668,16 +668,6 @@ private String generate256Base64Secret() { return Base64.getEncoder().encodeToString(secret); } -// String queryTemplateText = "{\"categoryFilters\": {\"" -// + consent_concept_path -// + "\":\"" -// + project_name + "." + consent_group -// + "\"}," -// + "\"numericFilters\":{},\"requiredFields\":[]," -// + "\"variantInfoFilters\":[{\"categoryVariantInfoFilters\":{},\"numericVariantInfoFilters\":{}}]," -// + "\"expectedResultType\": \"COUNT\"" -// + "}"; - private String createQueryTemplate(String consent_concept_path, String project_name, String consent_group) { return "{\"categoryFilters\": {\"" + consent_concept_path From 1610210f32c398d51d7018179b00f7e86723812b Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 13:41:32 -0400 Subject: [PATCH 205/222] Update test to expect NullPointerException --- .../hms/dbmi/avillach/auth/service/impl/UserServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index 86b01b82c..b752da31c 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -490,7 +490,7 @@ public void testGetDefaultQueryTemplate() { assertNotNull(result.get("queryTemplate")); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = NullPointerException.class) public void testAddUsers_withUserHavingNullRoleSet() { User user = createTestUser(); user.setRoles(null); From e0b85db96248352cd79396c504860f4b19145ff3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 13:41:49 -0400 Subject: [PATCH 206/222] Add RoleService unit tests --- .../auth/service/RoleServiceTest.java | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleServiceTest.java new file mode 100644 index 000000000..b5ae898a6 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleServiceTest.java @@ -0,0 +1,244 @@ +package edu.harvard.hms.dbmi.avillach.auth.service; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import edu.harvard.hms.dbmi.avillach.auth.repository.RoleRepository; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class RoleServiceTest { + + @Mock + private RoleRepository roleRepository; + + @Mock + private PrivilegeRepository privilegeRepo; + + @Mock + private SecurityContext securityContext; + + @Mock + private Authentication authentication; + + @InjectMocks + private RoleService roleService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + SecurityContextHolder.setContext(securityContext); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + @Test + public void testGetRoleById_String() { + UUID roleId = UUID.randomUUID(); + Role role = new Role(); + role.setUuid(roleId); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + + Optional result = roleService.getRoleById(roleId.toString()); + + assertTrue(result.isPresent()); + assertEquals(role, result.get()); + verify(roleRepository, times(1)).findById(roleId); + } + + @Test + public void testGetRoleById_UUID() { + UUID roleId = UUID.randomUUID(); + Role role = new Role(); + role.setUuid(roleId); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + + Optional result = roleService.getRoleById(roleId); + + assertTrue(result.isPresent()); + assertEquals(role, result.get()); + verify(roleRepository, times(1)).findById(roleId); + } + + @Test + public void testGetAllRoles() { + List roles = Arrays.asList(new Role(), new Role()); + + when(roleRepository.findAll()).thenReturn(roles); + + List result = roleService.getAllRoles(); + + assertEquals(roles, result); + verify(roleRepository, times(1)).findAll(); + } + + @Test + public void testAddRoles() { + List roles = Arrays.asList(new Role(), new Role()); + + when(roleRepository.saveAll(roles)).thenReturn(roles); + + List result = roleService.addRoles(roles); + + assertEquals(roles, result); + verify(roleRepository, times(1)).saveAll(roles); + } + + @Test(expected = RuntimeException.class) + public void testAddRoles_PrivilegeNotFound() { + Role role = new Role(); + Privilege privilege = new Privilege(); + privilege.setUuid(UUID.randomUUID()); + role.setPrivileges(new HashSet<>(Collections.singletonList(privilege))); + + when(privilegeRepo.findById(privilege.getUuid())).thenReturn(Optional.empty()); + + roleService.addRoles(Collections.singletonList(role)); + } + + @Test + public void testUpdateRoles() { + List roles = Arrays.asList(new Role(), new Role()); + + when(roleRepository.saveAll(roles)).thenReturn(roles); + + List result = roleService.updateRoles(roles); + + assertEquals(roles, result); + verify(roleRepository, times(1)).saveAll(roles); + } + + @Test(expected = RuntimeException.class) + public void testUpdateRoles_PrivilegeNotFound() { + Role role = new Role(); + Privilege privilege = new Privilege(); + privilege.setUuid(UUID.randomUUID()); + role.setPrivileges(new HashSet<>(Collections.singletonList(privilege))); + + when(privilegeRepo.findById(privilege.getUuid())).thenReturn(Optional.empty()); + + roleService.updateRoles(Collections.singletonList(role)); + } + + @Test + public void testRemoveRoleById_Success() { + User user = createTestUser(); + configureUserSecurityContext(user); + + Set roles = user.getRoles(); + Role role = roles.iterator().next(); + UUID roleId = role.getUuid(); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + when(roleRepository.findAll()).thenReturn(Collections.singletonList(role)); + + Optional> result = roleService.removeRoleById(roleId.toString()); + + assertTrue(result.isPresent()); + assertEquals(1, result.get().size()); + verify(roleRepository, times(1)).deleteById(roleId); + verify(roleRepository, times(1)).findAll(); + } + + @Test + public void testRemoveRoleById_RoleNotFound() { + UUID roleId = UUID.randomUUID(); + + when(roleRepository.findById(roleId)).thenReturn(Optional.empty()); + + Optional> result = roleService.removeRoleById(roleId.toString()); + + assertFalse(result.isPresent()); + verify(roleRepository, never()).deleteById(any()); + } + + @Test + public void testRemoveRoleById_InsufficientPrivileges() { + UUID roleId = UUID.randomUUID(); + Role role = new Role(); + role.setUuid(roleId); + + when(roleRepository.findById(roleId)).thenReturn(Optional.of(role)); + + Optional> result = roleService.removeRoleById(roleId.toString()); + + assertFalse(result.isPresent()); + verify(roleRepository, never()).deleteById(any()); + } + + @Test + public void testAddObjectToSet_RoleExists() { + Set roles = new HashSet<>(); + Role role = new Role(); + role.setUuid(UUID.randomUUID()); + + when(roleRepository.findById(role.getUuid())).thenReturn(Optional.of(role)); + + roleService.addObjectToSet(roles, role); + + assertTrue(roles.contains(role)); + } + + @Test(expected = RuntimeException.class) + public void testAddObjectToSet_RoleNotFound() { + Set roles = new HashSet<>(); + Role role = new Role(); + role.setUuid(UUID.randomUUID()); + + when(roleRepository.findById(role.getUuid())).thenReturn(Optional.empty()); + + roleService.addObjectToSet(roles, role); + } + + private Role createTopAdminRole() { + Role role = new Role(); + role.setName(SecurityRoles.PIC_SURE_TOP_ADMIN.getRole()); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createSuperAdminPrivilege())); + return role; + } + + private Privilege createSuperAdminPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName(AuthNaming.AuthRoleNaming.SUPER_ADMIN); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + + private void configureUserSecurityContext(User user) { + CustomUserDetails customUserDetails = new CustomUserDetails(user); + // configure security context + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + private User createTestUser() { + User user = new User(); + user.setUuid(UUID.randomUUID()); + user.setRoles(new HashSet<>(Collections.singleton(createTopAdminRole()))); + user.setSubject("TEST_SUBJECT"); + user.setEmail("test@email.com"); + user.setAcceptedTOS(new Date()); + user.setActive(true); + + return user; + } +} From 861584a610b91bdd032b50e2c1769d77416273c3 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 13:42:00 -0400 Subject: [PATCH 207/222] Add test cases for PrivilegeService --- .../auth/service/PrivilegeServiceTest.java | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeServiceTest.java diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeServiceTest.java new file mode 100644 index 000000000..19c2b5e59 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/PrivilegeServiceTest.java @@ -0,0 +1,140 @@ +package edu.harvard.hms.dbmi.avillach.auth.service; + +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.repository.PrivilegeRepository; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class PrivilegeServiceTest { + + @Mock + private PrivilegeRepository privilegeRepository; + + @Mock + private SecurityContext securityContext; + + @Mock + private Authentication authentication; + + @InjectMocks + private PrivilegeService privilegeService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + SecurityContextHolder.setContext(securityContext); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + @Test + public void testDeletePrivilegeByPrivilegeId_Success() { + UUID privilegeId = UUID.randomUUID(); + Privilege privilege = new Privilege(); + privilege.setUuid(privilegeId); + privilege.setName("USER_PRIVILEGE"); + + when(privilegeRepository.findById(privilegeId)).thenReturn(Optional.of(privilege)); + when(authentication.getName()).thenReturn("testUser"); + + List privileges = Arrays.asList(new Privilege(), new Privilege()); + when(privilegeRepository.findAll()).thenReturn(privileges); + + List result = privilegeService.deletePrivilegeByPrivilegeId(privilegeId.toString()); + + assertEquals(privileges, result); + verify(privilegeRepository, times(1)).deleteById(privilegeId); + verify(privilegeRepository, times(1)).findAll(); + } + + @Test(expected = RuntimeException.class) + public void testDeletePrivilegeByPrivilegeId_AdminPrivilege() { + UUID privilegeId = UUID.randomUUID(); + Privilege privilege = new Privilege(); + privilege.setUuid(privilegeId); + privilege.setName(ADMIN); + + when(privilegeRepository.findById(privilegeId)).thenReturn(Optional.of(privilege)); + when(authentication.getName()).thenReturn("testUser"); + + privilegeService.deletePrivilegeByPrivilegeId(privilegeId.toString()); + } + + @Test + public void testUpdatePrivileges() { + List privileges = Arrays.asList(new Privilege(), new Privilege()); + + when(privilegeRepository.saveAll(privileges)).thenReturn(privileges); + when(privilegeRepository.findAll()).thenReturn(privileges); + + List result = privilegeService.updatePrivileges(privileges); + + assertEquals(privileges, result); + verify(privilegeRepository, times(1)).saveAll(privileges); + verify(privilegeRepository, times(1)).findAll(); + } + + @Test + public void testAddPrivileges() { + List privileges = Arrays.asList(new Privilege(), new Privilege()); + + when(privilegeRepository.saveAll(privileges)).thenReturn(privileges); + + List result = privilegeService.addPrivileges(privileges); + + assertEquals(privileges, result); + verify(privilegeRepository, times(1)).saveAll(privileges); + } + + @Test + public void testGetPrivilegesAll() { + List privileges = Arrays.asList(new Privilege(), new Privilege()); + + when(privilegeRepository.findAll()).thenReturn(privileges); + + List result = privilegeService.getPrivilegesAll(); + + assertEquals(privileges, result); + verify(privilegeRepository, times(1)).findAll(); + } + + @Test + public void testGetPrivilegeById_Found() { + UUID privilegeId = UUID.randomUUID(); + Privilege privilege = new Privilege(); + privilege.setUuid(privilegeId); + + when(privilegeRepository.findById(privilegeId)).thenReturn(Optional.of(privilege)); + + Privilege result = privilegeService.getPrivilegeById(privilegeId.toString()); + + assertEquals(privilege, result); + verify(privilegeRepository, times(1)).findById(privilegeId); + } + + @Test + public void testGetPrivilegeById_NotFound() { + UUID privilegeId = UUID.randomUUID(); + + when(privilegeRepository.findById(privilegeId)).thenReturn(Optional.empty()); + + Privilege result = privilegeService.getPrivilegeById(privilegeId.toString()); + + assertNull(result); + verify(privilegeRepository, times(1)).findById(privilegeId); + } +} From 6dde55399470acf66e8d2c48cad24d6a2c457d55 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 14:23:00 -0400 Subject: [PATCH 208/222] Add AuthorizationServiceTest and refactor AuthorizationService A new test class has been added for `AuthorizationService` in the auth-services module. Adjustments have also been made in the `AuthorizationService` class, where unnecessary comments have been removed. --- .../service/impl/AuthorizationService.java | 8 +- .../impl/AuthorizationServiceTest.java | 238 ++++++++++++++++++ 2 files changed, 240 insertions(+), 6 deletions(-) create mode 100644 pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java index c31617296..b128ec3a3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationService.java @@ -68,9 +68,6 @@ public class AuthorizationService { * @see AccessRule */ public boolean isAuthorized(Application application , Object requestBody, User user){ - - // (application.getPrivileges().isEmpty() && !user.getPrivilegeNameSetByApplication(application).isEmpty()) - String applicationName = application.getName(); //in some cases, we don't go through the evaluation if (requestBody == null) { @@ -115,7 +112,6 @@ public boolean isAuthorized(Application application , Object requestBody, User u } Set accessRules = preProcessAccessRules(privileges); - if (accessRules == null || accessRules.isEmpty()) { logger.info("ACCESS_LOG ___ " + user.getUuid().toString() + "," + user.getEmail() + "," + user.getName() + " ___ has been granted access to execute query ___ " + formattedQuery + " ___ in application ___ " + applicationName @@ -399,7 +395,7 @@ protected boolean evaluateAccessRule(Object parsedRequestBody, AccessRule access * @param parsedRequestBody * @return */ - private boolean extractAndCheckRule(AccessRule accessRule, Object parsedRequestBody){ + boolean extractAndCheckRule(AccessRule accessRule, Object parsedRequestBody){ logger.debug("extractAndCheckRule() starting"); String rule = accessRule.getRule(); @@ -586,7 +582,7 @@ && evaluateNode(entry.getValue(), accessRule) == false) * @param requestBodyValue * @return */ - private boolean decisionMaker(AccessRule accessRule, String requestBodyValue){ + boolean decisionMaker(AccessRule accessRule, String requestBodyValue){ // it might be possible that sometimes there is value in the accessRule.getValue() // but the mergedValues doesn't have elements in it... diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java new file mode 100644 index 000000000..cd253ab21 --- /dev/null +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java @@ -0,0 +1,238 @@ +package edu.harvard.hms.dbmi.avillach.auth.service.impl; + +import edu.harvard.hms.dbmi.avillach.auth.entity.AccessRule; +import edu.harvard.hms.dbmi.avillach.auth.entity.Application; +import edu.harvard.hms.dbmi.avillach.auth.entity.Privilege; +import edu.harvard.hms.dbmi.avillach.auth.entity.Role; +import edu.harvard.hms.dbmi.avillach.auth.entity.User; +import edu.harvard.hms.dbmi.avillach.auth.enums.SecurityRoles; +import edu.harvard.hms.dbmi.avillach.auth.model.CustomUserDetails; +import edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class AuthorizationServiceTest { + + @Mock + private SecurityContext securityContext; + + @InjectMocks + private AuthorizationService authorizationService; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + SecurityContextHolder.setContext(securityContext); + } + + @Test + public void testIsAuthorized_NoRequestBody() { + Application application = createTestApplication(); + User user = createTestUser(); + + boolean result = authorizationService.isAuthorized(application, null, user); + + assertTrue(result); + } + + @Test + public void testIsAuthorized_NoPrivileges() { + Application application = createTestApplication(); + User user = createTestUser(); + + user.getRoles().iterator().next().setPrivileges(Collections.emptySet()); + boolean result = authorizationService.isAuthorized(application, new HashMap<>(), user); + + assertFalse(result); + } + + @Test + public void testIsAuthorized_AccessRulePassed() { + Application application = createTestApplication(); + User user = createTestUser(); + + // create access_rule for privilege + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + + // set application and access_rule for all user privileges + for (Privilege privilege1 : user.getRoles().iterator().next().getPrivileges()) { + privilege1.setAccessRules(Collections.singleton(accessRule)); + privilege1.setApplication(application); + } + configureUserSecurityContext(user); + + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + + boolean result = authorizationService.isAuthorized(application, requestBody, user); + + assertTrue(result); + } + + @Test + public void testIsAuthorized_AccessRuleFailed() { + Application application = createTestApplication(); + User user = createTestUser(); + configureUserSecurityContext(user); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "differentValue"); + + boolean result = authorizationService.isAuthorized(application, requestBody, user); + + assertFalse(result); + } + + @Test + public void testEvaluateAccessRule_GatesPassed() { + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + + @Test + public void testEvaluateAccessRule_GatesFailed() { + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "differentValue"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertFalse(result); + } + + @Test + public void testExtractAndCheckRule_Pass() { + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + + boolean result = authorizationService.extractAndCheckRule(accessRule, requestBody); + + assertTrue(result); + } + + @Test + public void testExtractAndCheckRule_Fail() { + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "differentValue"); + + boolean result = authorizationService.extractAndCheckRule(accessRule, requestBody); + + assertFalse(result); + } + + @Test + public void testDecisionMaker_Pass() { + AccessRule accessRule = new AccessRule(); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + boolean result = authorizationService.decisionMaker(accessRule, "value"); + + assertTrue(result); + } + + @Test + public void testDecisionMaker_Fail() { + AccessRule accessRule = new AccessRule(); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + boolean result = authorizationService.decisionMaker(accessRule, "differentValue"); + + assertFalse(result); + } + + private Role createTestRole() { + Role role = new Role(); + role.setName("TEST_ROLE"); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createTestPrivilege())); + return role; + } + + private Privilege createTestPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName("TEST_PRIVILEGE"); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + + private void configureUserSecurityContext(User user) { + CustomUserDetails customUserDetails = new CustomUserDetails(user); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities()); + when(securityContext.getAuthentication()).thenReturn(authentication); + } + + private Role createSuperAdminRole() { + Role role = new Role(); + role.setName(SecurityRoles.SUPER_ADMIN.name()); + role.setUuid(UUID.randomUUID()); + role.setPrivileges(Collections.singleton(createSuperAdminPrivilege())); + return role; + } + + private Privilege createSuperAdminPrivilege() { + Privilege privilege = new Privilege(); + privilege.setName(AuthNaming.AuthRoleNaming.SUPER_ADMIN); + privilege.setUuid(UUID.randomUUID()); + return privilege; + } + + private User createTestUser() { + User user = new User(); + user.setUuid(UUID.randomUUID()); + user.setRoles(new HashSet<>(Collections.singleton(createTestRole()))); + user.setSubject("TEST_SUBJECT"); + user.setEmail("test@email.com"); + user.setAcceptedTOS(new Date()); + user.setActive(true); + return user; + } + + private Application createTestApplication() { + Application application = new Application(); + application.setUuid(UUID.randomUUID()); + application.setName("TEST_APPLICATION"); + application.setPrivileges(new HashSet<>()); + return application; + } +} From cab400e923be91798879fcabcd064d819e126327 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 14:31:39 -0400 Subject: [PATCH 209/222] Update AuthorizationService test methods --- .../impl/AuthorizationServiceTest.java | 219 ++++++++++++++++-- 1 file changed, 198 insertions(+), 21 deletions(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java index cd253ab21..8dc044cfd 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java @@ -36,27 +36,27 @@ public void setUp() { SecurityContextHolder.setContext(securityContext); } - @Test - public void testIsAuthorized_NoRequestBody() { - Application application = createTestApplication(); - User user = createTestUser(); - - boolean result = authorizationService.isAuthorized(application, null, user); - - assertTrue(result); - } - - @Test - public void testIsAuthorized_NoPrivileges() { - Application application = createTestApplication(); - User user = createTestUser(); - - user.getRoles().iterator().next().setPrivileges(Collections.emptySet()); - boolean result = authorizationService.isAuthorized(application, new HashMap<>(), user); - - assertFalse(result); - } - +// @Test +// public void testIsAuthorized_NoRequestBody() { +// Application application = createTestApplication(); +// User user = createTestUser(); +// +// boolean result = authorizationService.isAuthorized(application, null, user); +// +// assertTrue(result); +// } +// +// @Test +// public void testIsAuthorized_NoPrivileges() { +// Application application = createTestApplication(); +// User user = createTestUser(); +// +// user.getRoles().iterator().next().setPrivileges(Collections.emptySet()); +// boolean result = authorizationService.isAuthorized(application, new HashMap<>(), user); +// +// assertFalse(result); +// } +// @Test public void testIsAuthorized_AccessRulePassed() { Application application = createTestApplication(); @@ -181,6 +181,183 @@ public void testDecisionMaker_Fail() { assertFalse(result); } + @Test + public void testIsAuthorized_NoRequestBody() { + Application application = createTestApplication(); + User user = createTestUser(); + configureUserSecurityContext(user); + application.setPrivileges(user.getPrivilegesByApplication(application)); + + boolean result = authorizationService.isAuthorized(application, null, user); + + assertTrue(result); + } + + @Test + public void testIsAuthorized_NoPrivileges() { + Application application = createTestApplication(); + User user = createTestUser(); + + user.getRoles().iterator().next().setPrivileges(Collections.emptySet()); + boolean result = authorizationService.isAuthorized(application, new HashMap<>(), user); + + assertFalse(result); + } + + @Test + public void testEvaluateAccessRule_NoGates() { + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + + @Test + public void testEvaluateAccessRule_AllGatesPassed_AND() { + AccessRule gate1 = new AccessRule(); + gate1.setRule("$.test"); + gate1.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate1.setValue("value"); + + AccessRule gate2 = new AccessRule(); + gate2.setRule("$.test2"); + gate2.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate2.setValue("value2"); + + AccessRule accessRule = new AccessRule(); + accessRule.setGates(new HashSet<>(Arrays.asList(gate1, gate2))); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + requestBody.put("test2", "value2"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + + @Test + public void testEvaluateAccessRule_OneGateFails_AND() { + AccessRule gate1 = new AccessRule(); + gate1.setRule("$.test"); + gate1.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate1.setValue("value"); + + AccessRule gate2 = new AccessRule(); + gate2.setRule("$.test2"); + gate2.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate2.setValue("value2"); + + AccessRule accessRule = new AccessRule(); + accessRule.setGates(new HashSet<>(Arrays.asList(gate1, gate2))); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + requestBody.put("test2", "differentValue"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertFalse(result); + } + + @Test + public void testEvaluateAccessRule_OneGatePassed_OR() { + AccessRule gate1 = new AccessRule(); + gate1.setRule("$.test"); + gate1.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate1.setValue("value"); + + AccessRule gate2 = new AccessRule(); + gate2.setRule("$.test2"); + gate2.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate2.setValue("value2"); + + AccessRule accessRule = new AccessRule(); + accessRule.setGates(new HashSet<>(Arrays.asList(gate1, gate2))); + accessRule.setGateAnyRelation(true); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + requestBody.put("test2", "differentValue"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + + @Test + public void testEvaluateAccessRule_AllGatesFail_OR() { + AccessRule gate1 = new AccessRule(); + gate1.setRule("$.test"); + gate1.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate1.setValue("value"); + + AccessRule gate2 = new AccessRule(); + gate2.setRule("$.test2"); + gate2.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate2.setValue("value2"); + + AccessRule accessRule = new AccessRule(); + accessRule.setGates(new HashSet<>(Arrays.asList(gate1, gate2))); + accessRule.setGateAnyRelation(true); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "differentValue"); + requestBody.put("test2", "differentValue"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertFalse(result); + } + + @Test + public void testEvaluateAccessRule_EvaluateOnlyByGates() { + AccessRule gate1 = new AccessRule(); + gate1.setRule("$.test"); + gate1.setType(AccessRule.TypeNaming.ALL_EQUALS); + gate1.setValue("value"); + + AccessRule accessRule = new AccessRule(); + accessRule.setGates(new HashSet<>(Collections.singletonList(gate1))); + accessRule.setEvaluateOnlyByGates(true); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + + @Test + public void testEvaluateAccessRule_SubAccessRules() { + AccessRule subAccessRule = new AccessRule(); + subAccessRule.setRule("$.test2"); + subAccessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + subAccessRule.setValue("value2"); + + AccessRule accessRule = new AccessRule(); + accessRule.setRule("$.test"); + accessRule.setType(AccessRule.TypeNaming.ALL_EQUALS); + accessRule.setValue("value"); + accessRule.setSubAccessRule(new HashSet<>(Collections.singletonList(subAccessRule))); + + Map requestBody = new HashMap<>(); + requestBody.put("test", "value"); + requestBody.put("test2", "value2"); + + boolean result = authorizationService.evaluateAccessRule(requestBody, accessRule); + + assertTrue(result); + } + private Role createTestRole() { Role role = new Role(); role.setName("TEST_ROLE"); From 8028f53c74f4d6a8d74225c16e1dcf1dc62cf305 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 14 May 2024 14:32:09 -0400 Subject: [PATCH 210/222] Remove unused test methods in AuthorizationServiceTest --- .../impl/AuthorizationServiceTest.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java index 8dc044cfd..587c8d9b7 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthorizationServiceTest.java @@ -36,27 +36,6 @@ public void setUp() { SecurityContextHolder.setContext(securityContext); } -// @Test -// public void testIsAuthorized_NoRequestBody() { -// Application application = createTestApplication(); -// User user = createTestUser(); -// -// boolean result = authorizationService.isAuthorized(application, null, user); -// -// assertTrue(result); -// } -// -// @Test -// public void testIsAuthorized_NoPrivileges() { -// Application application = createTestApplication(); -// User user = createTestUser(); -// -// user.getRoles().iterator().next().setPrivileges(Collections.emptySet()); -// boolean result = authorizationService.isAuthorized(application, new HashMap<>(), user); -// -// assertFalse(result); -// } -// @Test public void testIsAuthorized_AccessRulePassed() { Application application = createTestApplication(); From 5337420a7248d28a2521ceee8ac079fc84e657bb Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 15 May 2024 14:39:33 -0400 Subject: [PATCH 211/222] Removed unnecessary contains methods --- .../avillach/auth/enums/SecurityRoles.java | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java index d6e4a89cf..e3682335f 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.enums; -import java.util.List; import java.util.Set; public enum SecurityRoles { @@ -23,52 +22,11 @@ public enum SecurityRoles { * @return True if the role is contained in the set of roles */ public static boolean contains(Set roles, String role) { - for (String r : roles) { - if (r.equals(role)) { - return true; - } - } - return false; + return roles.contains(role); } public String getRole() { return role; } - public static SecurityRoles getRole(String role) { - for (SecurityRoles securityRole : SecurityRoles.values()) { - if (securityRole.getRole().equals(role)) { - return securityRole; - } - } - return null; - } - - public static boolean contains(String role) { - for (SecurityRoles securityRole : SecurityRoles.values()) { - if (securityRole.getRole().equals(role)) { - return true; - } - } - return false; - } - - public static boolean contains(SecurityRoles role) { - for (SecurityRoles securityRole : SecurityRoles.values()) { - if (securityRole.equals(role)) { - return true; - } - } - return false; - } - - public static boolean contains(List roles) { - for (String role : roles) { - if (!contains(role)) { - return false; - } - } - return true; - } - } From 84690a04b4c4c679c1d22c6b8e4ef908ad09422b Mon Sep 17 00:00:00 2001 From: Gcolon021 <34667267+Gcolon021@users.noreply.github.com> Date: Wed, 15 May 2024 14:42:57 -0400 Subject: [PATCH 212/222] Update pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java Co-authored-by: Luke Sikina --- .../edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index 7f54cc625..ed5a4d196 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -89,7 +89,7 @@ public Optional> removeRoleById(String roleId) { Set roles = context.getAuthentication().getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); - if (!SecurityRoles.contains(roles, SecurityRoles.PIC_SURE_TOP_ADMIN.getRole())){ + if (!roles.contains(SecurityRoles.PIC_SURE_TOP_ADMIN.getRole()){ logger.info("User doesn't have PIC-SURE Top Admin role, can't remove any role"); return Optional.empty(); } From 240c25b7ba2c6c1a46bcfb783ba44a488f8b986f Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 15 May 2024 14:43:18 -0400 Subject: [PATCH 213/222] Fixed missing character --- .../edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index ed5a4d196..b05dbc776 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -89,7 +89,7 @@ public Optional> removeRoleById(String roleId) { Set roles = context.getAuthentication().getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); - if (!roles.contains(SecurityRoles.PIC_SURE_TOP_ADMIN.getRole()){ + if (!roles.contains(SecurityRoles.PIC_SURE_TOP_ADMIN.getRole())){ logger.info("User doesn't have PIC-SURE Top Admin role, can't remove any role"); return Optional.empty(); } From 840cd0202efa5e23c84fd3c52f942e682061898c Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 15 May 2024 14:45:05 -0400 Subject: [PATCH 214/222] Update null handling in OauthUserMatchingService --- .../avillach/auth/service/impl/OauthUserMatchingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java index e37007a70..f8ab36f92 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/OauthUserMatchingService.java @@ -80,7 +80,7 @@ public User matchTokenToUser(JsonNode userInfo) { } //We only care about unmatched users - List users = userRepo.findByConnectionAndMatched(connection.orElse(null), false); + List users = userRepo.findByConnectionAndMatched(connection.get(), false); if (users == null || users.isEmpty()) { logger.info("No unmatched users exist with connectionId {}", connection); return null; From 35de2ffe45f6d800f94990bc4d51712056153eb1 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 15 May 2024 16:02:34 -0400 Subject: [PATCH 215/222] Remove contains method --- .../hms/dbmi/avillach/auth/enums/SecurityRoles.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java index e3682335f..e06f30264 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/enums/SecurityRoles.java @@ -1,6 +1,5 @@ package edu.harvard.hms.dbmi.avillach.auth.enums; -import java.util.Set; public enum SecurityRoles { @@ -14,17 +13,6 @@ public enum SecurityRoles { this.role = role; } - /** - * Check if a role is contained in a set of roles - * - * @param roles User roles - * @param role Role to check - * @return True if the role is contained in the set of roles - */ - public static boolean contains(Set roles, String role) { - return roles.contains(role); - } - public String getRole() { return role; } From 9ea209146610aa15bcb9afad69efbfa32185216b Mon Sep 17 00:00:00 2001 From: Luke Sikina Date: Tue, 21 May 2024 08:25:10 -0400 Subject: [PATCH 216/222] I don't like ResponseEntity (#171) Co-authored-by: Luke Sikina --- .../dbmi/avillach/auth/filter/JWTFilter.java | 4 +- .../auth/model/response/PICSUREResponse.java | 41 ++++++++----------- .../model/response/PicSureResponseBody.java | 4 ++ .../auth/rest/AccessRuleController.java | 9 ++-- .../auth/rest/ApplicationController.java | 8 ++-- .../auth/service/impl/TOSService.java | 18 ++++---- 6 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PicSureResponseBody.java diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 1d8919755..4c1f69fec 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -15,6 +15,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +27,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -204,7 +206,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe Set userRoles = authenticatedUser.getUser().getRoles(); // Check if the user has any roles and privileges associated with them - if (userRoles == null || userRoles.isEmpty() || userRoles.stream().noneMatch(role -> role.getPrivileges() != null && !role.getPrivileges().isEmpty())) { + if (userRoles == null || userRoles.isEmpty() || userRoles.stream().allMatch(role -> CollectionUtils.isEmpty(role.getPrivileges()))) { logger.error("User doesn't have any roles or privileges."); try { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User doesn't have any roles or privileges."); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java index d5244b2fc..24202da09 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PICSUREResponse.java @@ -15,68 +15,63 @@ public static ResponseEntity success() { return new ResponseEntity<>(HttpStatus.OK); } - public static ResponseEntity success(Object content) { - return new ResponseEntity<>(content, HttpStatus.OK); + public static ResponseEntity success(T content) { + return ResponseEntity.ok(content); } - public static ResponseEntity success(String message, Object content) { - Map response = new HashMap<>(); - response.put("message", message); - response.put("content", content); - return new ResponseEntity<>(response, HttpStatus.OK); + public static ResponseEntity> success(String message, T content) { + PicSureResponseBody body = new PicSureResponseBody<>(message, content); + return new ResponseEntity<>(body, HttpStatus.OK); } - public static ResponseEntity error(Object content) { + public static ResponseEntity error(T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, content); } - public static ResponseEntity error(String message, Object content) { + public static ResponseEntity> error(String message, T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); } - public static ResponseEntity error(HttpStatus status, Object content) { + public static ResponseEntity error(HttpStatus status, T content) { if (status == null) { status = DEFAULT_RESPONSE_ERROR_CODE; } return new ResponseEntity<>(content, status); } - public static ResponseEntity error(HttpStatus status, String message, Object content) { - Map response = new HashMap<>(); - response.put("message", message); - response.put("content", content); - return new ResponseEntity<>(response, status); + public static ResponseEntity> error(HttpStatus status, String message, T content) { + return new ResponseEntity<>(new PicSureResponseBody<>(message, content), status); } - public static ResponseEntity applicationError(Object content) { + public static ResponseEntity> applicationError(T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, "Application error", content); } - public static ResponseEntity applicationError(String message, Object content) { + public static ResponseEntity> applicationError(String message, T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); } - public static ResponseEntity riError(Object content) { + public static ResponseEntity> riError(T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, "RI error", content); } - public static ResponseEntity riError(String message, Object content) { + public static ResponseEntity> riError(String message, T content) { return error(DEFAULT_RESPONSE_ERROR_CODE, message, content); } - public static ResponseEntity protocolError(Object content) { + public static ResponseEntity protocolError(T content) { return error(HttpStatus.BAD_REQUEST, content); } - public static ResponseEntity protocolError(String message, Object content) { + public static ResponseEntity> protocolError(String message, T content) { return error(HttpStatus.BAD_REQUEST, message, content); } - public static ResponseEntity unauthorizedError(Object content) { + public static ResponseEntity> unauthorizedError(T content) { return error(HttpStatus.UNAUTHORIZED, "Unauthorized", content); } - public static ResponseEntity unauthorizedError(String message, Object content) { + public static ResponseEntity> unauthorizedError(String message, T content) { return error(HttpStatus.UNAUTHORIZED, message, content); } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PicSureResponseBody.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PicSureResponseBody.java new file mode 100644 index 000000000..a3b878215 --- /dev/null +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/model/response/PicSureResponseBody.java @@ -0,0 +1,4 @@ +package edu.harvard.hms.dbmi.avillach.auth.model.response; + +public record PicSureResponseBody(String message, T content) { +} diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java index 5336b33df..2b877ec52 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AccessRuleController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; import java.util.Optional; import static edu.harvard.hms.dbmi.avillach.auth.utils.AuthNaming.AuthRoleNaming.ADMIN; @@ -55,7 +56,7 @@ public ResponseEntity getAccessRuleById( @Operation(description = "GET a list of existing AccessRules, requires ADMIN or SUPER_ADMIN role") @Secured({ADMIN, SUPER_ADMIN}) @GetMapping("") - public ResponseEntity getAccessRuleAll() { + public ResponseEntity> getAccessRuleAll() { List allAccessRules = this.accessRuleService.getAllAccessRules(); return PICSUREResponse.success(allAccessRules); } @@ -78,7 +79,7 @@ public ResponseEntity addAccessRule( @Operation(description = "Update a list of AccessRules, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @PutMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity updateAccessRule( + public ResponseEntity> updateAccessRule( @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") @RequestBody List accessRules) { accessRules = this.accessRuleService.updateAccessRules(accessRules); @@ -88,7 +89,7 @@ public ResponseEntity updateAccessRule( @Operation(description = "DELETE an AccessRule by Id only if the accessRule is not associated by others, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @DeleteMapping(path = "/{accessRuleId}") - public ResponseEntity removeById( + public ResponseEntity> removeById( @Parameter(required = true, description = "A valid accessRule Id") @PathVariable("accessRuleId") final String accessRuleId) { return PICSUREResponse.success(this.accessRuleService.removeAccessRuleById(accessRuleId)); @@ -97,7 +98,7 @@ public ResponseEntity removeById( @Operation(description = "GET all types listed for the rule in accessRule that could be used, requires SUPER_ADMIN role") @RolesAllowed(SUPER_ADMIN) @GetMapping(path = "/allTypes", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getAllTypes() { + public ResponseEntity> getAllTypes() { return PICSUREResponse.success(AccessRule.TypeNaming.getTypeNameMap()); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java index 2be7e41f8..97332a6bf 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ApplicationController.java @@ -51,14 +51,14 @@ public ResponseEntity getApplicationById( @Operation(description = "GET a list of existing Applications, no role restrictions") @GetMapping - public ResponseEntity getApplicationAll() { + public ResponseEntity> getApplicationAll() { return PICSUREResponse.success(applicationService.getAllApplications()); } @Operation(description = "POST a list of Applications, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(consumes = "application/json", produces = "application/json") - public ResponseEntity addApplication( + public ResponseEntity> addApplication( @Parameter(required = true, description = "A list of AccessRule in JSON format") @RequestBody List applications) { applications = applicationService.addNewApplications(applications); @@ -68,7 +68,7 @@ public ResponseEntity addApplication( @Operation(description = "Update a list of Applications, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(consumes = "application/json", produces = "application/json") - public ResponseEntity updateApplication( + public ResponseEntity> updateApplication( @Parameter(required = true, description = "A list of AccessRule with fields to be updated in JSON format") @RequestBody List applications) { applications = applicationService.updateApplications(applications); @@ -78,7 +78,7 @@ public ResponseEntity updateApplication( @Operation(description = "Refresh a token of an application by application Id, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @GetMapping(value = "/refreshToken/{applicationId}") - public ResponseEntity refreshApplicationToken( + public ResponseEntity> refreshApplicationToken( @Parameter(required = true, description = "A valid application Id") @PathVariable("applicationId") String applicationId) { String newApplicationToken = applicationService.refreshApplicationToken(applicationId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java index c69a5c872..aca8a9ff8 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TOSService.java @@ -5,6 +5,7 @@ import edu.harvard.hms.dbmi.avillach.auth.repository.TermsOfServiceRepository; import edu.harvard.hms.dbmi.avillach.auth.repository.UserRepository; import edu.harvard.hms.dbmi.avillach.auth.rest.TermsOfServiceController; +import jakarta.annotation.Nullable; import jakarta.persistence.NoResultException; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -66,15 +67,11 @@ public Optional updateTermsOfService(String html) { return termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); } - public String getLatest() { - try { - Optional termsOfService = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); - if (termsOfService.isPresent()) { - return termsOfService.get().getContent(); - } - - throw new NoResultException(); - } catch (NoResultException e) { + public @Nullable String getLatest() { + Optional termsOfService = termsOfServiceRepo.findTopByOrderByDateUpdatedDesc(); + if (termsOfService.isPresent()) { + return termsOfService.get().getContent(); + } else { logger.info("Terms Of Service disabled: No Terms of Service found in database"); return null; } @@ -92,7 +89,8 @@ public User acceptTermsOfService(String userId) { throw new RuntimeException("No Terms of Service found in database"); } - logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(), tosDate.get().getDateUpdated()); + String userLogId = !StringUtils.isBlank(user.getEmail()) ? user.getEmail() : user.getGeneralMetadata(); + logger.info("TOS_LOG : User {} accepted the Terms of Service dated {}", userLogId, tosDate.get().getDateUpdated()); return user; } From db16150010b3134dd2ab8e90b5faa28a5716a18a Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 21 May 2024 08:28:40 -0400 Subject: [PATCH 217/222] Change method name --- .../hms/dbmi/avillach/auth/service/impl/TokenService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java index 96cfc9be2..945b28f9c 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/TokenService.java @@ -12,7 +12,6 @@ import edu.harvard.hms.dbmi.avillach.auth.utils.JWTUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -55,7 +54,7 @@ public Map inspectToken(Map inputMap) { logger.info("TokenInspect starting..."); TokenInspection tokenInspection; try { - tokenInspection = _inspectToken(inputMap); + tokenInspection = validateToken(inputMap); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -67,7 +66,7 @@ public Map inspectToken(Map inputMap) { return tokenInspection.getResponseMap(); } - private TokenInspection _inspectToken(Map inputMap) throws IllegalAccessException { + private TokenInspection validateToken(Map inputMap) throws IllegalAccessException { logger.debug("_inspectToken, the incoming token map is: {}", inputMap.entrySet() .stream() .map(entry -> entry.getKey() + " - " + entry.getValue()) From 47ae04368b701b9692e172818bc7b8dc40f00f35 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 21 May 2024 08:35:58 -0400 Subject: [PATCH 218/222] Update role logging in JWTFilter --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 4c1f69fec..2066a61c4 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.util.Set; +import java.util.stream.Collectors; /** @@ -215,7 +216,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } } - logger.info("User with email {} has roles {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).toList() : null); + logger.info("User with email {} has roles {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).collect(Collectors.joining(",")) : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); From dcda7a820742013d82450456042ab31088a50fa6 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 21 May 2024 08:36:15 -0400 Subject: [PATCH 219/222] Remove unused import in JWTFilter.java --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index 2066a61c4..b796813bb 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -15,7 +15,6 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 27988f83d9e3486652857b3c261d0bf42428ca68 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 21 May 2024 08:49:54 -0400 Subject: [PATCH 220/222] Refine return types in API responses Refactored the code to specify the return types in various API responses. Previously, the methods used to return a generic ResponseEntity object, which has now been replaced with ResponseEntity of specific types. This change increases the readability and robustness of the code. --- .../hms/dbmi/avillach/auth/rest/AuthController.java | 8 ++++++-- .../dbmi/avillach/auth/rest/ConnectionWebController.java | 6 +++--- .../hms/dbmi/avillach/auth/rest/PrivilegeController.java | 8 ++++---- .../hms/dbmi/avillach/auth/rest/RoleController.java | 2 +- .../dbmi/avillach/auth/rest/TermsOfServiceController.java | 4 ++-- .../hms/dbmi/avillach/auth/rest/TokenController.java | 2 +- .../hms/dbmi/avillach/auth/rest/UserController.java | 4 ++-- .../auth/rest/UserMetadataMappingWebController.java | 6 +++--- .../avillach/auth/service/impl/AuthenticationService.java | 5 ++--- .../auth/service/impl/FENCEAuthenticationService.java | 5 ++--- .../auth/service/impl/AuthenticationServiceTest.java | 5 ++--- 11 files changed, 28 insertions(+), 27 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java index 1ed79334c..aab56eb57 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/AuthController.java @@ -1,5 +1,6 @@ package edu.harvard.hms.dbmi.avillach.auth.rest; +import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthenticationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.AuthorizationService; import edu.harvard.hms.dbmi.avillach.auth.service.impl.FENCEAuthenticationService; @@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import java.io.IOException; +import java.util.HashMap; import java.util.Map; @@ -59,10 +61,12 @@ public ResponseEntity authentication( if (this.idp_provider.equalsIgnoreCase("fence")) { logger.debug("authentication() FENCE authentication"); - return fenceAuthenticationService.getFENCEProfile(authRequest); + HashMap fenceProfile = fenceAuthenticationService.getFENCEProfile(authRequest); + return PICSUREResponse.success(fenceProfile); } else { logger.debug("authentication() default authentication"); - return authenticationService.getToken(authRequest); + HashMap token = authenticationService.getToken(authRequest); + return PICSUREResponse.success(token); } } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java index 579440d1b..c6113a0ee 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/ConnectionWebController.java @@ -51,7 +51,7 @@ public ResponseEntity getConnectionById( @Operation(description = "GET a list of existing Connection, requires SUPER_ADMIN or ADMIN role") @GetMapping(value = "/", produces = "application/json") @Secured({SUPER_ADMIN, ADMIN}) - public ResponseEntity getAllConnections() { + public ResponseEntity> getAllConnections() { List allConnections = connectionWebService.getAllConnections(); return ResponseEntity.ok(allConnections); } @@ -74,7 +74,7 @@ public ResponseEntity addConnection( @Operation(description = "Update a list of Connections, will only update the fields listed, requires SUPER_ADMIN role") @Secured({SUPER_ADMIN}) @PutMapping(produces = "application/json", consumes = "application/json") - public ResponseEntity updateConnection( + public ResponseEntity> updateConnection( @Parameter(required = true, description = "A list of Connection with fields to be updated in JSON format") @RequestBody List connections) { List responseEntity = connectionWebService.updateConnections(connections); @@ -84,7 +84,7 @@ public ResponseEntity updateConnection( @Operation(description = "DELETE an Connection by Id only if the Connection is not associated by others, requires SUPER_ADMIN role") @Secured({SUPER_ADMIN}) @DeleteMapping(path = "/{connectionId}", produces = "application/json") - public ResponseEntity removeById( + public ResponseEntity> removeById( @Parameter(required = true, description = "A valid connection Id") @PathVariable("connectionId") final String connectionId) { List connections = connectionWebService.removeConnectionById(connectionId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java index 1a78c6b3d..f6a02ff92 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/PrivilegeController.java @@ -50,7 +50,7 @@ public ResponseEntity getPrivilegeById( @Operation(description = "GET a list of existing privileges, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json") - public ResponseEntity getPrivilegeAll() { + public ResponseEntity> getPrivilegeAll() { List privilegesAll = this.privilegeService.getPrivilegesAll(); return PICSUREResponse.success(privilegesAll); } @@ -58,7 +58,7 @@ public ResponseEntity getPrivilegeAll() { @Operation(description = "POST a list of privileges, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PostMapping(consumes = "application/json", produces = "application/json") - public ResponseEntity addPrivilege( + public ResponseEntity> addPrivilege( @Parameter(required = true, description = "A list of privileges in JSON format") @RequestBody List privileges){ privileges = this.privilegeService.addPrivileges(privileges); @@ -68,7 +68,7 @@ public ResponseEntity addPrivilege( @Operation(description = "Update a list of privileges, will only update the fields listed, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @PutMapping(consumes = "application/json", produces = "application/json") - public ResponseEntity updatePrivilege( + public ResponseEntity> updatePrivilege( @Parameter(required = true, description = "A list of privilege with fields to be updated in JSON format") @RequestBody List privileges){ privileges = this.privilegeService.updatePrivileges(privileges); @@ -78,7 +78,7 @@ public ResponseEntity updatePrivilege( @Operation(description = "DELETE an privilege by Id only if the privilege is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(path = "/{privilegeId}", produces = "application/json") - public ResponseEntity removeById( + public ResponseEntity> removeById( @Parameter(required = true, description = "A valid privilege Id") @PathVariable("privilegeId") final String privilegeId) { List privileges = this.privilegeService.deletePrivilegeByPrivilegeId(privilegeId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java index 1688e1413..9d947e82e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/RoleController.java @@ -51,7 +51,7 @@ public ResponseEntity getRoleById( @Operation(description = "GET a list of existing Roles, requires ADMIN or SUPER_ADMIN role") @GetMapping(produces = "application/json") @RolesAllowed({ADMIN, SUPER_ADMIN}) - public ResponseEntity getRoleAll() { + public ResponseEntity> getRoleAll() { List allRoles = this.roleService.getAllRoles(); return PICSUREResponse.success(allRoles); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java index a938298a7..a3b3ad8f3 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TermsOfServiceController.java @@ -46,7 +46,7 @@ public TermsOfServiceController(TOSService tosService, UserService userService) @Operation(description = "GET the latest Terms of Service") @GetMapping(path = "/latest", produces = "text/html") - public ResponseEntity getLatestTermsOfService(){ + public ResponseEntity getLatestTermsOfService(){ logger.info("Getting latest Terms of Service"); return PICSUREResponse.success(tosService.getLatest()); } @@ -65,7 +65,7 @@ public ResponseEntity updateTermsOfService( @Operation(description = "GET if current user has acceptted his TOS or not") @GetMapping(produces = "text/plain") - public ResponseEntity hasUserAcceptedTOS(){ + public ResponseEntity hasUserAcceptedTOS(){ SecurityContext context = SecurityContextHolder.getContext(); String userSubject = context.getAuthentication().getName(); logger.info("hasUserAcceptedTOS for user {}", userSubject); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java index a938a5f29..d492e1a5b 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/TokenController.java @@ -36,7 +36,7 @@ public TokenController(TokenService tokenService) { @Operation(description = "Token introspection endpoint for user to retrieve a valid token") @PostMapping(path = "/inspect", produces = "application/json") - public ResponseEntity inspectToken( + public ResponseEntity> inspectToken( @Parameter(required = true, description = "A JSON object that at least" + " include a user the token for validation") @RequestBody Map inputMap) { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java index b48279e65..8ea14b16e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserController.java @@ -41,7 +41,7 @@ public UserController(UserService userService) { @Operation(description = "GET information of one user with the UUID, requires ADMIN or SUPER_ADMIN roles") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "/{userId}", produces = "application/json") - public ResponseEntity getUserById( + public ResponseEntity getUserById( @Parameter(required = true, description = "The UUID of the user to fetch information about") @PathVariable("userId") String userId) { User userById = this.userService.getUserById(userId); @@ -51,7 +51,7 @@ public ResponseEntity getUserById( @Operation(description = "GET a list of existing users, requires ADMIN or SUPER_ADMIN roles") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json") - public ResponseEntity getUserAll() { + public ResponseEntity> getUserAll() { List entityAll = this.userService.getAllUsers(); return PICSUREResponse.success(entityAll); } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java index 237ba8e13..03a6e022e 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/rest/UserMetadataMappingWebController.java @@ -37,7 +37,7 @@ public UserMetadataMappingWebController(UserMetadataMappingService mappingServic @Operation(description = "GET information of one UserMetadataMapping with the UUID, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(path = "{connectionId}", produces = "application/json") - public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { + public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") String connection) { Connection allMappingsForConnection = this.mappingService.getAllMappingsForConnection(connection); return PICSUREResponse.success(allMappingsForConnection); } @@ -45,7 +45,7 @@ public ResponseEntity getMappingsForConnection(@PathVariable("connectionId") @Operation(description = "GET a list of existing UserMetadataMappings, requires ADMIN or SUPER_ADMIN role") @RolesAllowed({ADMIN, SUPER_ADMIN}) @GetMapping(produces = "application/json") - public ResponseEntity getAllMappings() { + public ResponseEntity> getAllMappings() { List allMappings = mappingService.getAllMappings(); return PICSUREResponse.success(allMappings); } @@ -83,7 +83,7 @@ public ResponseEntity updateMapping( @Operation(description = "DELETE an UserMetadataMapping by Id only if the UserMetadataMapping is not associated by others, requires SUPER_ADMIN role") @RolesAllowed({SUPER_ADMIN}) @DeleteMapping(path = "/{mappingId}", produces = "application/json") - public ResponseEntity removeById( + public ResponseEntity> removeById( @Parameter(required = true, description = "A valid UserMetadataMapping Id") @PathVariable("mappingId") final String mappingId) { List userMetadataMappings = this.mappingService.removeMetadataMappingByIdAndRetrieveAll(mappingId); diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java index 934043394..310a75b15 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationService.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.hms.dbmi.avillach.auth.entity.Connection; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.ConnectionRepository; import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; import org.slf4j.Logger; @@ -68,7 +67,7 @@ public AuthenticationService(OauthUserMatchingService matchingService, UserRepos this.restClientUtil = restClientUtil; } - public ResponseEntity getToken(Map authRequest) throws IOException { + public HashMap getToken(Map authRequest) throws IOException { String accessToken = authRequest.get("access_token"); String redirectURI = authRequest.get("redirectURI"); @@ -118,7 +117,7 @@ public ResponseEntity getToken(Map authRequest) throws IOExce HashMap responseMap = userService.getUserProfileResponse(claims); logger.info("LOGIN SUCCESS ___ {}:{} ___ Authorization will expire at ___ {}___", user.getEmail(), user.getUuid().toString(), responseMap.get("expirationDate")); - return PICSUREResponse.success(responseMap); + return responseMap; } JsonNode retrieveUserInfo(String accessToken) throws IOException { diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java index 7e1254911..59c5f1e59 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/FENCEAuthenticationService.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import edu.harvard.hms.dbmi.avillach.auth.entity.*; import edu.harvard.hms.dbmi.avillach.auth.exceptions.NotAuthorizedException; -import edu.harvard.hms.dbmi.avillach.auth.model.response.PICSUREResponse; import edu.harvard.hms.dbmi.avillach.auth.repository.*; import edu.harvard.hms.dbmi.avillach.auth.utils.RestClientUtil; import jakarta.annotation.PostConstruct; @@ -157,7 +156,7 @@ private JsonNode getFENCEAccessToken(String fence_code) { } // Get access_token from FENCE, based on the provided `code` - public ResponseEntity getFENCEProfile(Map authRequest) { + public HashMap getFENCEProfile(Map authRequest) { logger.debug("getFENCEProfile() starting..."); String fence_code = authRequest.get("code"); @@ -230,7 +229,7 @@ public ResponseEntity getFENCEProfile(Map authRequest) { logger.debug("getFENCEProfile() UserProfile response object has been generated"); logger.debug("getFENCEToken() finished"); - return PICSUREResponse.success(responseMap); + return responseMap; } diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java index 0c9c743b1..8be6cc31d 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/AuthenticationServiceTest.java @@ -90,9 +90,8 @@ public void testGetToken_Successful() throws Exception { // return null for matching user when(matchingService.matchTokenToUser(any())).thenReturn(null); - ResponseEntity response = authenticationService.getToken(authRequest); - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatusCode()); + HashMap token = authenticationService.getToken(authRequest); + assertNotNull(token); } // Additional test to handle retries in user info retrieval From 04508e29a1bd11d3181f8f1fa6920d50234d2066 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Tue, 21 May 2024 09:04:11 -0400 Subject: [PATCH 221/222] Add function to fetch roles by UUIDs in bulk This update includes adding a new method in RoleRepository to fetch multiple roles using a set of UUIDs, and applies this method in UserService. The change aims to ensure all roles can be fetched using their UUIDs and optimizes performance by reducing the number of queries to the database. --- .../auth/repository/RoleRepository.java | 4 ++ .../avillach/auth/service/RoleService.java | 4 ++ .../auth/service/impl/UserService.java | 20 ++++---- .../auth/service/impl/UserServiceTest.java | 47 ++++--------------- 4 files changed, 29 insertions(+), 46 deletions(-) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java index 9aa53a9ad..25e9c0154 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/repository/RoleRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Set; import java.util.UUID; /** @@ -16,4 +17,7 @@ public interface RoleRepository extends JpaRepository { Role findByName(String name); + // find all user roles where uuid is in the list of uuids + Set findByUuidIn(Set uuids); + } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java index b05dbc776..e028c31e0 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/RoleService.java @@ -107,5 +107,9 @@ public void addObjectToSet(Set roles, Role t) { roles.add(t); } + + public Set getRolesByIds(Set roleUuids) { + return this.roleRepository.findByUuidIn(roleUuids); + } } diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java index dc15d73b9..d8d3c1a3d 100644 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserService.java @@ -34,6 +34,7 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.*; +import java.util.stream.Collectors; @Service public class UserService { @@ -243,16 +244,17 @@ public List addUsers(List users) { private void checkAssociation(List users) { for (User user : users) { if (user.getRoles() != null) { - Set roles = new HashSet<>(); - user.getRoles().forEach(t -> { - Optional role = this.roleService.getRoleById(t.getUuid()); - if (role.isEmpty()) { - throw new RuntimeException("Role not found - uuid: " + t.getUuid().toString()); - } - roles.add(role.get()); - }); + Set roleUuids = user.getRoles().stream().map(Role::getUuid).collect(Collectors.toSet()); + Set rolesFromDb = this.roleService.getRolesByIds(roleUuids); + + // If the size of the roles from the database is not the same as the input role UUIDs, then + // we cannot find all roles by the input UUIDs. + if (rolesFromDb.size() != roleUuids.size()) { + logger.error("checkAssociation() cannot find all roles by UUIDs: {}", roleUuids); + throw new IllegalArgumentException("Cannot find all roles by input UUIDs: " + roleUuids); + } - user.setRoles(roles); + user.setRoles(rolesFromDb); } if (user.getConnection() != null) { diff --git a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java index b752da31c..ca305c5cc 100644 --- a/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java +++ b/pic-sure-auth-services/src/test/java/edu/harvard/hms/dbmi/avillach/auth/service/impl/UserServiceTest.java @@ -28,6 +28,7 @@ import java.security.SecureRandom; import java.util.*; +import java.util.stream.Collectors; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -146,10 +147,7 @@ public void testAddUsers() { configureUserSecurityContext(user); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); List result = userService.addUsers(List.of(user)); System.out.println(result); @@ -165,10 +163,7 @@ public void testAddUsers_SuperAdminRole() { configureUserSecurityContext(user); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); List result = userService.addUsers(List.of(user)); assertNotNull(result); @@ -183,10 +178,7 @@ public void testAddUsers_SuperAdminRole_withoutNecessaryPrivileges() { roles.add(createSuperAdminRole()); user.setRoles(roles); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); System.out.println(user.getRoles()); User loggedInUser = createTestUser(); @@ -219,11 +211,7 @@ public void testAddUsers_UserEmailNull_AndBadMetadata() { user.setGeneralMetadata("bad metadata"); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); userService.addUsers(List.of(user)); } @@ -240,10 +228,7 @@ public void testAddUsers_UserEmailNull_AndValidMetadata() { when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); userService.addUsers(List.of(user)); } @@ -254,10 +239,7 @@ public void testUpdateUser() { configureUserSecurityContext(user); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); @@ -272,10 +254,7 @@ public void testUpdateUser_SuperAdminRole() { configureUserSecurityContext(user); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); @@ -290,10 +269,7 @@ public void testUpdateUser_SuperAdminRole_withoutNecessaryPrivileges() { roles.add(createSuperAdminRole()); user.setRoles(roles); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); User loggedInUser = createTestUser(); configureUserSecurityContext(loggedInUser); @@ -509,10 +485,7 @@ public void testUpdateUser_withNoChanges() { when(userRepository.findById(user.getUuid())).thenReturn(Optional.of(user)); when(userRepository.saveAll(List.of(user))).thenReturn(List.of(user)); - doAnswer(invocation -> { - UUID argument = invocation.getArgument(0); - return user.getRoles().stream().filter(role -> role.getUuid().equals(argument)).findFirst(); - }).when(roleService).getRoleById(any(UUID.class)); + doAnswer(invocation -> new HashSet<>(user.getRoles())).when(roleService).getRolesByIds(anySet()); List result = userService.updateUser(List.of(user)); assertEquals(user, result.getFirst()); From 7ae76678a86e6f9617c69223c8a733b279cb1718 Mon Sep 17 00:00:00 2001 From: GeorgeC Date: Wed, 29 May 2024 11:09:26 -0400 Subject: [PATCH 222/222] Add logging for user privileges in JWTFilter --- .../edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java index b796813bb..5c6290318 100755 --- a/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java +++ b/pic-sure-auth-services/src/main/java/edu/harvard/hms/dbmi/avillach/auth/filter/JWTFilter.java @@ -216,6 +216,7 @@ private void setSecurityContextForUser(HttpServletRequest request, HttpServletRe } logger.info("User with email {} has roles {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getName).collect(Collectors.joining(",")) : null); + logger.info("User with email {} has privileges {}.", authenticatedUser.getUser().getEmail(), userRoles != null ? userRoles.stream().map(Role::getPrivileges).collect(Collectors.toSet()) : null); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authenticatedUser, null, authenticatedUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication);