Skip to content

Commit

Permalink
refactor: Move code from UsersResponder to UserService and UserRestSe…
Browse files Browse the repository at this point in the history
…rvice (#3067)
  • Loading branch information
seakayone committed Feb 28, 2024
1 parent 29b1ce6 commit 5345350
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 286 deletions.
Expand Up @@ -145,6 +145,7 @@ object LayersTest {
with TriplestoreService
with UsersResponder
with UsersRestService
with UserService
with ValuesResponderV2

private val commonLayersForAllIntegrationTests =
Expand Down
Expand Up @@ -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 {
Expand Down
Expand Up @@ -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)
}

Expand Down

Large diffs are not rendered by default.

Expand Up @@ -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`
Expand Down
Expand Up @@ -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

Expand Down Expand Up @@ -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

}

Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -277,7 +277,7 @@ final case class GroupsResponderADMLive(
.ask[Option[User]](
UserGetByIriADM(
UserIri.unsafeFrom(userIri),
UserInformationTypeADM.Restricted,
UserInformationType.Restricted,
KnoraSystemInstances.Users.SystemUser
)
)
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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
)
)
Expand Down Expand Up @@ -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
)
)
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 5345350

Please sign in to comment.