From e78a10618e2489a52e61d3a955637e5e6fd5b7ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Wed, 28 Feb 2024 16:23:41 +0100 Subject: [PATCH] refactor: Move code from UsersResponder to UserService and UserRestService (#3069) --- .../responders/admin/UsersResponderSpec.scala | 66 ++++--- webapi/src/main/scala/dsp/errors/Errors.scala | 5 +- .../usersmessages/UsersMessagesADM.scala | 94 ---------- .../responders/admin/GroupsResponderADM.scala | 4 +- .../responders/admin/UsersResponder.scala | 166 +++--------------- .../knora/webapi/routing/RouteUtilADM.scala | 1 - .../slice/admin/api/UsersEndpoints.scala | 25 ++- .../admin/api/UsersEndpointsHandler.scala | 29 ++- .../admin/api/service/UsersRestService.scala | 88 ++++++---- .../admin/domain/service/UserService.scala | 50 +++++- 10 files changed, 194 insertions(+), 334 deletions(-) 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 7914fdf0d7..cee4136fe7 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 @@ -32,6 +32,7 @@ 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.KnoraProject.ProjectIri 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 @@ -61,6 +62,27 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { def getAllUsers(requestingUser: User): ZIO[UsersRestService, Throwable, UsersGetResponseADM] = ZIO.serviceWithZIO[UsersRestService](_.getAllUsers(requestingUser)) + def addGroupToUserIsInGroup( + requestingUser: User, + userIri: UserIri, + groupIri: GroupIri + ): ZIO[UsersRestService, Throwable, UserResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.addGroupToUserIsInGroup(requestingUser, userIri, groupIri)) + + def addProjectToUserIsInProject( + requestingUser: User, + userIri: UserIri, + projectIri: ProjectIri + ): ZIO[UsersRestService, Throwable, UserResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.addProjectToUserIsInProject(requestingUser, userIri, projectIri)) + + def addProjectToUserIsInProjectAdminGroup( + requestingUser: User, + userIri: UserIri, + projectIri: ProjectIri + ): ZIO[UsersRestService, Throwable, UserResponseADM] = + ZIO.serviceWithZIO[UsersRestService](_.addProjectToUserIsInProjectAdminGroup(requestingUser, userIri, projectIri)) + "The UsersRestService" when { "calling getAllUsers" should { "with a SystemAdmin should return all real users" in { @@ -216,7 +238,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { "asked to update a user" should { "UPDATE the user's basic information" in { /* User information is updated by the user */ - val response1: UserOperationResponseADM = UnsafeZioRun.runOrThrow( + val response1 = UnsafeZioRun.runOrThrow( UsersResponder.changeBasicUserInformationADM( SharedTestDataADM.normalUser.userIri, BasicUserInformationChangeRequest(givenName = Some(GivenName.unsafeFrom("Donald"))), @@ -227,7 +249,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { response1.user.givenName should equal("Donald") /* User information is updated by a system admin */ - val response2: UserOperationResponseADM = UnsafeZioRun.runOrThrow( + val response2 = UnsafeZioRun.runOrThrow( UsersResponder.changeBasicUserInformationADM( SharedTestDataADM.normalUser.userIri, BasicUserInformationChangeRequest(familyName = Some(FamilyName.unsafeFrom("Duck"))), @@ -238,7 +260,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { response2.user.familyName should equal("Duck") /* User information is updated by a system admin */ - val response3: UserOperationResponseADM = UnsafeZioRun.runOrThrow( + val response3 = UnsafeZioRun.runOrThrow( UsersResponder.changeBasicUserInformationADM( SharedTestDataADM.normalUser.userIri, BasicUserInformationChangeRequest( @@ -375,9 +397,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { membershipsBeforeUpdate.projects should equal(Chunk.empty) // add user to images project (00FF) - UnsafeZioRun.runOrThrow( - UsersResponder.addProjectToUserIsInProject(normalUser.userIri, imagesProject.projectIri, UUID.randomUUID()) - ) + UnsafeZioRun.runOrThrow(addProjectToUserIsInProject(rootUser, normalUser.userIri, imagesProject.projectIri)) val membershipsAfterUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) membershipsAfterUpdate.projects.map(_.id) should equal(Chunk(imagesProject.id)) @@ -397,13 +417,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { membershipsBeforeUpdate.projects.map(_.id).sorted should equal(Seq(imagesProject.id).sorted) // add user to images project (00FF) - UnsafeZioRun.runOrThrow( - UsersResponder.addProjectToUserIsInProject( - normalUser.userIri, - incunabulaProject.projectIri, - UUID.randomUUID() - ) - ) + UnsafeZioRun.runOrThrow(addProjectToUserIsInProject(rootUser, normalUser.userIri, incunabulaProject.projectIri)) val membershipsAfterUpdate = UnsafeZioRun.runOrThrow(getProjectMemberShipsByUserIri(normalUser.userIri)) membershipsAfterUpdate.projects.map(_.id).sorted should equal( @@ -428,11 +442,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { // add user as project admin to images project UnsafeZioRun.runOrThrow( - UsersResponder.addProjectToUserIsInProjectAdminGroup( - normalUser.userIri, - imagesProject.projectIri, - UUID.randomUUID() - ) + addProjectToUserIsInProjectAdminGroup(rootUser, normalUser.userIri, imagesProject.projectIri) ) // verify that the user has been added as project admin to the images project @@ -475,11 +485,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { // try to add user as project admin to images project (expected to fail because he is not a member of the project) val exit = UnsafeZioRun.run( - UsersResponder.addProjectToUserIsInProjectAdminGroup( - normalUser.userIri, - imagesProject.projectIri, - UUID.randomUUID() - ) + addProjectToUserIsInProjectAdminGroup(rootUser, normalUser.userIri, imagesProject.projectIri) ) assertFailsWithA[BadRequestException]( exit, @@ -494,17 +500,11 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { membershipsBeforeUpdate.projects should equal(Seq()) // add user as project member to images project - UnsafeZioRun.runOrThrow( - UsersResponder.addProjectToUserIsInProject(normalUser.userIri, imagesProject.projectIri, UUID.randomUUID()) - ) + UnsafeZioRun.runOrThrow(addProjectToUserIsInProject(rootUser, normalUser.userIri, imagesProject.projectIri)) // add user as project admin to images project UnsafeZioRun.runOrThrow( - UsersResponder.addProjectToUserIsInProjectAdminGroup( - normalUser.userIri, - imagesProject.projectIri, - UUID.randomUUID() - ) + addProjectToUserIsInProjectAdminGroup(rootUser, normalUser.userIri, imagesProject.projectIri) ) // get the updated project admin memberships (should contain images project) @@ -553,9 +553,7 @@ class UsersResponderSpec extends CoreSpec with ImplicitSender { val membershipsBeforeUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsBeforeUpdate should equal(Seq()) - UnsafeZioRun.runOrThrow( - UsersResponder.addGroupToUserIsInGroup(normalUser.userIri, imagesReviewerGroup.groupIri, UUID.randomUUID()) - ) + UnsafeZioRun.runOrThrow(addGroupToUserIsInGroup(rootUser, normalUser.userIri, imagesReviewerGroup.groupIri)) val membershipsAfterUpdate = findGroupMembershipsByIri(normalUser.userIri) membershipsAfterUpdate.map(_.id) should equal(Seq(imagesReviewerGroup.id)) diff --git a/webapi/src/main/scala/dsp/errors/Errors.scala b/webapi/src/main/scala/dsp/errors/Errors.scala index 6856a042a8..bf072fa912 100644 --- a/webapi/src/main/scala/dsp/errors/Errors.scala +++ b/webapi/src/main/scala/dsp/errors/Errors.scala @@ -11,6 +11,8 @@ import org.apache.commons.lang3.SerializationUtils import zio.json.DeriveJsonCodec import zio.json.JsonCodec +import org.knora.webapi.slice.admin.domain.service.UserService.Errors.UserServiceError + /* How to use and extend these exceptions @@ -86,8 +88,9 @@ object RequestRejectedException { * * @param message a description of the error. */ -case class BadRequestException(message: String) extends RequestRejectedException(message) +final case class BadRequestException(message: String) extends RequestRejectedException(message) object BadRequestException { + def apply(e: UserServiceError): BadRequestException = BadRequestException(e.message) def invalidQueryParamValue(key: String): BadRequestException = BadRequestException(s"Invalid value for query parameter '$key'") def missingQueryParamValue(key: String): BadRequestException = 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 5afd2066f8..4f943faa3a 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 @@ -10,9 +10,6 @@ import spray.json.* import java.util.UUID -import dsp.errors.BadRequestException -import dsp.valueobjects.LanguageCode -import org.knora.webapi.* import org.knora.webapi.core.RelayedMessage import org.knora.webapi.messages.ResponderRequest.KnoraRequestADM import org.knora.webapi.messages.admin.responder.AdminKnoraResponseADM @@ -102,15 +99,6 @@ case class UserGroupMembershipsGetResponseADM(groups: Seq[GroupADM]) extends Adm def toJsValue: JsValue = UsersADMJsonProtocol.userGroupMembershipsGetResponseADMFormat.write(this) } -/** - * Represents an answer to a user creating/modifying operation. - * - * @param user the new user profile of the created/modified user. - */ -case class UserOperationResponseADM(user: User) extends AdminKnoraResponseADM { - def toJsValue: JsValue = UsersADMJsonProtocol.userOperationResponseADMFormat.write(this) -} - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Components of messages @@ -135,85 +123,6 @@ object UserInformationType { } -/** - * Payload used for updating an existing user. - * - * @param username the new username. - * @param email the new email address. Needs to be unique on the server. - * @param givenName the new given name. - * @param familyName the new family name. - * @param status the new status. - * @param lang the new language. - * @param projects the new project memberships list. - * @param projectsAdmin the new projects admin membership list. - * @param groups the new group memberships list. - * @param systemAdmin the new system admin membership - */ -case class UserChangeRequestADM( - username: Option[Username] = None, - email: Option[Email] = None, - givenName: Option[GivenName] = None, - familyName: Option[FamilyName] = None, - status: Option[UserStatus] = None, - lang: Option[LanguageCode] = None, - projects: Option[Seq[IRI]] = None, - projectsAdmin: Option[Seq[IRI]] = None, - groups: Option[Seq[IRI]] = None, - systemAdmin: Option[SystemAdmin] = None -) { - - val parametersCount: Int = List( - username, - email, - givenName, - familyName, - status, - lang, - projects, - projectsAdmin, - groups, - systemAdmin - ).flatten.size - - // something needs to be sent, i.e. everything 'None' is not allowed - if (parametersCount == 0) { - throw BadRequestException("No data sent in API request.") - } - - // change status case - if (status.isDefined && parametersCount > 1) { - throw BadRequestException("Too many parameters sent for user status change.") - } - - // change system admin membership case - if (systemAdmin.isDefined && parametersCount > 1) { - throw BadRequestException("Too many parameters sent for system admin membership change.") - } - - // change project memberships (could also involve changing projectAdmin memberships) - if ( - projects.isDefined && projectsAdmin.isDefined && parametersCount > 2 || - projects.isDefined && projectsAdmin.isEmpty && parametersCount > 1 - ) { - throw BadRequestException("Too many parameters sent for project membership change.") - } - - // change projectAdmin memberships only (without changing project memberships) - if (projectsAdmin.isDefined && projects.isEmpty && parametersCount > 1) { - throw BadRequestException("Too many parameters sent for projectAdmin membership change.") - } - - // change group memberships - if (groups.isDefined && parametersCount > 1) { - throw BadRequestException("Too many parameters sent for group membership change.") - } - - // change basic user information case - if (parametersCount > 5) { - throw BadRequestException("Too many parameters sent for basic user information change.") - } -} - /** * Represents an answer to a group membership request. * @@ -247,7 +156,4 @@ object UsersADMJsonProtocol : RootJsonFormat[UserProjectAdminMembershipsGetResponseADM] = jsonFormat1(UserProjectAdminMembershipsGetResponseADM) implicit val userGroupMembershipsGetResponseADMFormat: RootJsonFormat[UserGroupMembershipsGetResponseADM] = jsonFormat1(UserGroupMembershipsGetResponseADM) - implicit val userOperationResponseADMFormat: RootJsonFormat[UserOperationResponseADM] = jsonFormat1( - UserOperationResponseADM - ) } 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 7fe6e8f706..ab7a2aed00 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 @@ -576,10 +576,10 @@ final case class GroupsResponderADMLive( requestingUser = KnoraSystemInstances.Users.SystemUser ) - seqOfFutures: Seq[Task[UserOperationResponseADM]] = + seqOfFutures: Seq[Task[UserResponseADM]] = members.map { (user: User) => messageRelay - .ask[UserOperationResponseADM]( + .ask[UserResponseADM]( UserGroupMembershipRemoveRequestADM(user.userIri, changedGroup.groupIri, apiRequestID) ) } 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 9c72b58f09..67c50edba2 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 @@ -21,7 +21,7 @@ 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.usersmessages.UserOperationResponseADM +import org.knora.webapi.messages.admin.responder.usersmessages.UserResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.* import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService @@ -91,7 +91,7 @@ final case class UsersResponder( * @param changeRequest the updated information stored as [[BasicUserInformationChangeRequest]]. * * @param apiRequestID the unique api request ID. - * @return a future containing a [[UserOperationResponseADM]]. + * @return a future containing a [[UserResponseADM]]. * with a [[BadRequestException]] if the necessary parameters are not supplied. * with a [[ForbiddenException]] if the user doesn't hold the necessary permission for the operation. */ @@ -99,7 +99,7 @@ final case class UsersResponder( userIri: UserIri, changeRequest: BasicUserInformationChangeRequest, apiRequestID: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = for { _ <- userRepo.findById(userIri).someOrFail(NotFoundException(s"User with IRI $userIri not found")) @@ -135,7 +135,7 @@ final case class UsersResponder( * @param changeRequest the current password of the requesting user and the new password. * @param requestingUser the requesting user. * @param apiRequestID the unique api request ID. - * @return a future containing a [[UserOperationResponseADM]]. + * @return a future containing a [[UserResponseADM]]. * fails with a [[BadRequestException]] if necessary parameters are not supplied. * fails with a [[ForbiddenException]] if the user doesn't hold the necessary permission for the operation. * fails with a [[ForbiddenException]] if the supplied old password doesn't match with the user's current password. @@ -146,7 +146,7 @@ final case class UsersResponder( changeRequest: PasswordChangeRequest, requestingUser: User, apiRequestID: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = for { _ <- // check if supplied password matches requesting user's password @@ -175,11 +175,11 @@ final case class UsersResponder( * @param userIri the IRI of the existing user that we want to update. * @param status the new status. * @param apiRequestID the unique api request ID. - * @return a task containing a [[UserOperationResponseADM]]. + * @return a task containing a [[UserResponseADM]]. * fails with a [[BadRequestException]] if necessary parameters are not supplied. * fails with a [[ForbiddenException]] if the requestingUser doesn't hold the necessary permission for the operation. */ - def changeUserStatus(userIri: UserIri, status: UserStatus, apiRequestID: UUID): Task[UserOperationResponseADM] = { + def changeUserStatus(userIri: UserIri, status: UserStatus, apiRequestID: UUID): Task[UserResponseADM] = { val updateTask = updateUserADM(userIri, UserChangeRequest(status = Some(status))) IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) } @@ -191,7 +191,7 @@ final case class UsersResponder( * @param systemAdmin the new status. * * @param apiRequestId the unique api request ID. - * @return a future containing a [[UserOperationResponseADM]]. + * @return a future containing a [[UserResponseADM]]. * fails with a [[BadRequestException]] if necessary parameters are not supplied. * fails with a [[ForbiddenException]] if the user doesn't hold the necessary permission for the operation. */ @@ -199,38 +199,11 @@ final case class UsersResponder( userIri: UserIri, systemAdmin: SystemAdmin, apiRequestId: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = updateUserADM(userIri, UserChangeRequest(systemAdmin = Some(systemAdmin))) IriLocker.runWithIriLock(apiRequestId, userIri.value, updateTask) } - /** - * Adds a user to a project. - * - * @param userIri the user's IRI. - * @param projectIri the project's IRI. - * @param apiRequestID the unique api request ID. - * @return - */ - def addProjectToUserIsInProject( - userIri: UserIri, - projectIri: ProjectIri, - apiRequestID: UUID - ): Task[UserOperationResponseADM] = { - val updateTask = - for { - kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) - currentIsInProject = kUser.isInProject - _ <- ZIO.when(currentIsInProject.contains(projectIri))( - ZIO.fail(BadRequestException(s"User ${userIri.value} is already member of project ${projectIri.value}.")) - ) - newIsInProject = currentIsInProject :+ projectIri - theChange = UserChangeRequest(projects = Some(newIsInProject)) - updateUserResult <- updateUserADM(userIri, theChange) - } yield updateUserResult - IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) - } - /** * Removes a project from the user's projects. * If the project is not in the user's projects, a BadRequestException is returned. @@ -245,7 +218,7 @@ final case class UsersResponder( userIri: UserIri, projectIri: ProjectIri, apiRequestID: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = for { kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) @@ -262,55 +235,19 @@ final case class UsersResponder( IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) } - /** - * Adds a user to the project admin group of a project. - * - * @param userIri the user's IRI. - * @param projectIri the project's IRI. - * @param apiRequestID the unique api request ID. - * @return a [[UserOperationResponseADM]]. - */ - def addProjectToUserIsInProjectAdminGroup( - userIri: UserIri, - projectIri: ProjectIri, - apiRequestID: UUID - ): Task[UserOperationResponseADM] = { - val updateTask = - for { - kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) - currentIsInProject = kUser.isInProject - _ <- - ZIO.when(!currentIsInProject.contains(projectIri))( - ZIO.fail( - BadRequestException( - s"User ${userIri.value} is not a member of project ${projectIri.value}. A user needs to be a member of the project to be added as project admin." - ) - ) - ) - currentIsInProjectAdminGroup = kUser.isInProjectAdminGroup - _ <- ZIO.when(currentIsInProjectAdminGroup.contains(projectIri))( - ZIO.fail(BadRequestException(s"User $userIri is already a project admin for project $projectIri.")) - ) - newIsInProjectAdminGroup = currentIsInProjectAdminGroup :+ projectIri - theChange = UserChangeRequest(projectsAdmin = Some(newIsInProjectAdminGroup)) - updateUserResult <- updateUserADM(userIri, theChange) - } yield updateUserResult - IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) - } - /** * Removes a user from project admin group of a project. * * @param userIri the user's IRI. * @param projectIri the project's IRI. * @param apiRequestID the unique api request ID. - * @return a [[UserOperationResponseADM]] + * @return a [[UserResponseADM]] */ def removeProjectFromUserIsInProjectAdminGroup( userIri: UserIri, projectIri: ProjectIri, apiRequestID: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = for { kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) @@ -325,45 +262,19 @@ final case class UsersResponder( IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) } - /** - * Adds a user to a group. - * - * @param userIri the user's IRI. - * @param groupIri the group IRI. - * @param apiRequestID the unique api request ID. - * @return a [[UserOperationResponseADM]]. - */ - def addGroupToUserIsInGroup( - userIri: UserIri, - groupIri: GroupIri, - apiRequestID: UUID - ): Task[UserOperationResponseADM] = { - val updateTask = - for { - kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) - currentIsInGroup = kUser.isInGroup - _ <- ZIO.when(currentIsInGroup.contains(groupIri))( - ZIO.fail(BadRequestException(s"User $userIri is already member of group $groupIri.")) - ) - theChange = UserChangeRequest(groups = Some(currentIsInGroup :+ groupIri)) - result <- updateUserADM(userIri, theChange) - } yield result - IriLocker.runWithIriLock(apiRequestID, userIri.value, updateTask) - } - /** * Removes a user from a group. * * @param userIri the user's IRI. * @param groupIri the group IRI. * @param apiRequestID the unique api request ID. - * @return a [[UserOperationResponseADM]]. + * @return a [[UserResponseADM]]. */ def removeGroupFromUserIsInGroup( userIri: UserIri, groupIri: GroupIri, apiRequestID: UUID - ): Task[UserOperationResponseADM] = { + ): Task[UserResponseADM] = { val updateTask = for { kUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"The user $userIri does not exist.")) @@ -386,14 +297,14 @@ final case class UsersResponder( * * @param userIri the IRI of the existing user that we want to update. * @param theUpdate the updated information. - * @return a [[UserOperationResponseADM]]. + * @return a [[UserResponseADM]]. * 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, theUpdate: UserChangeRequest - ): ZIO[Any, Throwable, UserOperationResponseADM] = + ): ZIO[Any, Throwable, UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) currentUser <- userRepo.findById(userIri).someOrFail(NotFoundException(s"User '$userIri' not found.")) @@ -402,7 +313,7 @@ final case class UsersResponder( userService .findUserByIri(userIri) .someOrFail(UpdateNotPerformedException("User was not updated. Please report this as a possible bug.")) - } yield UserOperationResponseADM(updatedUserADM.ofType(UserInformationType.Restricted)) + } yield UserResponseADM(updatedUserADM.ofType(UserInformationType.Restricted)) /** * Creates a new user. Self-registration is allowed, so even the default user, i.e. with no credentials supplied, @@ -414,9 +325,9 @@ final case class UsersResponder( * * @param req a [[UserCreateRequest]] object containing information about the new user to be created. * @param apiRequestID the unique api request ID. - * @return a [[UserOperationResponseADM]]. + * @return a [[UserResponseADM]]. */ - def createNewUserADM(req: UserCreateRequest, apiRequestID: UUID): Task[UserOperationResponseADM] = { + def createNewUserADM(req: UserCreateRequest, apiRequestID: UUID): Task[UserResponseADM] = { val createNewUserTask = for { _ <- ensureUsernameDoesNotExist(req.username) @@ -453,7 +364,7 @@ final case class UsersResponder( UpdateNotPerformedException(s"User ${userIri.value} was not created. Please report this as a possible bug.") } - } yield UserOperationResponseADM(createdUser.ofType(UserInformationType.Restricted)) + } yield UserResponseADM(createdUser.ofType(UserInformationType.Restricted)) IriLocker.runWithIriLock(apiRequestID, USERS_GLOBAL_LOCK_IRI, createNewUserTask) } @@ -464,14 +375,14 @@ object UsersResponder { userIri: UserIri, status: UserStatus, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.changeUserStatus(userIri, status, apiRequestID)) def changeSystemAdmin( userIri: UserIri, systemAdmin: SystemAdmin, apiRequestId: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.changeSystemAdmin(userIri, systemAdmin, apiRequestId)) def findUserByIri( @@ -481,25 +392,11 @@ object UsersResponder { ): ZIO[UsersResponder, Throwable, Option[User]] = ZIO.serviceWithZIO[UsersResponder](_.findUserByIri(identifier, userInformationType, requestingUser)) - def addProjectToUserIsInProject( - userIri: UserIri, - projectIri: ProjectIri, - apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = - ZIO.serviceWithZIO[UsersResponder](_.addProjectToUserIsInProject(userIri, projectIri, apiRequestID)) - - def addProjectToUserIsInProjectAdminGroup( - userIri: UserIri, - projectIri: ProjectIri, - apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = - ZIO.serviceWithZIO[UsersResponder](_.addProjectToUserIsInProjectAdminGroup(userIri, projectIri, apiRequestID)) - def removeProjectFromUserIsInProjectAndIsInProjectAdminGroup( userIri: UserIri, projectIri: ProjectIri, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder]( _.removeProjectFromUserIsInProjectAndIsInProjectAdminGroup(userIri, projectIri, apiRequestID) ) @@ -508,7 +405,7 @@ object UsersResponder { userIri: UserIri, projectIri: ProjectIri, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder]( _.removeProjectFromUserIsInProjectAdminGroup(userIri, projectIri, apiRequestID) ) @@ -516,14 +413,14 @@ object UsersResponder { def createNewUserADM( req: UserCreateRequest, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.createNewUserADM(req, apiRequestID)) def changeBasicUserInformationADM( userIri: UserIri, changeRequest: BasicUserInformationChangeRequest, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.changeBasicUserInformationADM(userIri, changeRequest, apiRequestID)) def changePassword( @@ -531,21 +428,14 @@ object UsersResponder { changeRequest: PasswordChangeRequest, requestingUser: User, apiRequestID: UUID - ): RIO[UsersResponder, UserOperationResponseADM] = + ): RIO[UsersResponder, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.changePassword(userIri, changeRequest, requestingUser, apiRequestID)) - def addGroupToUserIsInGroup( - userIri: UserIri, - groupIri: GroupIri, - apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = - ZIO.serviceWithZIO[UsersResponder](_.addGroupToUserIsInGroup(userIri, groupIri, apiRequestID)) - def removeGroupFromUserIsInGroup( userIri: UserIri, groupIri: GroupIri, apiRequestID: UUID - ): ZIO[UsersResponder, Throwable, UserOperationResponseADM] = + ): ZIO[UsersResponder, Throwable, UserResponseADM] = ZIO.serviceWithZIO[UsersResponder](_.removeGroupFromUserIsInGroup(userIri, groupIri, apiRequestID)) val layer: URLayer[ diff --git a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala index 3af0f62c42..02360b492a 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/RouteUtilADM.scala @@ -84,7 +84,6 @@ object RouteUtilADM { UserProjectAdminMembershipsGetResponseADM(projects.map(projectAsExternalRepresentation)) case UserGroupMembershipsGetResponseADM(groups) => UserGroupMembershipsGetResponseADM(groups.map(groupAsExternalRepresentation)) - case UserOperationResponseADM(user) => UserOperationResponseADM(userAsExternalRepresentation(user)) case _ => response } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpoints.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpoints.scala index 235e6ae8a1..ca02f4d9d4 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpoints.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/UsersEndpoints.scala @@ -15,7 +15,6 @@ import zio.json.JsonCodec import dsp.valueobjects.LanguageCode import org.knora.webapi.messages.admin.responder.usersmessages.UserGroupMembershipsGetResponseADM -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 import org.knora.webapi.messages.admin.responder.usersmessages.UserResponseADM @@ -105,22 +104,22 @@ final case class UsersEndpoints(baseEndpoints: BaseEndpoints) { val users = baseEndpoints.securedEndpoint.post .in(base) .in(zioJsonBody[UserCreateRequest]) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Create a new user.") val usersByIriProjectMemberShips = baseEndpoints.securedEndpoint.post .in(base / "iri" / PathVars.userIriPathVar / "project-memberships" / AdminPathVariables.projectIri) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Add a user to a project identified by IRI.") val usersByIriProjectAdminMemberShips = baseEndpoints.securedEndpoint.post .in(base / "iri" / PathVars.userIriPathVar / "project-admin-memberships" / AdminPathVariables.projectIri) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Add a user as an admin to a project identified by IRI.") val usersByIriGroupMemberShips = baseEndpoints.securedEndpoint.post .in(base / "iri" / PathVars.userIriPathVar / "group-memberships" / AdminPathVariables.groupIriPathVar) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Add a user to a group identified by IRI.") } @@ -128,25 +127,25 @@ final case class UsersEndpoints(baseEndpoints: BaseEndpoints) { val usersIriBasicInformation = baseEndpoints.securedEndpoint.put .in(base / "iri" / PathVars.userIriPathVar / "BasicUserInformation") .in(zioJsonBody[BasicUserInformationChangeRequest]) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Update a user's basic information identified by IRI.") val usersIriPassword = baseEndpoints.securedEndpoint.put .in(base / "iri" / PathVars.userIriPathVar / "Password") .in(zioJsonBody[PasswordChangeRequest]) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Change a user's password identified by IRI.") val usersIriStatus = baseEndpoints.securedEndpoint.put .in(base / "iri" / PathVars.userIriPathVar / "Status") .in(zioJsonBody[StatusChangeRequest]) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Change a user's status identified by IRI.") val usersIriSystemAdmin = baseEndpoints.securedEndpoint.put .in(base / "iri" / PathVars.userIriPathVar / "SystemAdmin") .in(zioJsonBody[SystemAdminChangeRequest]) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Change a user's SystemAdmin status identified by IRI.") } @@ -154,22 +153,22 @@ final case class UsersEndpoints(baseEndpoints: BaseEndpoints) { object delete { val deleteUser = baseEndpoints.securedEndpoint.delete .in(base / "iri" / PathVars.userIriPathVar) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Delete a user identified by IRI (change status to false).") val usersByIriProjectMemberShips = baseEndpoints.securedEndpoint.delete .in(base / "iri" / PathVars.userIriPathVar / "project-memberships" / AdminPathVariables.projectIri) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Remove a user from a project membership identified by IRI.") val usersByIriProjectAdminMemberShips = baseEndpoints.securedEndpoint.delete .in(base / "iri" / PathVars.userIriPathVar / "project-admin-memberships" / AdminPathVariables.projectIri) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Remove a user form an admin project membership identified by IRI.") val usersByIriGroupMemberShips = baseEndpoints.securedEndpoint.delete .in(base / "iri" / PathVars.userIriPathVar / "group-memberships" / AdminPathVariables.groupIriPathVar) - .out(sprayJsonBody[UserOperationResponseADM]) + .out(sprayJsonBody[UserResponseADM]) .description("Remove a user form an group membership identified by IRI.") } 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 adb351d0b5..1dc92230df 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 @@ -8,7 +8,6 @@ package org.knora.webapi.slice.admin.api import zio.ZLayer import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier -import org.knora.webapi.messages.admin.responder.usersmessages.UserOperationResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.UserResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.UsersGetResponseADM import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.BasicUserInformationChangeRequest @@ -67,29 +66,29 @@ case class UsersEndpointsHandler( ) // Create - private val createUserHandler = SecuredEndpointHandler[UserCreateRequest, UserOperationResponseADM]( + private val createUserHandler = SecuredEndpointHandler[UserCreateRequest, UserResponseADM]( usersEndpoints.post.users, requestingUser => userCreateRequest => restService.createUser(requestingUser, userCreateRequest) ) private val postUsersByIriProjectMemberShipsHandler = - SecuredEndpointHandler[(UserIri, IriIdentifier), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, IriIdentifier), UserResponseADM]( usersEndpoints.post.usersByIriProjectMemberShips, requestingUser => { case (userIri: UserIri, projectIri: IriIdentifier) => - restService.addProjectToUserIsInProject(requestingUser, userIri, projectIri) + restService.addProjectToUserIsInProject(requestingUser, userIri, projectIri.value) } ) private val postUsersByIriProjectAdminMemberShipsHandler = - SecuredEndpointHandler[(UserIri, IriIdentifier), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, IriIdentifier), UserResponseADM]( usersEndpoints.post.usersByIriProjectAdminMemberShips, requestingUser => { case (userIri: UserIri, projectIri: IriIdentifier) => - restService.addProjectToUserIsInProjectAdminGroup(requestingUser, userIri, projectIri) + restService.addProjectToUserIsInProjectAdminGroup(requestingUser, userIri, projectIri.value) } ) private val postUsersByIriGroupMemberShipsHandler = - SecuredEndpointHandler[(UserIri, GroupIri), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, GroupIri), UserResponseADM]( usersEndpoints.post.usersByIriGroupMemberShips, requestingUser => { case (userIri: UserIri, groupIri: GroupIri) => restService.addGroupToUserIsInGroup(requestingUser, userIri, groupIri) @@ -98,7 +97,7 @@ case class UsersEndpointsHandler( // Update private val putUsersIriBasicInformationHandler = - SecuredEndpointHandler[(UserIri, BasicUserInformationChangeRequest), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, BasicUserInformationChangeRequest), UserResponseADM]( usersEndpoints.put.usersIriBasicInformation, requestingUser => { case (userIri: UserIri, changeRequest: BasicUserInformationChangeRequest) => restService.updateUser(requestingUser, userIri, changeRequest) @@ -106,7 +105,7 @@ case class UsersEndpointsHandler( ) private val putUsersIriPasswordHandler = - SecuredEndpointHandler[(UserIri, PasswordChangeRequest), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, PasswordChangeRequest), UserResponseADM]( usersEndpoints.put.usersIriPassword, requestingUser => { case (userIri: UserIri, changeRequest: PasswordChangeRequest) => restService.changePassword(requestingUser, userIri, changeRequest) @@ -114,7 +113,7 @@ case class UsersEndpointsHandler( ) private val putUsersIriStatusHandler = - SecuredEndpointHandler[(UserIri, StatusChangeRequest), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, StatusChangeRequest), UserResponseADM]( usersEndpoints.put.usersIriStatus, requestingUser => { case (userIri: UserIri, changeRequest: StatusChangeRequest) => restService.changeStatus(requestingUser, userIri, changeRequest) @@ -122,7 +121,7 @@ case class UsersEndpointsHandler( ) private val putUsersIriSystemAdminHandler = - SecuredEndpointHandler[(UserIri, SystemAdminChangeRequest), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, SystemAdminChangeRequest), UserResponseADM]( usersEndpoints.put.usersIriSystemAdmin, requestingUser => { case (userIri: UserIri, changeRequest: SystemAdminChangeRequest) => restService.changeSystemAdmin(requestingUser, userIri, changeRequest) @@ -130,13 +129,13 @@ case class UsersEndpointsHandler( ) // Deletes - private val deleteUserByIriHandler = SecuredEndpointHandler[UserIri, UserOperationResponseADM]( + private val deleteUserByIriHandler = SecuredEndpointHandler[UserIri, UserResponseADM]( usersEndpoints.delete.deleteUser, requestingUser => userIri => restService.deleteUser(requestingUser, userIri) ) private val deleteUsersByIriProjectMemberShipsHandler = - SecuredEndpointHandler[(UserIri, IriIdentifier), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, IriIdentifier), UserResponseADM]( usersEndpoints.delete.usersByIriProjectMemberShips, requestingUser => { case (userIri: UserIri, projectIri: IriIdentifier) => restService.removeProjectToUserIsInProject(requestingUser, userIri, projectIri) @@ -144,7 +143,7 @@ case class UsersEndpointsHandler( ) private val deleteUsersByIriProjectAdminMemberShipsHandler = - SecuredEndpointHandler[(UserIri, IriIdentifier), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, IriIdentifier), UserResponseADM]( usersEndpoints.delete.usersByIriProjectAdminMemberShips, requestingUser => { case (userIri: UserIri, projectIri: IriIdentifier) => restService.removeProjectFromUserIsInProjectAdminGroup(requestingUser, userIri, projectIri) @@ -152,7 +151,7 @@ case class UsersEndpointsHandler( ) private val deleteUsersByIriGroupMemberShipsHandler = - SecuredEndpointHandler[(UserIri, GroupIri), UserOperationResponseADM]( + SecuredEndpointHandler[(UserIri, GroupIri), UserResponseADM]( usersEndpoints.delete.usersByIriGroupMemberShips, requestingUser => { case (userIri: UserIri, groupIri: GroupIri) => restService.removeGroupFromUserIsInGroup(requestingUser, userIri, groupIri) 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 a83add2564..402c5e86e3 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 @@ -12,11 +12,11 @@ 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.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 import org.knora.webapi.messages.admin.responder.usersmessages.UserResponseADM import org.knora.webapi.messages.admin.responder.usersmessages.UsersGetResponseADM +import org.knora.webapi.responders.admin.GroupsResponderADM import org.knora.webapi.responders.admin.UsersResponder import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.BasicUserInformationChangeRequest @@ -25,6 +25,8 @@ import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.StatusChangeRequ import org.knora.webapi.slice.admin.api.UsersEndpoints.Requests.SystemAdminChangeRequest import org.knora.webapi.slice.admin.domain.model.Email import org.knora.webapi.slice.admin.domain.model.GroupIri +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri +import org.knora.webapi.slice.admin.domain.model.KnoraUser 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 @@ -37,6 +39,7 @@ import org.knora.webapi.slice.common.api.KnoraResponseRenderer final case class UsersRestService( auth: AuthorizationRestService, + groupsResponder: GroupsResponderADM, userService: UserService, userRepo: KnoraUserRepo, projectService: ProjectADMService, @@ -53,7 +56,7 @@ final case class UsersRestService( external <- format.toExternal(internal) } yield external - def deleteUser(requestingUser: User, deleteIri: UserIri): Task[UserOperationResponseADM] = for { + def deleteUser(requestingUser: User, deleteIri: UserIri): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(deleteIri) _ <- ensureSelfUpdateOrSystemAdmin(deleteIri, requestingUser) uuid <- Random.nextUUID @@ -76,7 +79,7 @@ final case class UsersRestService( .map(UserGroupMembershipsGetResponseADM) .flatMap(format.toExternal) - def createUser(requestingUser: User, userCreateRequest: Requests.UserCreateRequest): Task[UserOperationResponseADM] = + def createUser(requestingUser: User, userCreateRequest: Requests.UserCreateRequest): Task[UserResponseADM] = for { _ <- auth.ensureSystemAdmin(requestingUser) uuid <- Random.nextUUID @@ -105,8 +108,7 @@ final case class UsersRestService( 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) + external <- asExternalUserOperationResponse(requestingUser, user) } yield external def getUserByIri(requestingUser: User, userIri: UserIri): Task[UserResponseADM] = for { @@ -126,7 +128,7 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, changeRequest: BasicUserInformationChangeRequest - ): Task[UserOperationResponseADM] = for { + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- ensureSelfUpdateOrSystemAdmin(userIri, requestingUser) uuid <- Random.nextUUID @@ -138,7 +140,7 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, changeRequest: PasswordChangeRequest - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- ensureSelfUpdateOrSystemAdmin(userIri, requestingUser) @@ -150,7 +152,7 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, changeRequest: StatusChangeRequest - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- ensureSelfUpdateOrSystemAdmin(userIri, requestingUser) @@ -162,7 +164,7 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, changeRequest: SystemAdminChangeRequest - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- auth.ensureSystemAdmin(requestingUser) @@ -173,32 +175,50 @@ final case class UsersRestService( def addProjectToUserIsInProject( requestingUser: User, userIri: UserIri, - projectIri: ProjectIdentifierADM.IriIdentifier - ): Task[UserOperationResponseADM] = + projectIri: ProjectIri + ): Task[UserResponseADM] = for { - _ <- ensureNotABuiltInUser(userIri) - _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri.value) - uuid <- Random.nextUUID - response <- responder.addProjectToUserIsInProject(userIri, projectIri.value, uuid) - } yield response + _ <- ensureNotABuiltInUser(userIri) + _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri) + kUser <- getKnoraUserOrNotFound(userIri) + project <- getProjectADMOrBadRequest(projectIri) + updatedUser <- userService.addProjectToUserIsInProject(kUser, project).mapError(BadRequestException.apply) + external <- asExternalUserOperationResponse(requestingUser, updatedUser) + } yield external + + private def getProjectADMOrBadRequest(projectIri: ProjectIri) = + projectService + .findById(projectIri) + .someOrFail(BadRequestException(s"Project with iri ${projectIri.value} not found.")) + + private def asExternalUserOperationResponse(requestingUser: User, kUser: KnoraUser): Task[UserResponseADM] = + userService.toUser(kUser).flatMap(asExternalUserOperationResponse(requestingUser, _)) + + private def asExternalUserOperationResponse(requestingUser: User, user: User): Task[UserResponseADM] = { + val userFiltered = user.filterUserInformation(requestingUser, UserInformationType.Restricted) + format.toExternal(UserResponseADM(userFiltered)) + } def addProjectToUserIsInProjectAdminGroup( requestingUser: User, userIri: UserIri, - projectIri: ProjectIdentifierADM.IriIdentifier - ): Task[UserOperationResponseADM] = + projectIri: ProjectIri + ): Task[UserResponseADM] = for { - _ <- ensureNotABuiltInUser(userIri) - _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri.value) - uuid <- Random.nextUUID - response <- responder.addProjectToUserIsInProjectAdminGroup(userIri, projectIri.value, uuid) - } yield response + _ <- ensureNotABuiltInUser(userIri) + _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri) + user <- getKnoraUserOrNotFound(userIri) + project <- getProjectADMOrBadRequest(projectIri) + updatedUser <- + userService.addProjectToUserIsInProjectAdminGroup(user, project).mapError(BadRequestException.apply) + external <- asExternalUserOperationResponse(requestingUser, updatedUser) + } yield external def removeProjectToUserIsInProject( requestingUser: User, userIri: UserIri, projectIri: ProjectIdentifierADM.IriIdentifier - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri.value) @@ -210,7 +230,7 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, projectIri: ProjectIdentifierADM.IriIdentifier - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- auth.ensureSystemAdminOrProjectAdmin(requestingUser, projectIri.value) @@ -222,19 +242,23 @@ final case class UsersRestService( requestingUser: User, userIri: UserIri, groupIri: GroupIri - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { - _ <- ensureNotABuiltInUser(userIri) - _ <- auth.ensureSystemAdminOrProjectAdminOfGroup(requestingUser, groupIri) - uuid <- Random.nextUUID - response <- responder.addGroupToUserIsInGroup(userIri, groupIri, uuid) - } yield response + _ <- ensureNotABuiltInUser(userIri) + _ <- auth.ensureSystemAdminOrProjectAdminOfGroup(requestingUser, groupIri) + kUser <- getKnoraUserOrNotFound(userIri) + group <- groupsResponder + .groupGetADM(groupIri.value) + .someOrFail(BadRequestException(s"Group with iri ${groupIri.value} not found.")) + updatedKUser <- userService.addGroupToUserIsInGroup(kUser, group).mapError(BadRequestException.apply) + external <- asExternalUserOperationResponse(requestingUser, updatedKUser) + } yield external def removeGroupFromUserIsInGroup( requestingUser: User, userIri: UserIri, groupIri: GroupIri - ): Task[UserOperationResponseADM] = + ): Task[UserResponseADM] = for { _ <- ensureNotABuiltInUser(userIri) _ <- auth.ensureSystemAdminOrProjectAdminOfGroup(requestingUser, groupIri) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala index c91d6bf7f2..c8fe5d9f51 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/UserService.scala @@ -6,11 +6,14 @@ package org.knora.webapi.slice.admin.domain.service import zio.Chunk +import zio.IO import zio.Task import zio.ZIO import zio.ZLayer import dsp.valueobjects.LanguageCode +import org.knora.webapi.messages.admin.responder.groupsmessages.GroupADM +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.responders.admin.GroupsResponderADM import org.knora.webapi.responders.admin.PermissionsResponderADM import org.knora.webapi.slice.admin.domain.model.Email @@ -25,6 +28,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.UserStatus import org.knora.webapi.slice.admin.domain.model.Username +import org.knora.webapi.slice.admin.domain.service.UserService.Errors.UserServiceError import org.knora.webapi.store.cache.api.CacheService final case class UserChangeRequest( @@ -84,15 +88,49 @@ case class UserService( status = update.status.getOrElse(kUser.status), password = update.passwordHash.getOrElse(kUser.password), preferredLanguage = update.lang.getOrElse(kUser.preferredLanguage), - isInProject = update.projects.getOrElse(kUser.isInProject), - isInProjectAdminGroup = update.projectsAdmin.getOrElse(kUser.isInProjectAdminGroup), - isInGroup = update.groups.getOrElse(kUser.isInGroup), + isInProject = update.projects.getOrElse(kUser.isInProject).distinct, + isInProjectAdminGroup = update.projectsAdmin.getOrElse(kUser.isInProjectAdminGroup).distinct, + isInGroup = update.groups.getOrElse(kUser.isInGroup).distinct, isInSystemAdminGroup = update.systemAdmin.getOrElse(kUser.isInSystemAdminGroup) ) userRepo.save(updatedUser) } - private def toUser(kUser: KnoraUser): Task[User] = for { + def addGroupToUserIsInGroup(user: KnoraUser, group: GroupADM): IO[UserServiceError, KnoraUser] = for { + _ <- ZIO.when(user.isInGroup.contains(group.groupIri))( + ZIO.fail(UserServiceError(s"User ${user.id.value} is already member of group ${group.groupIri.value}.")) + ) + user <- updateUser(user, UserChangeRequest(groups = Some(user.isInGroup :+ group.groupIri))).orDie + } yield user + + def addProjectToUserIsInProject(user: KnoraUser, project: ProjectADM): IO[UserServiceError, KnoraUser] = for { + _ <- ZIO + .fail(UserServiceError(s"User ${user.id.value} is already member of project ${project.projectIri.value}.")) + .when(user.isInProject.contains(project.projectIri)) + user <- updateUser(user, UserChangeRequest(projects = Some(user.isInProject :+ project.projectIri))).orDie + } yield user + + def addProjectToUserIsInProjectAdminGroup( + user: KnoraUser, + project: ProjectADM + ): IO[UserServiceError, KnoraUser] = for { + _ <- + ZIO + .fail( + UserServiceError(s"User ${user.id.value} is already admin member of project ${project.projectIri.value}.") + ) + .when(user.isInProjectAdminGroup.contains(project.projectIri)) + _ <- + ZIO.fail { + val msg = + s"User ${user.id.value} is not a member of project ${project.projectIri.value}. A user needs to be a member of the project to be added as project admin." + UserServiceError(msg) + }.when(!user.isInProject.contains(project.projectIri)) + theChange = UserChangeRequest(projectsAdmin = Some(user.isInProjectAdminGroup :+ project.projectIri)) + user <- updateUser(user, theChange).orDie + } yield user + + def toUser(kUser: KnoraUser): Task[User] = for { projects <- ZIO.foreach(kUser.isInProject)(projectsService.findById).map(_.flatten) groups <- ZIO.foreach(kUser.isInGroup.map(_.value))(groupsService.groupGetADM).map(_.flatten) permissionData <- @@ -118,5 +156,9 @@ case class UserService( } object UserService { + object Errors { + final case class UserServiceError(message: String) + } + val layer = ZLayer.derive[UserService] }