From e6147778888a06648ea5bbcdadb43d8986e74cec Mon Sep 17 00:00:00 2001 From: Marcos Lopez Gonzalez Date: Wed, 26 May 2021 17:14:35 +0200 Subject: [PATCH] added namespace and country rights --- .../identity/service/IdentityService.java | 33 +++ .../identity/service/IdentityServiceImpl.java | 47 +++- .../database/TestCaseDatabaseInitializer.java | 1 + .../test/mocks/IdentityServiceMock.java | 34 ++- .../gbif/registry/ws/it/UserManagementIT.java | 229 +++++++++++++++--- .../persistence/mapper/UserMapper.java | 32 ++- .../persistence/mapper/UserRightsMapper.java | 4 +- .../liquibase/096-country-rights.xml | 17 ++ .../src/main/resources/liquibase/master.xml | 1 + .../persistence/mapper/UserMapper.xml | 78 +++++- .../persistence/mapper/UserRightsMapper.xml | 8 + .../security/EditorAuthorizationService.java | 2 - .../EditorAuthorizationServiceImpl.java | 16 +- .../org/gbif/registry/security/UserRoles.java | 3 + .../collections/merge/BaseMergeService.java | 3 +- .../merge/InstitutionMergeService.java | 3 +- .../ws/resources/UserManagementResource.java | 148 ++++++++++- 17 files changed, 604 insertions(+), 55 deletions(-) create mode 100644 registry-persistence/src/main/resources/liquibase/096-country-rights.xml diff --git a/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityService.java b/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityService.java index 17b1f33853..60604ab40f 100644 --- a/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityService.java +++ b/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityService.java @@ -20,6 +20,7 @@ import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.occurrence.Download; import org.gbif.api.service.common.IdentityAccessService; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.identity.model.UserModelMutationResult; @@ -76,6 +77,8 @@ public interface IdentityService extends IdentityAccessService { */ PagingResponse search(String query, @Nullable Set roles, @Nullable Set editorRightsOn, + @Nullable Set namespaceRightsOn, + @Nullable Set countryRightsOn, @Nullable Pageable page); @@ -175,4 +178,34 @@ UserModelMutationResult updateEmail( * Remove rights from the given entity for the user. */ void deleteEditorRight(String userName, UUID key); + + /** + * Lists the namespaces the user has editor permissions on. + */ + List listNamespaceRights(String userName); + + /** + * Grant the user rights over the given namespace. + */ + void addNamespaceRight(String userName, String namespace); + + /** + * Remove rights from the given namespace for the user. + */ + void deleteNamespaceRight(String userName, String namespace); + + /** + * Lists the countries the user has editor permissions on. + */ + List listCountryRights(String userName); + + /** + * Grant the user rights over the given country. + */ + void addCountryRight(String userName, Country country); + + /** + * Remove rights from the given country for the user. + */ + void deleteCountryRight(String userName, Country country); } diff --git a/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityServiceImpl.java b/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityServiceImpl.java index 2f7e6c7825..fb74552d0d 100644 --- a/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityServiceImpl.java +++ b/registry-identity/src/main/java/org/gbif/registry/identity/service/IdentityServiceImpl.java @@ -22,6 +22,7 @@ import org.gbif.api.model.occurrence.Download; import org.gbif.api.model.registry.PostPersist; import org.gbif.api.model.registry.PrePersist; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.identity.model.ModelMutationError; import org.gbif.registry.identity.model.PropertyConstants; @@ -181,12 +182,22 @@ public GbifUser getBySystemSetting(String key, String value) { @Override public PagingResponse list(@Nullable Pageable pageable) { - return search(null, null, null, pageable); + return search(null, null, null, null, null, pageable); } @Override - public PagingResponse search(@Nullable String query, Set roles, @Nullable Set editorRightsOn, @Nullable Pageable pageable) { - return pagingResponse(pageable, userMapper.count(query, roles, editorRightsOn), userMapper.search(query, roles, editorRightsOn, pageable)); + public PagingResponse search( + @Nullable String query, + Set roles, + @Nullable Set editorRightsOn, + @Nullable Set namespaceRightsOn, + @Nullable Set countryRightsOn, + @Nullable Pageable pageable) { + return pagingResponse( + pageable, + userMapper.count(query, roles, editorRightsOn, namespaceRightsOn, countryRightsOn), + userMapper.search( + query, roles, editorRightsOn, namespaceRightsOn, countryRightsOn, pageable)); } /** @@ -370,4 +381,34 @@ public void addEditorRight(String userName, UUID key) { public void deleteEditorRight(String userName, UUID key) { userMapper.deleteEditorRight(userName, key); } + + @Override + public List listNamespaceRights(String userName) { + return userMapper.listNamespaceRights(userName); + } + + @Override + public void addNamespaceRight(String userName, String namespace) { + userMapper.addNamespaceRight(userName, namespace); + } + + @Override + public void deleteNamespaceRight(String userName, String namespace) { + userMapper.deleteNamespaceRight(userName, namespace); + } + + @Override + public List listCountryRights(String userName) { + return userMapper.listCountryRights(userName); + } + + @Override + public void addCountryRight(String userName, Country country) { + userMapper.addCountryRight(userName, country); + } + + @Override + public void deleteCountryRight(String userName, Country country) { + userMapper.deleteCountryRight(userName, country); + } } diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java b/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java index 1886f4bccd..f8acc71deb 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/database/TestCaseDatabaseInitializer.java @@ -147,6 +147,7 @@ private void truncateTables(DataSource dataSource) throws Exception { * – endpoint_machine_tag * – metasync_history * – namespace_rights + * - country_rights * – network_identifier * – node_contact * – node_endpoint diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/IdentityServiceMock.java b/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/IdentityServiceMock.java index d6119a7a6a..0493403f45 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/IdentityServiceMock.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/test/mocks/IdentityServiceMock.java @@ -19,6 +19,7 @@ import org.gbif.api.model.common.paging.Pageable; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.occurrence.Download; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.identity.model.UserModelMutationResult; import org.gbif.registry.identity.service.IdentityService; @@ -133,7 +134,8 @@ public PagingResponse list(@Nullable Pageable pageable) { } @Override - public PagingResponse search(String query, @Nullable Set roles, @Nullable Set editorRightsOn, @Nullable Pageable page) { + public PagingResponse search(String query, @Nullable Set roles, @Nullable Set editorRightsOn, Set namespaceRightsOn, + @Nullable Set countryRightsOn, @Nullable Pageable page) { throw new UnsupportedOperationException(); } @@ -209,6 +211,36 @@ public void deleteEditorRight(String userName, UUID key) { throw new UnsupportedOperationException(); } + @Override + public List listNamespaceRights(String userName) { + throw new UnsupportedOperationException(); + } + + @Override + public void addNamespaceRight(String userName, String namespace) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteNamespaceRight(String userName, String namespace) { + throw new UnsupportedOperationException(); + } + + @Override + public List listCountryRights(String userName) { + throw new UnsupportedOperationException(); + } + + @Override + public void addCountryRight(String userName, Country country) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteCountryRight(String userName, Country country) { + throw new UnsupportedOperationException(); + } + /** * Return a copy of the user with the lastLogin date set to now. * diff --git a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/UserManagementIT.java b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/UserManagementIT.java index 6d2b030df0..76700663a4 100644 --- a/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/UserManagementIT.java +++ b/registry-integration-tests/src/test/java/org/gbif/registry/ws/it/UserManagementIT.java @@ -18,6 +18,7 @@ import org.gbif.api.model.common.GbifUser; import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.registry.ConfirmationKeyParameter; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.database.TestCaseDatabaseInitializer; import org.gbif.registry.domain.ws.AuthenticationDataParameters; @@ -114,19 +115,22 @@ public void testCreateUser() throws Exception { .getRequest(ALTERNATE_USERNAME, PASSWORD, "/user/login") .andExpect(status().isOk()); - // Test search by rights on entity userTestFixture.prepareAdminUser(); - ResultActions result =requestTestFixture - .getSignedRequest(TEST_ADMIN, "/admin/user/search", - ImmutableMap.builder() - .put("role", UserRole.USER.name()) - .put("1", TEST_ADMIN) - .build()) - .andExpect(status().isOk()); - PagingResponse adminUsers = requestTestFixture.extractJsonResponse(result, new TypeReference>() { }); + ResultActions result = + requestTestFixture + .getSignedRequest( + TEST_ADMIN, + "/admin/user/search", + ImmutableMap.builder() + .put("role", UserRole.USER.name()) + .put("1", TEST_ADMIN) + .build()) + .andExpect(status().isOk()); + PagingResponse adminUsers = + requestTestFixture.extractJsonResponse( + result, new TypeReference>() {}); assertTrue(adminUsers.getCount() == 2); - } @Test @@ -221,13 +225,16 @@ public void getUserFromAdmin() throws Exception { // Test search user role userTestFixture.prepareAdminUser(); - ResultActions result =requestTestFixture - .getSignedRequest(TEST_ADMIN, "/admin/user/search", - ImmutableMap.builder() - .put("role", UserRole.USER.name()) - .build()) - .andExpect(status().isOk()); - PagingResponse adminUsers = requestTestFixture.extractJsonResponse(result, new TypeReference>() { }); + ResultActions result = + requestTestFixture + .getSignedRequest( + TEST_ADMIN, + "/admin/user/search", + ImmutableMap.builder().put("role", UserRole.USER.name()).build()) + .andExpect(status().isOk()); + PagingResponse adminUsers = + requestTestFixture.extractJsonResponse( + result, new TypeReference>() {}); assertTrue(adminUsers.getCount() == 2); } @@ -331,22 +338,26 @@ public void testUserEditorRights() throws Exception { .andExpect(status().isOk()); // Test search by rights on entity - ResultActions rightsSearchResult =requestTestFixture - .getSignedRequest(TEST_ADMIN, "/admin/user/search", - ImmutableMap.builder() - .put("editorRightsOn", key.toString()) - .put("role", UserRoles.USER_ROLE) - .put("q", USERNAME) - .build()) - .andExpect(status().isOk()); - PagingResponse editorUsers = requestTestFixture.extractJsonResponse(rightsSearchResult, new TypeReference>() { }); + ResultActions rightsSearchResult = + requestTestFixture + .getSignedRequest( + TEST_ADMIN, + "/admin/user/search", + ImmutableMap.builder() + .put("editorRightsOn", key.toString()) + .put("role", UserRoles.USER_ROLE) + .put("q", USERNAME) + .build()) + .andExpect(status().isOk()); + PagingResponse editorUsers = + requestTestFixture.extractJsonResponse( + rightsSearchResult, new TypeReference>() {}); assertTrue(editorUsers.getCount() == 1); // Admin delete right requestTestFixture .deleteSignedRequest(TEST_ADMIN, "/admin/user/" + USERNAME + "/editorRight/" + key) .andExpect(status().isNoContent()); - } @Test @@ -439,4 +450,168 @@ public void testChangeEmail() throws Exception { .getRequest(ALTERNATIVE_EMAIL, PASSWORD, "/user/login") .andExpect(status().isOk()); } + + @Test + public void testUserNamespaceRights() throws Exception { + // Create a first admin user; this can't be done through the API + userTestFixture.prepareAdminUser(); + userTestFixture.prepareUser(); + String namespace = "ns.test.com"; + + // Admin add right + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, namespace, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isCreated()); + + // Admin see rights + requestTestFixture + .getSignedRequest(TEST_ADMIN, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isOk()); + + // See own rights + requestTestFixture + .getSignedRequest(USERNAME, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isOk()); + + // Test search by rights on entity + ResultActions rightsSearchResult = + requestTestFixture + .getSignedRequest( + TEST_ADMIN, + "/admin/user/search", + ImmutableMap.builder() + .put("namespaceRightsOn", namespace) + .put("role", UserRoles.USER_ROLE) + .put("q", USERNAME) + .build()) + .andExpect(status().isOk()); + PagingResponse editorUsers = + requestTestFixture.extractJsonResponse( + rightsSearchResult, new TypeReference>() {}); + assertTrue(editorUsers.getCount() == 1); + + // Admin delete right + requestTestFixture + .deleteSignedRequest(TEST_ADMIN, "/admin/user/" + USERNAME + "/namespaceRight/" + namespace) + .andExpect(status().isNoContent()); + } + + @Test + public void testUserNamespaceRightsErrors() throws Exception { + // Create a first admin user; this can't be done through the API + userTestFixture.prepareAdminUser(); + userTestFixture.prepareUser(); + String namespace = "ns2.test.com"; + + // User doesn't exist + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, namespace, "/admin/user/someOtherUser/namespaceRight") + .andExpect(status().isNotFound()); + + // Not an admin user + requestTestFixture + .postSignedRequestPlainText( + USERNAME, namespace, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isForbidden()); + + // Right already exists + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, namespace, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isCreated()); + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, namespace, "/admin/user/" + USERNAME + "/namespaceRight") + .andExpect(status().isConflict()); + + // Right doesn't exist + String randomNamespace = "foo"; + requestTestFixture + .deleteSignedRequest( + TEST_ADMIN, "/admin/user/" + USERNAME + "/namespaceRight/" + randomNamespace) + .andExpect(status().isNotFound()); + } + + @Test + public void testUserCountryRights() throws Exception { + // Create a first admin user; this can't be done through the API + userTestFixture.prepareAdminUser(); + userTestFixture.prepareUser(); + String country = Country.SPAIN.getIso2LetterCode(); + + // Admin add right + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, country, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isCreated()); + + // Admin see rights + requestTestFixture + .getSignedRequest(TEST_ADMIN, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isOk()); + + // See own rights + requestTestFixture + .getSignedRequest(USERNAME, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isOk()); + + // Test search by rights on entity + ResultActions rightsSearchResult = + requestTestFixture + .getSignedRequest( + TEST_ADMIN, + "/admin/user/search", + ImmutableMap.builder() + .put("countryRightsOn", country) + .put("role", UserRoles.USER_ROLE) + .put("q", USERNAME) + .build()) + .andExpect(status().isOk()); + PagingResponse editorUsers = + requestTestFixture.extractJsonResponse( + rightsSearchResult, new TypeReference>() {}); + assertTrue(editorUsers.getCount() == 1); + + // Admin delete right + requestTestFixture + .deleteSignedRequest(TEST_ADMIN, "/admin/user/" + USERNAME + "/countryRight/" + country) + .andExpect(status().isNoContent()); + } + + @Test + public void testUserCountryRightsErrors() throws Exception { + // Create a first admin user; this can't be done through the API + userTestFixture.prepareAdminUser(); + userTestFixture.prepareUser(); + String country = Country.AFGHANISTAN.getIso2LetterCode(); + + // User doesn't exist + requestTestFixture + .postSignedRequestPlainText(TEST_ADMIN, country, "/admin/user/someOtherUser/countryRight") + .andExpect(status().isNotFound()); + + // Not an admin user + requestTestFixture + .postSignedRequestPlainText(USERNAME, country, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isForbidden()); + + // Right already exists + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, country, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isCreated()); + requestTestFixture + .postSignedRequestPlainText( + TEST_ADMIN, country, "/admin/user/" + USERNAME + "/countryRight") + .andExpect(status().isConflict()); + + // Right doesn't exist + String randomCountry = "FO"; + requestTestFixture + .deleteSignedRequest( + TEST_ADMIN, "/admin/user/" + USERNAME + "/countryRight/" + randomCountry) + .andExpect(status().isNotFound()); + } } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserMapper.java index 21749217db..84b01c7c9b 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserMapper.java @@ -17,6 +17,7 @@ import org.gbif.api.model.common.GbifUser; import org.gbif.api.model.common.paging.Pageable; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.persistence.ChallengeCodeSupportMapper; @@ -58,9 +59,19 @@ public interface UserMapper extends ChallengeCodeSupportMapper { void update(GbifUser user); List search( - @Nullable @Param("query") String query, @Nullable @Param("roles") Set roles, @Nullable @Param("editorRightsOn") Set editorRightsOn, @Nullable @Param("page") Pageable page); - - int count(@Nullable @Param("query") String query, @Nullable @Param("roles") Set roles, @Nullable @Param("editorRightsOn") Set editorRightsOn); + @Nullable @Param("query") String query, + @Nullable @Param("roles") Set roles, + @Nullable @Param("editorRightsOn") Set editorRightsOn, + @Nullable @Param("namespaceRightsOn") Set namespaceRightsOn, + @Nullable @Param("countryRightsOn") Set countryRightsOn, + @Nullable @Param("page") Pageable page); + + int count( + @Nullable @Param("query") String query, + @Nullable @Param("roles") Set roles, + @Nullable @Param("editorRightsOn") Set editorRightsOn, + @Nullable @Param("namespaceRightsOn") Set namespaceRightsOn, + @Nullable @Param("countryRightsOn") Set countryRightsOn); /* * Editor rights @@ -70,4 +81,19 @@ List search( void addEditorRight(@Param("userName") String userName, @Param("key") UUID key); void deleteEditorRight(@Param("userName") String userName, @Param("key") UUID key); + + /** Namespace rights. */ + void addNamespaceRight(@Param("userName") String userName, @Param("namespace") String namespace); + + void deleteNamespaceRight( + @Param("userName") String userName, @Param("namespace") String namespace); + + List listNamespaceRights(@Param("userName") String userName); + + /** Country rights */ + void addCountryRight(@Param("userName") String userName, @Param("country") Country country); + + void deleteCountryRight(@Param("userName") String userName, @Param("country") Country country); + + List listCountryRights(@Param("userName") String userName); } diff --git a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserRightsMapper.java b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserRightsMapper.java index df76c34f58..dccd9d2539 100644 --- a/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserRightsMapper.java +++ b/registry-persistence/src/main/java/org/gbif/registry/persistence/mapper/UserRightsMapper.java @@ -28,10 +28,10 @@ public interface UserRightsMapper { List getKeysByUser(@Param("username") String username); - @Deprecated boolean namespaceExistsForUser(@Param("username") String username, @Param("ns") String namespace); - @Deprecated boolean allowedToDeleteMachineTag( @Param("username") String username, @Param("key") int machineTagKey); + + boolean countryExistsForUser(@Param("username") String username, @Param("country") String country); } diff --git a/registry-persistence/src/main/resources/liquibase/096-country-rights.xml b/registry-persistence/src/main/resources/liquibase/096-country-rights.xml new file mode 100644 index 0000000000..fb732c327b --- /dev/null +++ b/registry-persistence/src/main/resources/liquibase/096-country-rights.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/registry-persistence/src/main/resources/liquibase/master.xml b/registry-persistence/src/main/resources/liquibase/master.xml index b1747be771..13b0eeb67d 100644 --- a/registry-persistence/src/main/resources/liquibase/master.xml +++ b/registry-persistence/src/main/resources/liquibase/master.xml @@ -101,4 +101,5 @@ + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserMapper.xml index 2f624ce1a5..fa882ce086 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserMapper.xml @@ -70,9 +70,22 @@ SELECT FROM public.user u - JOIN editor_rights er ON er.username = u.username AND er.key IN - #{item} - + JOIN editor_rights er ON er.username = u.username AND er.key IN + + #{item} + + + + JOIN namespace_rights nr ON nr.username = u.username AND nr.namespace IN + + #{item} + + + + JOIN country_rights cr ON cr.username = u.username AND cr.country IN + + #{item} + @@ -90,9 +103,22 @@ SELECT COUNT(*) FROM public.user u - JOIN editor_rights er ON er.username = u.username AND er.key IN - #{item} - + JOIN editor_rights er ON er.username = u.username AND er.key IN + + #{item} + + + + JOIN namespace_rights nr ON nr.username = u.username AND nr.namespace IN + + #{item} + + + + JOIN country_rights cr ON cr.username = u.username AND cr.country IN + + #{item} + @@ -188,4 +214,44 @@ WHERE username = #{userName,jdbcType=VARCHAR} AND key = #{key,jdbcType=OTHER} + + + INSERT INTO namespace_rights(username,namespace) + VALUES( + #{userName,jdbcType=VARCHAR}, + #{namespace,jdbcType=VARCHAR} + ) + + + + DELETE FROM namespace_rights + WHERE username = #{userName,jdbcType=VARCHAR} + AND namespace = #{namespace,jdbcType=VARCHAR} + + + + + + INSERT INTO country_rights(username,country) + VALUES( + #{userName,jdbcType=VARCHAR}, + #{country,jdbcType=VARCHAR} + ) + + + + DELETE FROM country_rights + WHERE username = #{userName,jdbcType=VARCHAR} + AND country = #{country,jdbcType=VARCHAR} + + + diff --git a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserRightsMapper.xml b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserRightsMapper.xml index f9527bdf37..93aea380d0 100644 --- a/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserRightsMapper.xml +++ b/registry-persistence/src/main/resources/org/gbif/registry/persistence/mapper/UserRightsMapper.xml @@ -32,4 +32,12 @@ WHERE username = #{username} + + diff --git a/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationService.java b/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationService.java index e5ec2f8338..4748714ac7 100644 --- a/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationService.java +++ b/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationService.java @@ -34,7 +34,6 @@ public interface EditorAuthorizationService { * @param ns the namespace in question * @return true if the user is allowed to modify the namespace. */ - @Deprecated boolean allowedToModifyNamespace(@Nullable String name, String ns); /** @@ -44,7 +43,6 @@ public interface EditorAuthorizationService { * @param machineTagKey the machine tag in question * @return true if rights exist for this user to delete the tag. */ - @Deprecated boolean allowedToDeleteMachineTag(@Nullable String name, int machineTagKey); /** diff --git a/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationServiceImpl.java b/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationServiceImpl.java index a9eeac7666..2ae57a232e 100644 --- a/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationServiceImpl.java +++ b/registry-security/src/main/java/org/gbif/registry/security/EditorAuthorizationServiceImpl.java @@ -59,7 +59,6 @@ public EditorAuthorizationServiceImpl( this.machineTagMapper = machineTagMapper; } - @Deprecated @Override public boolean allowedToModifyNamespace(String name, String ns) { if (name == null) { @@ -68,7 +67,6 @@ public boolean allowedToModifyNamespace(String name, String ns) { return userRightsMapper.namespaceExistsForUser(name, ns); } - @Deprecated @Override public boolean allowedToDeleteMachineTag(String name, int machineTagKey) { if (name == null) { @@ -83,8 +81,11 @@ public boolean allowedToCreateMachineTag(String name, UUID datasetKey, MachineTa return false; } - return TagNamespace.GBIF_DEFAULT_TERM.getNamespace().equals(machineTag.getNamespace()) - && allowedToModifyDataset(name, datasetKey); + // it calls the allowedToModifyDataset for convenience since it first checks the + // allowedToModifyEntity and returns if true + return (allowedToModifyNamespace(name, machineTag.getNamespace()) + || (TagNamespace.GBIF_DEFAULT_TERM.getNamespace().equals(machineTag.getNamespace())) + && allowedToModifyDataset(name, datasetKey)); } @Override @@ -98,8 +99,11 @@ public boolean allowedToDeleteMachineTag(String name, UUID datasetKey, int machi return false; } - return TagNamespace.GBIF_DEFAULT_TERM.getNamespace().equals(machineTag.getNamespace()) - && allowedToModifyDataset(name, datasetKey); + // it calls the allowedToModifyDataset for convenience since it first checks the + // allowedToModifyEntity and returns if true + return (allowedToModifyNamespace(name, machineTag.getNamespace()) + || (TagNamespace.GBIF_DEFAULT_TERM.getNamespace().equals(machineTag.getNamespace())) + && allowedToModifyDataset(name, datasetKey)); } @Override diff --git a/registry-security/src/main/java/org/gbif/registry/security/UserRoles.java b/registry-security/src/main/java/org/gbif/registry/security/UserRoles.java index 5faa7fd01a..0e6b06928a 100644 --- a/registry-security/src/main/java/org/gbif/registry/security/UserRoles.java +++ b/registry-security/src/main/java/org/gbif/registry/security/UserRoles.java @@ -38,5 +38,8 @@ private UserRoles() {} public static final String GRSCICOLL_EDITOR_ROLE = "GRSCICOLL_EDITOR"; + public static final String GRSCICOLL_MEDIATOR_ROLE = "GRSCICOLL_MEDIATOR"; + + @Deprecated public static final String IDIGBIO_GRSCICOLL_EDITOR_ROLE = "IDIGBIO_GRSCICOLL_EDITOR"; } diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/merge/BaseMergeService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/merge/BaseMergeService.java index 3172955425..63a1b9e950 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/merge/BaseMergeService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/merge/BaseMergeService.java @@ -52,6 +52,7 @@ import static org.gbif.registry.domain.collections.Constants.IH_NAMESPACE; import static org.gbif.registry.domain.collections.Constants.IRN_TAG; import static org.gbif.registry.security.UserRoles.GRSCICOLL_ADMIN_ROLE; +import static org.gbif.registry.security.UserRoles.GRSCICOLL_MEDIATOR_ROLE; import static org.gbif.registry.security.UserRoles.IDIGBIO_GRSCICOLL_EDITOR_ROLE; public abstract class BaseMergeService< @@ -66,7 +67,7 @@ protected BaseMergeService(PrimaryCollectionEntityService primaryEntityServic this.primaryEntityService = primaryEntityService; } - @Secured({GRSCICOLL_ADMIN_ROLE, IDIGBIO_GRSCICOLL_EDITOR_ROLE}) + @Secured({GRSCICOLL_ADMIN_ROLE, GRSCICOLL_MEDIATOR_ROLE}) @Transactional @Override public void merge(UUID entityToReplaceKey, UUID replacementKey) { diff --git a/registry-service/src/main/java/org/gbif/registry/service/collections/merge/InstitutionMergeService.java b/registry-service/src/main/java/org/gbif/registry/service/collections/merge/InstitutionMergeService.java index fd95750417..4c9db44d98 100644 --- a/registry-service/src/main/java/org/gbif/registry/service/collections/merge/InstitutionMergeService.java +++ b/registry-service/src/main/java/org/gbif/registry/service/collections/merge/InstitutionMergeService.java @@ -46,6 +46,7 @@ import static org.gbif.common.shaded.com.google.common.base.Preconditions.checkArgument; import static org.gbif.registry.security.UserRoles.GRSCICOLL_ADMIN_ROLE; +import static org.gbif.registry.security.UserRoles.GRSCICOLL_MEDIATOR_ROLE; import static org.gbif.registry.security.UserRoles.IDIGBIO_GRSCICOLL_EDITOR_ROLE; /** Service to merge duplicated {@link Institution}. */ @@ -67,7 +68,7 @@ public InstitutionMergeService( this.personService = personService; } - @Secured({GRSCICOLL_ADMIN_ROLE, IDIGBIO_GRSCICOLL_EDITOR_ROLE}) + @Secured({GRSCICOLL_ADMIN_ROLE, GRSCICOLL_MEDIATOR_ROLE}) public UUID convertToCollection( UUID institutionKey, @Nullable UUID institutionKeyForNewCollection, diff --git a/registry-ws/src/main/java/org/gbif/registry/ws/resources/UserManagementResource.java b/registry-ws/src/main/java/org/gbif/registry/ws/resources/UserManagementResource.java index b5e64023dd..a17e5f6f53 100644 --- a/registry-ws/src/main/java/org/gbif/registry/ws/resources/UserManagementResource.java +++ b/registry-ws/src/main/java/org/gbif/registry/ws/resources/UserManagementResource.java @@ -21,6 +21,7 @@ import org.gbif.api.model.common.paging.PagingResponse; import org.gbif.api.model.occurrence.Download; import org.gbif.api.model.registry.ConfirmationKeyParameter; +import org.gbif.api.vocabulary.Country; import org.gbif.api.vocabulary.UserRole; import org.gbif.registry.domain.ws.AuthenticationDataParameters; import org.gbif.registry.domain.ws.EmailChangeRequest; @@ -293,13 +294,20 @@ public PagingResponse search( @Nullable @RequestParam(value = "q", required = false) String query, @Nullable @RequestParam(value = "role", required = false) Set roles, @Nullable @RequestParam(value = "editorRightsOn", required = false) Set editorRightsOn, + @Nullable @RequestParam(value = "namespaceRightsOn", required = false) Set namespaceRightsOn, + @Nullable @RequestParam(value = "countryRightsOn", required = false) Set countryRightsOn, Pageable page) { page = page == null ? new PagingRequest() : page; String q = Optional.ofNullable(query) .map(v -> Strings.nullToEmpty(CharMatcher.WHITESPACE.trimFrom(v))) .orElse(null); - return identityService.search(q, roles, editorRightsOn, page); + Set countries = + Optional.ofNullable(countryRightsOn) + .map(v -> v.stream().map(Country::fromIsoCode).collect(Collectors.toSet())) + .orElse(null); + + return identityService.search(q, roles, editorRightsOn, namespaceRightsOn, countries, page); } /** @@ -451,9 +459,143 @@ public ResponseEntity deleteEditorRight( } } + /** List the namespace rights for a user. */ + @GetMapping("{username}/namespaceRight") + @Secured({ADMIN_ROLE, USER_ROLE}) + public ResponseEntity> namespaceRights( + @PathVariable String username, Authentication authentication) { + // Non-admin users can only see their own entry. + if (!SecurityContextCheck.checkUserInRole(authentication, ADMIN_ROLE)) { + String usernameInContext = authentication.getName(); + if (!usernameInContext.equals(username)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + List rights = identityService.listNamespaceRights(username); + return ResponseEntity.ok(rights); + } + + /** Add a namespace right for a user. */ + @PostMapping( + path = "{username}/namespaceRight", + consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @Secured({ADMIN_ROLE}) + public ResponseEntity addNamespaceRight( + @PathVariable String username, @RequestBody @NotNull String namespace) { + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + if (identityService.listNamespaceRights(username).contains(namespace)) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } else { + identityService.addNamespaceRight(username, namespace); + return ResponseEntity.ok(namespace); + } + } + + /** Delete a namespace right for a user. */ + @DeleteMapping("{username}/namespaceRight/{namespace}") + @Secured(ADMIN_ROLE) + public ResponseEntity deleteNamespaceRight( + @PathVariable String username, @PathVariable String namespace) { + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + if (!identityService.listNamespaceRights(username).contains(namespace)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } else { + identityService.deleteNamespaceRight(username, namespace); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + } + + /** List the namespace rights for a user. */ + @GetMapping("{username}/countryRight") + @Secured({ADMIN_ROLE, USER_ROLE}) + public ResponseEntity> countryRights( + @PathVariable String username, Authentication authentication) { + // Non-admin users can only see their own entry. + if (!SecurityContextCheck.checkUserInRole(authentication, ADMIN_ROLE)) { + String usernameInContext = authentication.getName(); + if (!usernameInContext.equals(username)) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + } + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + List rights = identityService.listCountryRights(username); + return ResponseEntity.ok(rights); + } + + /** Add a country right for a user. */ + @PostMapping( + path = "{username}/countryRight", + consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE}) + @Secured({ADMIN_ROLE}) + public ResponseEntity addCountryRight( + @PathVariable String username, @RequestBody @NotNull String countryParam) { + + final Country country = Country.fromIsoCode(countryParam); + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + if (identityService.listCountryRights(username).contains(country)) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } else { + identityService.addCountryRight(username, country); + return ResponseEntity.ok(country.getIso2LetterCode()); + } + } + + /** Delete a country right for a user. */ + @DeleteMapping("{username}/countryRight/{countryParam}") + @Secured(ADMIN_ROLE) + public ResponseEntity deleteCountryRight( + @PathVariable String username, @PathVariable String countryParam) { + + final Country country = Country.fromIsoCode(countryParam); + + // Ensure user exists + GbifUser currentUser = identityService.get(username); + if (currentUser == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + + if (!identityService.listCountryRights(username).contains(country)) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } else { + identityService.deleteCountryRight(username, country); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + } + /** - * Changes the user email only if the token presented is valid for the user account. The - * username is expected to be present in the security context (authenticated by appkey). + * Changes the user email only if the token presented is valid for the user account. The username + * is expected to be present in the security context (authenticated by appkey). */ @Secured({USER_ROLE}) @PutMapping(path = "changeEmail", consumes = MediaType.APPLICATION_JSON_VALUE)