diff --git a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala index 0299378ce2..a656228398 100644 --- a/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala +++ b/integration/src/test/scala/org/knora/webapi/core/LayersTest.scala @@ -145,6 +145,7 @@ object LayersTest { with TriplestoreService with UsersResponder with UsersRestService + with UserService with ValuesResponderV2 private val commonLayersForAllIntegrationTests = diff --git a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADMSpec.scala b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADMSpec.scala index 9f2771d278..a6219d4f7d 100644 --- a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADMSpec.scala @@ -56,7 +56,7 @@ class UsersMessagesADMSpec extends CoreSpec { permissions = permissions.ofType(PermissionProfileType.Restricted) ) - assert(rootUser.ofType(UserInformationTypeADM.Restricted) === rootUserRestricted) + assert(rootUser.ofType(UserInformationType.Restricted) === rootUserRestricted) } "return true if user is ProjectAdmin in any project " in { diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala index 8eecae4cc1..a5bb77ec65 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala @@ -190,8 +190,8 @@ class GroupsResponderADMSpec extends CoreSpec { UnsafeZioRun.runOrThrow(GroupsResponderADM.groupMembersGetRequest(iri, rootUser)) received.members.map(_.id) should contain allElementsOf Seq( - multiuserUser.ofType(UserInformationTypeADM.Restricted), - imagesReviewerUser.ofType(UserInformationTypeADM.Restricted) + multiuserUser.ofType(UserInformationType.Restricted), + imagesReviewerUser.ofType(UserInformationType.Restricted) ).map(_.id) } diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/UsersResponderSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/UsersResponderSpec.scala index b7a992021b..7914fdf0d7 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/UsersResponderSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/UsersResponderSpec.scala @@ -6,6 +6,7 @@ package org.knora.webapi.responders.admin import org.apache.pekko.testkit.ImplicitSender +import zio.Chunk import zio.ZIO import java.util.UUID @@ -13,9 +14,11 @@ import java.util.UUID import dsp.errors.BadRequestException import dsp.errors.DuplicateValueException import dsp.errors.ForbiddenException +import dsp.errors.NotFoundException import dsp.valueobjects.LanguageCode import org.knora.webapi.* import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.messages.admin.responder.groupsmessages.GroupADM import org.knora.webapi.messages.admin.responder.groupsmessages.GroupMembersGetRequestADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* import org.knora.webapi.messages.admin.responder.usersmessages.* @@ -29,12 +32,11 @@ import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.BasicUserInforma import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.PasswordChangeRequest import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.UserCreateRequest import org.knora.webapi.slice.admin.api.service.UsersRestService +import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.model.* +import org.knora.webapi.slice.admin.domain.service.UserService import org.knora.webapi.util.ZioScalaTestUtil.assertFailsWithA -/** - * This spec is used to test the messages received by the [[UsersResponder]] actor. - */ class UsersResponderSpec extends CoreSpec with ImplicitSender { private val rootUser = SharedTestDataADM.rootUser @@ -46,12 +48,21 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance - "The UsersRestService" when { - "calling getAllUsers" should { + def getProjectMemberShipsByUserIri( + userIri: UserIri + ): ZIO[UsersRestService, Throwable, UserProjectMembershipsGetResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.getProjectMemberShipsByUserIri(userIri)) + + def getProjectAdminMemberShipsByUserIri( + userIri: UserIri + ): ZIO[UsersRestService, Throwable, UserProjectAdminMembershipsGetResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.getProjectAdminMemberShipsByUserIri(userIri)) - def getAllUsers(requestingUser: User): ZIO[UsersRestService, Throwable, UsersGetResponseADM] = - ZIO.serviceWithZIO[UsersRestService](_.getAllUsers(requestingUser)) + def getAllUsers(requestingUser: User): ZIO[UsersRestService, Throwable, UsersGetResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.getAllUsers(requestingUser)) + "The UsersRestService" when { + "calling getAllUsers" should { "with a SystemAdmin should return all real users" in { val response = UnsafeZioRun.runOrThrow(getAllUsers(rootUser)) response.users.nonEmpty should be(true) @@ -65,74 +76,71 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { assertFailsWithA[ForbiddenException](exit) } } - } - "The UsersResponder " when { - "asked about an user identified by 'iri' " should { + "calling getUserByEmail" should { + + def getUserByEmail(email: Email, requestingUser: User): ZIO[UsersRestService, Throwable, UserResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.getUserByEmail(requestingUser, email)) + "return a profile if the user (root user) is known" in { val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByIri( - UserIri.unsafeFrom(rootUser.id), - UserInformationTypeADM.Full, - KnoraSystemInstances.Users.SystemUser - ) + getUserByEmail(Email.unsafeFrom(rootUser.email), KnoraSystemInstances.Users.SystemUser) ) - actual shouldBe Some(rootUser.ofType(UserInformationTypeADM.Full)) + actual.user shouldBe rootUser.ofType(UserInformationType.Restricted) } "return 'None' when the user is unknown" in { - val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByIri( - UserIri.unsafeFrom("http://rdfh.ch/users/notexisting"), - UserInformationTypeADM.Full, - KnoraSystemInstances.Users.SystemUser - ) + val exit = UnsafeZioRun.run( + getUserByEmail(Email.unsafeFrom("userwrong@example.com"), KnoraSystemInstances.Users.SystemUser) ) - actual shouldBe None + assertFailsWithA[NotFoundException](exit) } } - "asked about an user identified by 'email'" should { + "calling getUserByUsername" should { + + def getUserByUsername(requestingUser: User, username: Username) = + ZIO.serviceWithZIO[UsersRestService](_.getUserByUsername(requestingUser, username)) + "return a profile if the user (root user) is known" in { val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByEmail( - Email.unsafeFrom(rootUser.email), - UserInformationTypeADM.Full, - KnoraSystemInstances.Users.SystemUser - ) + getUserByUsername(KnoraSystemInstances.Users.SystemUser, rootUser.getUsername) ) - actual shouldBe Some(rootUser.ofType(UserInformationTypeADM.Full)) + + actual.user shouldBe rootUser.ofType(UserInformationType.Restricted) } "return 'None' when the user is unknown" in { - val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByEmail( - Email.unsafeFrom("userwrong@example.com"), - UserInformationTypeADM.Full, - KnoraSystemInstances.Users.SystemUser + val exit = UnsafeZioRun.run( + getUserByUsername( + KnoraSystemInstances.Users.SystemUser, + Username.unsafeFrom("userwrong") ) ) - actual shouldBe None + assertFailsWithA[NotFoundException](exit) } } - "asked about an user identified by 'username'" should { + } + + "The UsersResponder " when { + "asked about an user identified by 'iri' " should { "return a profile if the user (root user) is known" in { val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByUsername( - Username.unsafeFrom(rootUser.username), - UserInformationTypeADM.Full, + UsersResponder.findUserByIri( + UserIri.unsafeFrom(rootUser.id), + UserInformationType.Full, KnoraSystemInstances.Users.SystemUser ) ) - actual shouldBe Some(rootUser.ofType(UserInformationTypeADM.Full)) + actual shouldBe Some(rootUser.ofType(UserInformationType.Full)) } "return 'None' when the user is unknown" in { val actual = UnsafeZioRun.runOrThrow( - UsersResponder.findUserByUsername( - Username.unsafeFrom("userwrong"), - UserInformationTypeADM.Full, + UsersResponder.findUserByIri( + UserIri.unsafeFrom("http://rdfh.ch/users/notexisting"), + UserInformationType.Full, KnoraSystemInstances.Users.SystemUser ) ) @@ -359,21 +367,20 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { } "asked to update the user's project membership" should { + "ADD user to project" in { // get current project memberships - val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) - membershipsBeforeUpdate.projects should equal(Seq()) + val membershipsBeforeUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) + membershipsBeforeUpdate.projects should equal(Chunk.empty) // add user to images project (00FF) UnsafeZioRun.runOrThrow( UsersResponder.addProjectToUserIsInProject(normalUser.userIri, imagesProject.projectIri, UUID.randomUUID()) ) - val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) - membershipsAfterUpdate.projects should equal(Seq(imagesProject)) + val membershipsAfterUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) + membershipsAfterUpdate.projects.map(_.id) should equal(Chunk(imagesProject.id)) val received = UnsafeZioRun.runOrThrow( ProjectsResponderADM.projectMembersGetRequestADM( @@ -386,8 +393,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "ADD user to project as project admin" in { // get current project memberships - val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) + val membershipsBeforeUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) membershipsBeforeUpdate.projects.map(_.id).sorted should equal(Seq(imagesProject.id).sorted) // add user to images project (00FF) @@ -399,8 +405,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { ) ) - val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) + val membershipsAfterUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) membershipsAfterUpdate.projects.map(_.id).sorted should equal( Seq(imagesProject.id, incunabulaProject.id).sorted ) @@ -416,8 +421,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "DELETE user from project and also as project admin" in { // check project memberships (user should be member of images and incunabula projects) - val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) + val membershipsBeforeUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) membershipsBeforeUpdate.projects.map(_.id).sorted should equal( Seq(imagesProject.id, incunabulaProject.id).sorted ) @@ -433,7 +437,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { // verify that the user has been added as project admin to the images project val projectAdminMembershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) projectAdminMembershipsBeforeUpdate.projects.map(_.id).sorted should equal(Seq(imagesProject.id).sorted) // remove the user as member of the images project @@ -446,20 +450,19 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { ) // verify that the user has been removed as project member of the images project - val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findProjectMemberShipsByIri(normalUser.userIri)) - membershipsAfterUpdate.projects should equal(Seq(incunabulaProject)) + val membershipsAfterUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) + membershipsAfterUpdate.projects.map(_.id) should equal(Chunk(incunabulaProject.id)) // this should also have removed him as project admin from images project val projectAdminMembershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) projectAdminMembershipsAfterUpdate.projects should equal(Seq()) // also check that the user has been removed from the project's list of users val received = UnsafeZioRun.runOrThrow( ProjectsResponderADM.projectMembersGetRequestADM(IriIdentifier.unsafeFrom(imagesProject.id), rootUser) ) - received.members should not contain normalUser.ofType(UserInformationTypeADM.Restricted) + received.members should not contain normalUser.ofType(UserInformationType.Restricted) } } @@ -467,7 +470,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "Not ADD user to project admin group if he is not a member of that project" in { // get the current project admin memberships (should be empty) val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) membershipsBeforeUpdate.projects should equal(Seq()) // try to add user as project admin to images project (expected to fail because he is not a member of the project) @@ -487,7 +490,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "ADD user to project admin group" in { // get the current project admin memberships (should be empty) val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) membershipsBeforeUpdate.projects should equal(Seq()) // add user as project member to images project @@ -506,8 +509,8 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { // get the updated project admin memberships (should contain images project) val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) - membershipsAfterUpdate.projects should equal(Seq(imagesProject)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) + membershipsAfterUpdate.projects.map(_.id) should equal(Seq(imagesProject.id)) // get project admins for images project (should contain normal user) val received = UnsafeZioRun.runOrThrow( @@ -518,8 +521,8 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "DELETE user from project admin group" in { val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) - membershipsBeforeUpdate.projects should equal(Seq(imagesProject)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) + membershipsBeforeUpdate.projects.map(_.id) should equal(Seq(imagesProject.id)) UnsafeZioRun.runOrThrow( UsersResponder.removeProjectFromUserIsInProjectAdminGroup( @@ -530,28 +533,31 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { ) val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findUserProjectAdminMemberships(normalUser.userIri)) + UnsafeZioRun.runOrThrow(getProjectAdminMemberShipsByUserIri(normalUser.userIri)) membershipsAfterUpdate.projects should equal(Seq()) val received = UnsafeZioRun.runOrThrow( ProjectsResponderADM.projectAdminMembersGetRequestADM(IriIdentifier.unsafeFrom(imagesProject.id), rootUser) ) - received.members should not contain normalUser.ofType(UserInformationTypeADM.Restricted) + received.members should not contain normalUser.ofType(UserInformationType.Restricted) } } "asked to update the user's group membership" should { + def findGroupMembershipsByIri(userIri: UserIri): Seq[GroupADM] = + UnsafeZioRun.runOrThrow( + ZIO.serviceWithZIO[UserService](_.findUserByIri(userIri).map(_.map(_.groups).getOrElse(Seq.empty))) + ) + "ADD user to group" in { - val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findGroupMembershipsByIri(normalUser.userIri)) + val membershipsBeforeUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsBeforeUpdate should equal(Seq()) UnsafeZioRun.runOrThrow( UsersResponder.addGroupToUserIsInGroup(normalUser.userIri, imagesReviewerGroup.groupIri, UUID.randomUUID()) ) - val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findGroupMembershipsByIri(normalUser.userIri)) + val membershipsAfterUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsAfterUpdate.map(_.id) should equal(Seq(imagesReviewerGroup.id)) appActor ! GroupMembersGetRequestADM( @@ -564,8 +570,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { } "DELETE user from group" in { - val membershipsBeforeUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findGroupMembershipsByIri(normalUser.userIri)) + val membershipsBeforeUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsBeforeUpdate.map(_.id) should equal(Seq(imagesReviewerGroup.id)) UnsafeZioRun.runOrThrow( @@ -576,8 +581,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { ) ) - val membershipsAfterUpdate = - UnsafeZioRun.runOrThrow(UsersResponder.findGroupMembershipsByIri(normalUser.userIri)) + val membershipsAfterUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsAfterUpdate should equal(Seq()) appActor ! GroupMembersGetRequestADM( diff --git a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala index 3438be999c..4270dd2645 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/core/LayersLive.scala @@ -84,7 +84,7 @@ object LayersLive { ProjectExportStorageService & ProjectImportService & ProjectsResponderADM & QueryTraverser & RepositoryUpdater & ResourcesResponderV2 & ResourceUtilV2 & ResourceUtilV2 & RestCardinalityService & RestResourceInfoService & SearchApiRoutes & SearchResponderV2 & AssetPermissionsResponder & SipiService & StandoffResponderV2 & StandoffTagUtilV2 & - State & StoreRestService & StringFormatter & TriplestoreService & UsersResponder & ValuesResponderV2 & UsersRestService + State & StoreRestService & StringFormatter & TriplestoreService & UsersResponder & ValuesResponderV2 & UsersRestService & UserService /** * All effect layers needed to provide the `Environment` diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala index a57c00174c..5afd2066f8 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala @@ -40,7 +40,7 @@ sealed trait UsersResponderRequestADM extends KnoraRequestADM with RelayedMessag */ case class UserGetByIriADM( identifier: UserIri, - userInformationTypeADM: UserInformationTypeADM = UserInformationTypeADM.Short, + userInformationTypeADM: UserInformationType = UserInformationType.Short, requestingUser: User ) extends UsersResponderRequestADM @@ -126,12 +126,12 @@ case class UserOperationResponseADM(user: User) extends AdminKnoraResponseADM { * sensitive information to the outside world. Since in API Admin [[User]] is returned with some responses, * we use 'restricted' in those cases. */ -sealed trait UserInformationTypeADM -object UserInformationTypeADM { - case object Public extends UserInformationTypeADM - case object Short extends UserInformationTypeADM - case object Restricted extends UserInformationTypeADM - case object Full extends UserInformationTypeADM +sealed trait UserInformationType +object UserInformationType { + case object Public extends UserInformationType + case object Short extends UserInformationType + case object Restricted extends UserInformationType + case object Full extends UserInformationType } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/util/UserUtilADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/util/UserUtilADM.scala index 8dd8ea2626..497d7d8da4 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/util/UserUtilADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/util/UserUtilADM.scala @@ -10,7 +10,7 @@ import zio.* import dsp.errors.ForbiddenException import dsp.errors.NotFoundException import org.knora.webapi.IRI -import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM.Full +import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationType.Full import org.knora.webapi.messages.util.KnoraSystemInstances.Users.SystemUser import org.knora.webapi.responders.admin.UsersResponder import org.knora.webapi.slice.admin.domain.model.User diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala index ba5fd49bb6..7fe6e8f706 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/GroupsResponderADM.scala @@ -277,7 +277,7 @@ final case class GroupsResponderADMLive( .ask[Option[User]]( UserGetByIriADM( UserIri.unsafeFrom(userIri), - UserInformationTypeADM.Restricted, + UserInformationType.Restricted, KnoraSystemInstances.Users.SystemUser ) ) diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala index 084c0b7333..d0b6bd3e23 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala @@ -22,7 +22,7 @@ import org.knora.webapi.messages.admin.responder.permissionsmessages.* import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* import org.knora.webapi.messages.admin.responder.projectsmessages.* import org.knora.webapi.messages.admin.responder.usersmessages.UserGetByIriADM -import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM +import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationType import org.knora.webapi.messages.store.cacheservicemessages.CacheServiceClearCache import org.knora.webapi.messages.store.triplestoremessages.* import org.knora.webapi.messages.twirl.queries.sparql @@ -289,7 +289,7 @@ final case class ProjectsResponderADMLive( .ask[Option[User]]( UserGetByIriADM( identifier = UserIri.unsafeFrom(userIri), - userInformationTypeADM = UserInformationTypeADM.Restricted, + userInformationTypeADM = UserInformationType.Restricted, requestingUser = KnoraSystemInstances.Users.SystemUser ) ) @@ -343,7 +343,7 @@ final case class ProjectsResponderADMLive( .ask[Option[User]]( UserGetByIriADM( identifier = UserIri.unsafeFrom(userIri), - userInformationTypeADM = UserInformationTypeADM.Restricted, + userInformationTypeADM = UserInformationType.Restricted, requestingUser = KnoraSystemInstances.Users.SystemUser ) ) diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/UsersResponder.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/UsersResponder.scala index a7c8396f0a..9c72b58f09 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/UsersResponder.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/UsersResponder.scala @@ -16,19 +16,13 @@ import zio.ZLayer import java.util.UUID import dsp.errors.* -import org.knora.webapi.* import org.knora.webapi.config.AppConfig import org.knora.webapi.core.MessageHandler import org.knora.webapi.core.MessageRelay import org.knora.webapi.messages.ResponderRequest import org.knora.webapi.messages.StringFormatter -import org.knora.webapi.messages.admin.responder.groupsmessages.GroupADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* import org.knora.webapi.messages.admin.responder.usersmessages.UserOperationResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.* -import org.knora.webapi.messages.util.KnoraSystemInstances.Users import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder @@ -84,55 +78,10 @@ final case class UsersResponder( */ def findUserByIri( identifier: UserIri, - userInformationType: UserInformationTypeADM, + userInformationType: UserInformationType, requestingUser: User ): Task[Option[User]] = - userService.findUserByIri(identifier).map(_.map(filterUserInformation(_, requestingUser, userInformationType))) - - /** - * If the requesting user is a system admin, or is requesting themselves, or is a system user, - * returns the user in the requested format. Otherwise, returns only public information. - * @param user the user to be returned - * @param requestingUser the user requesting the information - * @param infoType the type of information requested - * @return - */ - private def filterUserInformation(user: User, requestingUser: User, infoType: UserInformationTypeADM): User = - if (requestingUser.permissions.isSystemAdmin || requestingUser.id == user.id || requestingUser.isSystemUser) - user.ofType(infoType) - else user.ofType(UserInformationTypeADM.Public) - - /** - * Gets information about a Knora user, and returns it as a [[User]]. - * - * @param email the email of the user. - * @param userInformationType the type of the requested profile (restricted - * of full). - * @param requestingUser the user initiating the request. - * @return a [[User]] describing the user. - */ - def findUserByEmail( - email: Email, - userInformationType: UserInformationTypeADM, - requestingUser: User - ): Task[Option[User]] = - userService.findUserByEmail(email).map(_.map(filterUserInformation(_, requestingUser, userInformationType))) - - /** - * Gets information about a Knora user, and returns it as a [[User]]. - * - * @param username the username of the user. - * @param userInformationType the type of the requested profile (restricted - * of full). - * @param requestingUser the user initiating the request. - * @return a [[User]] describing the user. - */ - def findUserByUsername( - username: Username, - userInformationType: UserInformationTypeADM, - requestingUser: User - ): Task[Option[User]] = - userService.findUserByUsername(username).map(_.map(filterUserInformation(_, requestingUser, userInformationType))) + userService.findUserByIri(identifier).map(_.map(_.filterUserInformation(requestingUser, userInformationType))) /** * Updates an existing user. Only basic user data information (username, email, givenName, familyName, lang) @@ -206,7 +155,7 @@ final case class UsersResponder( .map(PasswordHash.unsafeFrom) .mapBoth( _ => ForbiddenException("The requesting user has no password."), - pwHash => passwordService.matches(changeRequest.requesterPassword, pwHash) + passwordService.matches(changeRequest.requesterPassword, _) ) .filterOrFail(identity)( ForbiddenException("The supplied password does not match the requesting user's password.") @@ -255,31 +204,6 @@ final case class UsersResponder( IriLocker.runWithIriLock(apiRequestId, userIri.value, updateTask) } - /** - * Returns user's project memberships as a sequence of [[ProjectADM]]. - * - * @param userIri the IRI of the user. - * @return a sequence of [[ProjectADM]] - */ - private def userProjectMembershipsGetADM(userIri: IRI) = - findUserByIri(UserIri.unsafeFrom(userIri), UserInformationTypeADM.Full, Users.SystemUser) - .map(_.map(_.projects).getOrElse(Seq.empty)) - - /** - * Returns the user's project memberships as [[UserProjectMembershipsGetResponseADM]]. - * - * @param userIri the user's IRI. - * @return a [[UserProjectMembershipsGetResponseADM]]. - */ - def findProjectMemberShipsByIri(userIri: UserIri): Task[UserProjectMembershipsGetResponseADM] = - for { - _ <- - ZIO.whenZIO(userRepo.existsById(userIri).negate)( - ZIO.fail(BadRequestException(s"User $userIri does not exist.")) - ) - projects <- userProjectMembershipsGetADM(userIri.value) - } yield UserProjectMembershipsGetResponseADM(projects) - /** * Adds a user to a project. * @@ -338,24 +262,6 @@ final case class UsersResponder( IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) } - /** - * Returns the user's project admin group memberships, where the result contains the IRIs of the projects the user - * is a member of the project admin group. - * - * @param userIri the user's IRI. - * @return a [[UserProjectAdminMembershipsGetResponseADM]]. - */ - def findUserProjectAdminMemberships(userIri: UserIri): Task[UserProjectAdminMembershipsGetResponseADM] = - ZIO.whenZIO(userRepo.existsById(userIri).negate)( - ZIO.fail(BadRequestException(s"User ${userIri.value} does not exist.")) - ) *> (for { - kUser <- userRepo - .findById(userIri) - .someOrFail(NotFoundException(s"The user $userIri does not exist.")) - requests = kUser.isInProjectAdminGroup.map(IriIdentifier.from).map(ProjectGetADM.apply) - projects <- ZIO.foreach(requests)(messageRelay.ask[Option[ProjectADM]](_)) - } yield projects.flatten).map(UserProjectAdminMembershipsGetResponseADM) - /** * Adds a user to the project admin group of a project. * @@ -419,16 +325,6 @@ final case class UsersResponder( IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) } - /** - * Returns the user's group memberships as a sequence of [[GroupADM]] - * - * @param userIri the IRI of the user. - * @return a sequence of [[GroupADM]]. - */ - def findGroupMembershipsByIri(userIri: UserIri): Task[Seq[GroupADM]] = - findUserByIri(userIri, UserInformationTypeADM.Full, Users.SystemUser) - .map(_.map(_.groups).getOrElse(Seq.empty)) - /** * Adds a user to a group. * @@ -489,22 +385,24 @@ final case class UsersResponder( * Updates an existing user. Should not be directly used from the receive method. * * @param userIri the IRI of the existing user that we want to update. - * @param req the updated information. + * @param theUpdate the updated information. * @return a [[UserOperationResponseADM]]. * fails with a BadRequestException if necessary parameters are not supplied. * fails with a UpdateNotPerformedException if the update was not performed. */ - private def updateUserADM(userIri: UserIri, req: UserChangeRequest): ZIO[Any, Throwable, UserOperationResponseADM] = + private def updateUserADM( + userIri: UserIri, + theUpdate: UserChangeRequest + ): ZIO[Any, Throwable, UserOperationResponseADM] = for { - _ <- ensureNotABuiltInUser(userIri) - currentUser <- userRepo - .findById(userIri) - .someOrFail(NotFoundException(s"User '$userIri' not found.")) - _ <- userService.updateUser(currentUser, req) + _ <- ensureNotABuiltInUser(userIri) + currentUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"User '$userIri' not found.")) + _ <- userService.updateUser(currentUser, theUpdate) updatedUserADM <- - findUserByIri(userIri, UserInformationTypeADM.Full, Users.SystemUser) + userService + .findUserByIri(userIri) .someOrFail(UpdateNotPerformedException("User was not updated. Please report this as a possible bug.")) - } yield UserOperationResponseADM(updatedUserADM.ofType(UserInformationTypeADM.Restricted)) + } yield UserOperationResponseADM(updatedUserADM.ofType(UserInformationType.Restricted)) /** * Creates a new user. Self-registration is allowed, so even the default user, i.e. with no credentials supplied, @@ -538,10 +436,10 @@ final case class UsersResponder( userIri, req.username, req.email, - familyName = req.familyName, + req.familyName, req.givenName, passwordHash, - preferredLanguage = req.lang, + req.lang, req.status, Chunk.empty, Chunk.empty, @@ -551,11 +449,11 @@ final case class UsersResponder( _ <- userRepo.save(newUser) createdUser <- - findUserByIri(userIri, UserInformationTypeADM.Full, Users.SystemUser).someOrFail { - val msg = s"User ${userIri.value} was not created. Please report this as a possible bug." - UpdateNotPerformedException(msg) + userService.findUserByIri(userIri).someOrFail { + UpdateNotPerformedException(s"User ${userIri.value} was not created. Please report this as a possible bug.") } - } yield UserOperationResponseADM(createdUser.ofType(UserInformationTypeADM.Restricted)) + + } yield UserOperationResponseADM(createdUser.ofType(UserInformationType.Restricted)) IriLocker.runWithIriLock(apiRequestID, USERS_GLOBAL_LOCK_IRI, createNewUserTask) } @@ -578,30 +476,11 @@ object UsersResponder { def findUserByIri( identifier: UserIri, - userInformationType: UserInformationTypeADM, + userInformationType: UserInformationType, requestingUser: User ): ZIO[UsersResponder, Throwable, Option[User]] = ZIO.serviceWithZIO[UsersResponder](_.findUserByIri(identifier, userInformationType, requestingUser)) - def findUserByEmail( - email: Email, - userInformationType: UserInformationTypeADM, - requestingUser: User - ): ZIO[UsersResponder, Throwable, Option[User]] = - ZIO.serviceWithZIO[UsersResponder](_.findUserByEmail(email, userInformationType, requestingUser)) - - def findUserByUsername( - username: Username, - userInformationType: UserInformationTypeADM, - requestingUser: User - ): ZIO[UsersResponder, Throwable, Option[User]] = - ZIO.serviceWithZIO[UsersResponder](_.findUserByUsername(username, userInformationType, requestingUser)) - - def findProjectMemberShipsByIri( - userIri: UserIri - ): ZIO[UsersResponder, Throwable, UserProjectMembershipsGetResponseADM] = - ZIO.serviceWithZIO[UsersResponder](_.findProjectMemberShipsByIri(userIri)) - def addProjectToUserIsInProject( userIri: UserIri, projectIri: ProjectIri, @@ -634,14 +513,6 @@ object UsersResponder { _.removeProjectFromUserIsInProjectAdminGroup(userIri, projectIri, apiRequestID) ) - def findUserProjectAdminMemberships( - userIri: UserIri - ): ZIO[UsersResponder, Throwable, UserProjectAdminMembershipsGetResponseADM] = - ZIO.serviceWithZIO[UsersResponder](_.findUserProjectAdminMemberships(userIri)) - - def findGroupMembershipsByIri(userIri: UserIri): ZIO[UsersResponder, Throwable, Seq[GroupADM]] = - ZIO.serviceWithZIO[UsersResponder](_.findGroupMembershipsByIri(userIri)) - def createNewUserADM( req: UserCreateRequest, apiRequestID: UUID diff --git a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala index 7d5125634f..7f3a5bee84 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/Authenticator.scala @@ -5,7 +5,6 @@ package org.knora.webapi.routing -import com.typesafe.scalalogging.Logger import org.apache.commons.codec.binary.Base32 import org.apache.pekko.http.scaladsl.model.* import org.apache.pekko.http.scaladsl.model.headers @@ -13,7 +12,6 @@ import org.apache.pekko.http.scaladsl.model.headers.HttpCookie import org.apache.pekko.http.scaladsl.model.headers.HttpCookiePair import org.apache.pekko.http.scaladsl.server.RequestContext import org.apache.pekko.util.ByteString -import org.slf4j.LoggerFactory import spray.json.* import zio.* import zio.macros.accessible @@ -30,7 +28,6 @@ import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredenti import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraPasswordCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages.KnoraCredentialsV2.KnoraSessionCredentialsV2 import org.knora.webapi.messages.v2.routing.authenticationmessages.* -import org.knora.webapi.responders.admin.UsersResponder import org.knora.webapi.routing.Authenticator.AUTHENTICATION_INVALIDATION_CACHE_NAME import org.knora.webapi.routing.Authenticator.BAD_CRED_NONE_SUPPLIED import org.knora.webapi.routing.Authenticator.BAD_CRED_NOT_VALID @@ -39,6 +36,7 @@ import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.admin.domain.model.Username import org.knora.webapi.slice.admin.domain.service.PasswordService +import org.knora.webapi.slice.admin.domain.service.UserService import org.knora.webapi.util.cache.CacheUtil /** @@ -156,14 +154,12 @@ object Authenticator { final case class AuthenticatorLive( private val appConfig: AppConfig, - private val usersResponder: UsersResponder, + private val userService: UserService, private val jwtService: JwtService, private val passwordService: PasswordService, private implicit val stringFormatter: StringFormatter ) extends Authenticator { - private val logger = Logger(LoggerFactory.getLogger(this.getClass)) - /** * Checks if the provided credentials are valid, and if so returns a JWT token for the client to save. * @@ -375,7 +371,7 @@ final case class AuthenticatorLive( if (credentials.isEmpty) { ZIO.succeed(KnoraSystemInstances.Users.AnonymousUser) } else { - getUserADMThroughCredentialsV2(credentials).map(_.ofType(UserInformationTypeADM.Full)) + getUserADMThroughCredentialsV2(credentials).map(_.ofType(UserInformationType.Full)) } } @@ -441,10 +437,8 @@ final case class AuthenticatorLive( private def extractCredentialsV2(requestContext: RequestContext): Option[KnoraCredentialsV2] = { val credentialsFromParameters: Option[KnoraCredentialsV2] = extractCredentialsFromParametersV2(requestContext) - logger.debug("extractCredentialsV2 - credentialsFromParameters: {}", credentialsFromParameters) val credentialsFromHeaders: Option[KnoraCredentialsV2] = extractCredentialsFromHeaderV2(requestContext) - logger.debug("extractCredentialsV2 - credentialsFromHeader: {}", credentialsFromHeaders) // return found credentials based on precedence: 1. url parameters, 2. header (basic auth, token) val credentials = if (credentialsFromParameters.nonEmpty) { @@ -453,7 +447,6 @@ final case class AuthenticatorLive( credentialsFromHeaders } - logger.debug("extractCredentialsV2 - returned credentials: '{}'", credentials) credentials } @@ -656,9 +649,7 @@ final case class AuthenticatorLive( * [[BadCredentialsException]] when either the supplied email is empty or no user with such an email could be found. */ override def getUserByIri(iri: UserIri): Task[User] = - usersResponder - .findUserByIri(iri, UserInformationTypeADM.Full, KnoraSystemInstances.Users.SystemUser) - .someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) + userService.findUserByIri(iri).someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) /** * Tries to get a [[User]]. @@ -669,9 +660,7 @@ final case class AuthenticatorLive( * [[BadCredentialsException]] when either the supplied email is empty or no user with such an email could be found. */ override def getUserByEmail(email: Email): Task[User] = - usersResponder - .findUserByEmail(email, UserInformationTypeADM.Full, KnoraSystemInstances.Users.SystemUser) - .someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) + userService.findUserByEmail(email).someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) /** * Tries to get a [[User]]. @@ -682,9 +671,7 @@ final case class AuthenticatorLive( * [[BadCredentialsException]] when either the supplied email is empty or no user with such an email could be found. */ override def getUserByUsername(username: Username): Task[User] = - usersResponder - .findUserByUsername(username, UserInformationTypeADM.Full, KnoraSystemInstances.Users.SystemUser) - .someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) + userService.findUserByUsername(username).someOrFail(BadCredentialsException(BAD_CRED_NOT_VALID)) /** * Calculates the cookie name, where the external host and port are encoded as a base32 string diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala index 157ee275a4..adb351d0b5 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpointsHandler.scala @@ -53,12 +53,12 @@ case class UsersEndpointsHandler( private val getUsersByIriProjectMemberShipsHandler = PublicEndpointHandler( usersEndpoints.get.usersByIriProjectMemberShips, - restService.getProjectMemberShipsByIri + restService.getProjectMemberShipsByUserIri ) private val getUsersByIriProjectAdminMemberShipsHandler = PublicEndpointHandler( usersEndpoints.get.usersByIriProjectAdminMemberShips, - restService.getProjectAdminMemberShipsByIri + restService.getProjectAdminMemberShipsByUserIri ) private val getUsersByIriGroupMembershipsHandler = PublicEndpointHandler( diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/UsersRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/UsersRestService.scala index 318546c9bd..a83add2564 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/UsersRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/UsersRestService.scala @@ -11,7 +11,7 @@ import dsp.errors.BadRequestException import dsp.errors.NotFoundException import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.usersmessages.UserGroupMembershipsGetResponseADM -import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM +import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationType import org.knora.webapi.messages.admin.responder.usersmessages.UserOperationResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.UserProjectAdminMembershipsGetResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.UserProjectMembershipsGetResponseADM @@ -29,6 +29,8 @@ import org.knora.webapi.slice.admin.domain.model.User import org.knora.webapi.slice.admin.domain.model.UserIri import org.knora.webapi.slice.admin.domain.model.UserStatus import org.knora.webapi.slice.admin.domain.model.Username +import org.knora.webapi.slice.admin.domain.service.KnoraUserRepo +import org.knora.webapi.slice.admin.domain.service.ProjectADMService import org.knora.webapi.slice.admin.domain.service.UserService import org.knora.webapi.slice.common.api.AuthorizationRestService import org.knora.webapi.slice.common.api.KnoraResponseRenderer @@ -36,6 +38,8 @@ import org.knora.webapi.slice.common.api.KnoraResponseRenderer final case class UsersRestService( auth: AuthorizationRestService, userService: UserService, + userRepo: KnoraUserRepo, + projectService: ProjectADMService, responder: UsersResponder, format: KnoraResponseRenderer ) { @@ -58,15 +62,19 @@ final case class UsersRestService( } yield external def getUserByEmail(requestingUser: User, email: Email): Task[UserResponseADM] = for { - internal <- responder - .findUserByEmail(email, UserInformationTypeADM.Restricted, requestingUser) - .someOrFail(NotFoundException(s"User with email '${email.value}' not found")) - .map(UserResponseADM.apply) + user <- userService + .findUserByEmail(email) + .someOrFail(NotFoundException(s"User with email '${email.value}' not found")) + internal = UserResponseADM(user.filterUserInformation(requestingUser, UserInformationType.Restricted)) external <- format.toExternal(internal) } yield external def getGroupMemberShipsByIri(userIri: UserIri): Task[UserGroupMembershipsGetResponseADM] = - responder.findGroupMembershipsByIri(userIri).map(UserGroupMembershipsGetResponseADM).flatMap(format.toExternal) + userService + .findUserByIri(userIri) + .map(_.map(_.groups).getOrElse(Seq.empty)) + .map(UserGroupMembershipsGetResponseADM) + .flatMap(format.toExternal) def createUser(requestingUser: User, userCreateRequest: Requests.UserCreateRequest): Task[UserOperationResponseADM] = for { @@ -76,23 +84,34 @@ final case class UsersRestService( external <- format.toExternal(internal) } yield external - def getProjectMemberShipsByIri(userIri: UserIri): Task[UserProjectMembershipsGetResponseADM] = - responder.findProjectMemberShipsByIri(userIri).flatMap(format.toExternal) + def getProjectMemberShipsByUserIri(userIri: UserIri): Task[UserProjectMembershipsGetResponseADM] = + for { + kUser <- getKnoraUserOrNotFound(userIri) + projects <- projectService.findByIds(kUser.isInProject) + external <- format.toExternal(UserProjectMembershipsGetResponseADM(projects)) + } yield external - def getProjectAdminMemberShipsByIri(userIri: UserIri): Task[UserProjectAdminMembershipsGetResponseADM] = - responder.findUserProjectAdminMemberships(userIri).flatMap(format.toExternal) + private def getKnoraUserOrNotFound(userIri: UserIri) = + userRepo.findById(userIri).someOrFail(NotFoundException(s"User with iri ${userIri.value} not found.")) + + def getProjectAdminMemberShipsByUserIri(userIri: UserIri): Task[UserProjectAdminMembershipsGetResponseADM] = + for { + kUser <- getKnoraUserOrNotFound(userIri) + projects <- projectService.findByIds(kUser.isInProjectAdminGroup) + external <- format.toExternal(UserProjectAdminMembershipsGetResponseADM(projects)) + } yield external def getUserByUsername(requestingUser: User, username: Username): Task[UserResponseADM] = for { - internal <- responder - .findUserByUsername(username, UserInformationTypeADM.Restricted, requestingUser) - .someOrFail(NotFoundException(s"User with username '${username.value}' not found")) - .map(UserResponseADM.apply) + user <- userService + .findUserByUsername(username) + .someOrFail(NotFoundException(s"User with username '${username.value}' not found")) + internal = UserResponseADM(user.filterUserInformation(requestingUser, UserInformationType.Restricted)) external <- format.toExternal(internal) } yield external def getUserByIri(requestingUser: User, userIri: UserIri): Task[UserResponseADM] = for { internal <- responder - .findUserByIri(userIri, UserInformationTypeADM.Restricted, requestingUser) + .findUserByIri(userIri, UserInformationType.Restricted, requestingUser) .someOrFail(NotFoundException(s"User '${userIri.value}' not found")) .map(UserResponseADM.apply) external <- format.toExternal(internal) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/User.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/User.scala index f8f401419b..3050b2ed7a 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/User.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/User.scala @@ -17,7 +17,7 @@ import org.knora.webapi.messages.OntologyConstants import org.knora.webapi.messages.admin.responder.groupsmessages.GroupADM import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM -import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM +import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationType import org.knora.webapi.messages.admin.responder.usersmessages.UsersADMJsonProtocol import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.common.IntValueCompanion @@ -88,9 +88,9 @@ final case class User( * * @return a [[User]] */ - def ofType(userTemplateType: UserInformationTypeADM): User = + def ofType(userTemplateType: UserInformationType): User = userTemplateType match { - case UserInformationTypeADM.Public => + case UserInformationType.Public => self.copy( username = "", email = "", @@ -101,16 +101,16 @@ final case class User( projects = Seq.empty[ProjectADM], permissions = PermissionsDataADM() ) - case UserInformationTypeADM.Short => + case UserInformationType.Short => self.copy( password = None, groups = Seq.empty[GroupADM], projects = Seq.empty[ProjectADM], permissions = PermissionsDataADM() ) - case UserInformationTypeADM.Restricted => + case UserInformationType.Restricted => self.copy(password = None) - case UserInformationTypeADM.Full => + case UserInformationType.Full => self } @@ -129,6 +129,11 @@ final case class User( def toJsValue: JsValue = UsersADMJsonProtocol.userADMFormat.write(this) def isAnonymousUser: Boolean = id.equalsIgnoreCase(OntologyConstants.KnoraAdmin.AnonymousUser) + + def filterUserInformation(requestingUser: User, infoType: UserInformationType): User = + if (requestingUser.permissions.isSystemAdmin || requestingUser.id == this.id || requestingUser.isSystemUser) + self.ofType(infoType) + else self.ofType(UserInformationType.Public) } final case class UserIri private (value: String) extends AnyVal with StringValue diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala index cce26b5a57..378b9c1933 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala @@ -30,6 +30,8 @@ final case class ProjectADMService( def findById(id: ProjectIri): Task[Option[ProjectADM]] = findByProjectIdentifier(ProjectIdentifierADM.from(id)) + def findByIds(id: Seq[ProjectIri]): Task[Seq[ProjectADM]] = ZIO.foreach(id)(findById).map(_.flatten) + def findByProjectIdentifier(projectId: ProjectIdentifierADM): Task[Option[ProjectADM]] = cacheService.getProjectADM(projectId).flatMap { case Some(project) => ZIO.some(project)