Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: User can be project admin without being project member (DEV-1383) #2248

Expand Up @@ -837,13 +837,15 @@ case class UserChangeRequestADM(
throw BadRequestException("Too many parameters sent for system admin membership change.")
}

// change project memberships
if (projects.isDefined && parametersCount > 1) {
// change project memberships (could also involve changing projectAdmin memberships)
if (
projects.isDefined && projectsAdmin.isDefined && parametersCount > 2 || projects.isDefined && !projectsAdmin.isDefined && parametersCount > 1
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved
) {
throw BadRequestException("Too many parameters sent for project membership change.")
}

// change projectAdmin memberships
if (projectsAdmin.isDefined && parametersCount > 1) {
// change projectAdmin memberships only (without changing project memberships)
if (projectsAdmin.isDefined && !projects.isDefined && parametersCount > 1) {
throw BadRequestException("Too many parameters sent for projectAdmin membership change.")
}

Expand Down
Expand Up @@ -694,7 +694,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the user's IRI.
* @param projectIri the project's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return
Expand Down Expand Up @@ -779,7 +778,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the user's IRI.
* @param projectIri the project's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return
Expand Down Expand Up @@ -826,7 +824,7 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
)
currentProjectMembershipIris = currentProjectMemberships.map(_.id)

// check if user is not already a member and if he is then remove the project from to list
// check if user is a member and if he is then remove the project from to list
updatedProjectMembershipIris =
if (currentProjectMembershipIris.contains(projectIri)) {
currentProjectMembershipIris diff Seq(projectIri)
Expand All @@ -836,10 +834,29 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
)
}

// get users current project admin membership list
currentProjectAdminMemberships <- userProjectAdminMembershipsGetADM(
userIri = userIri,
requestingUser = KnoraSystemInstances.Users.SystemUser,
apiRequestID = apiRequestID
)

currentProjectAdminMembershipIris: Seq[IRI] = currentProjectAdminMemberships.map(_.id)

// in case the user has an admin membership for that project, remove it as well
maybeUpdatedProjectAdminMembershipIris = if (currentProjectAdminMembershipIris.contains(projectIri)) {
Some(
currentProjectAdminMembershipIris.filterNot(p => p == projectIri)
)
} else None

// create the update request by using the SystemUser
result <- updateUserADM(
userIri = userIri,
userUpdatePayload = UserChangeRequestADM(projects = Some(updatedProjectMembershipIris)),
userUpdatePayload = UserChangeRequestADM(
projects = Some(updatedProjectMembershipIris),
projectsAdmin = maybeUpdatedProjectAdminMembershipIris
),
requestingUser = KnoraSystemInstances.Users.SystemUser,
apiRequestID = apiRequestID
)
Expand All @@ -859,7 +876,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* Returns the user's project admin group memberships as a sequence of [[IRI]]
*
* @param userIri the user's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a [[UserProjectMembershipsGetResponseV1]].
Expand Down Expand Up @@ -912,7 +928,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* is a member of the project admin group.
*
* @param userIri the user's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a [[UserProjectMembershipsGetResponseV1]].
Expand Down Expand Up @@ -942,10 +957,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the user's IRI.
* @param projectIri the project's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return
* @return a [[UserOperationResponseADM]].
*/
private def userProjectAdminMembershipAddRequestADM(
userIri: IRI,
Expand Down Expand Up @@ -983,6 +997,22 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
_ = if (!projectExists) throw NotFoundException(s"The project $projectIri does not exist.")

// get users current project membership list
currentProjectMemberships <- userProjectMembershipsGetADM(
userIri = userIri,
requestingUser = KnoraSystemInstances.Users.SystemUser
)

currentProjectMembershipIris = currentProjectMemberships.map(_.id)

// check if user is already project member and if not throw exception

_ = if (!currentProjectMembershipIris.contains(projectIri)) {
throw BadRequestException(
s"User $userIri is not a member of project $projectIri. A user needs to be a member of the project to be added as project admin."
)
}
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved

// get users current project admin membership list
currentProjectAdminMemberships <- userProjectAdminMembershipsGetADM(
userIri = userIri,
requestingUser = KnoraSystemInstances.Users.SystemUser,
Expand All @@ -991,7 +1021,7 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde

currentProjectAdminMembershipIris: Seq[IRI] = currentProjectAdminMemberships.map(_.id)

// check if user is already member and if not then append to list
// check if user is already project admin and if not then append to list
updatedProjectAdminMembershipIris =
if (!currentProjectAdminMembershipIris.contains(projectIri)) {
currentProjectAdminMembershipIris :+ projectIri
Expand Down Expand Up @@ -1026,10 +1056,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the user's IRI.
* @param projectIri the project's IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return
* @return a [[UserOperationResponseADM]]
*/
private def userProjectAdminMembershipRemoveRequestADM(
userIri: IRI,
Expand Down Expand Up @@ -1109,7 +1138,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* Returns the user's group memberships as a sequence of [[GroupADM]]
*
* @param userIri the IRI of the user.
*
* @param requestingUser the requesting user.
* @return a sequence of [[GroupADM]].
*/
Expand Down Expand Up @@ -1162,7 +1190,6 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the user's IRI.
* @param groupIri the group IRI.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a [[UserOperationResponseADM]].
Expand Down Expand Up @@ -1255,6 +1282,15 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde

}

/**
* Removes a user from a group.
*
* @param userIri the user's IRI.
* @param groupIri the group IRI.
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a [[UserOperationResponseADM]].
*/
private def userGroupMembershipRemoveRequestADM(
userIri: IRI,
groupIri: IRI,
Expand Down Expand Up @@ -1343,10 +1379,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the IRI of the existing user that we want to update.
* @param userUpdatePayload the updated information.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a future containing a [[UserOperationResponseADM]].
* @return a [[UserOperationResponseADM]].
* @throws BadRequestException if necessary parameters are not supplied.
* @throws UpdateNotPerformedException if the update was not performed.
*/
Expand Down Expand Up @@ -1558,10 +1593,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
*
* @param userIri the IRI of the existing user that we want to update.
* @param password the new password.
*
* @param requestingUser the requesting user.
* @param apiRequestID the unique api request ID.
* @return a future containing a [[UserOperationResponseADM]].
* @return a [[UserOperationResponseADM]].
* @throws BadRequestException if necessary parameters are not supplied.
* @throws UpdateNotPerformedException if the update was not performed.
*/
Expand Down Expand Up @@ -1636,9 +1670,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* - http://blog.ircmaxell.com/2012/12/seven-ways-to-screw-up-bcrypt.html
*
* @param userCreatePayloadADM a [[UserCreatePayloadADM]] object containing information about the new user to be created.
*
* @param requestingUser a [[UserADM]] object containing information about the requesting user.
* @return a future containing the [[UserOperationResponseADM]].
* @param requestingUser a [[UserADM]] object containing information about the requesting user.
* @param apiRequestID the unique api request ID.
* @return a [[UserOperationResponseADM]].
*/
private def createNewUserADM(
userCreatePayloadADM: UserCreatePayloadADM,
Expand Down Expand Up @@ -1761,10 +1795,13 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
/**
* Tries to retrieve a [[UserADM]] either from triplestore or cache if caching is enabled.
* If user is not found in cache but in triplestore, then user is written to cache.
*
* @param identifier The identifier of the user (can be IRI, e-mail or username)
* @return a [[Option[UserADM]]]
*/
private def getUserFromCacheOrTriplestore(
identifier: UserIdentifierADM
): Future[Option[UserADM]] = tracedFuture("admin-user-get-user-from-cache-or-triplestore") {
): Future[Option[UserADM]] = // tracedFuture("admin-user-get-user-from-cache-or-triplestore") {
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved
if (cacheServiceSettings.cacheServiceEnabled) {
// caching enabled
getUserFromCache(identifier).flatMap {
Expand Down Expand Up @@ -1793,10 +1830,13 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
log.debug("getUserFromCacheOrTriplestore - caching disabled. getting from triplestore.")
getUserFromTriplestore(identifier = identifier)
}
}
// }

/**
* Tries to retrieve a [[UserADM]] from the triplestore.
*
* @param identifier The identifier of the user (can be IRI, e-mail or username)
* @return a [[Option[UserADM]]]
*/
private def getUserFromTriplestore(
identifier: UserIdentifierADM
Expand Down Expand Up @@ -1836,8 +1876,7 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* Helper method used to create a [[UserADM]] from the [[SparqlExtendedConstructResponse]] containing user data.
*
* @param statements result from the SPARQL query containing user data.
*
* @return a [[UserADM]] containing the user's data.
* @return a [[Option[UserADM]]]
*/
private def statements2UserADM(
statements: (SubjectV2, Map[SmartIri, Seq[LiteralV2]])
Expand Down Expand Up @@ -2114,6 +2153,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde

/**
* Tries to retrieve a [[UserADM]] from the cache.
*
* @param identifier the user's identifier (could be IRI, e-mail or username)
* @return a [[Option[UserADM]]]
*/
private def getUserFromCache(identifier: UserIdentifierADM): Future[Option[UserADM]] =
tracedFuture("admin-user-get-user-from-cache") {
Expand All @@ -2132,7 +2174,7 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde
* Writes the user profile to cache.
*
* @param user a [[UserADM]].
* @return true if writing was successful.
* @return Unit
* @throws ApplicationCacheException when there is a problem with writing the user's profile to cache.
*/
private def writeUserADMToCache(user: UserADM): Future[Unit] = for {
Expand All @@ -2142,6 +2184,9 @@ class UsersResponderADM(responderData: ResponderData) extends Responder(responde

/**
* Removes the user from cache.
*
* @param maybeUser the optional user which is removed from the cache
* @return a [[Unit]]
*/
private def invalidateCachedUserADM(maybeUser: Option[UserADM]): Future[Unit] =
if (cacheServiceSettings.cacheServiceEnabled) {
Expand Down
Expand Up @@ -113,7 +113,7 @@ final case class HealthRoute(routeData: KnoraRouteData, runtime: Runtime[State])
get { requestContext =>
val res: ZIO[State, Nothing, HttpResponse] = {
for {
_ <- ZIO.logInfo("health route start")
_ <- ZIO.logDebug("health route start")
ec <- ZIO.executor.map(_.asExecutionContext)
state <- ZIO.service[State]
requestingUser <-
Expand All @@ -123,7 +123,7 @@ final case class HealthRoute(routeData: KnoraRouteData, runtime: Runtime[State])
)
.orElse(ZIO.succeed(KnoraSystemInstances.Users.AnonymousUser))
result <- healthCheck(state)
_ <- ZIO.logInfo("health route finished") @@ ZIOAspect.annotated("user-id", requestingUser.id.toString())
_ <- ZIO.logDebug("health route finished") @@ ZIOAspect.annotated("user-id", requestingUser.id.toString())
} yield result
} @@ LogAspect.logSpan("health-request") @@ LogAspect.logAnnotateCorrelationId(requestContext.request)

Expand Down
Expand Up @@ -35,17 +35,19 @@ object IIIFServiceManager {
val layer: ZLayer[IIIFService, Nothing, IIIFServiceManager] =
ZLayer {
for {
iiifs <- ZIO.service[IIIFService]
} yield new IIIFServiceManager {

override def receive(message: IIIFRequest) = message match {
case req: GetFileMetadataRequest => iiifs.getFileMetadata(req)
case req: MoveTemporaryFileToPermanentStorageRequest => iiifs.moveTemporaryFileToPermanentStorage(req)
case req: DeleteTemporaryFileRequest => iiifs.deleteTemporaryFile(req)
case req: SipiGetTextFileRequest => iiifs.getTextFileRequest(req)
case IIIFServiceGetStatus => iiifs.getStatus()
case other => ZIO.logError(s"IIIFServiceManager received an unexpected message: $other")
}
}
iiifService <- ZIO.service[IIIFService]
} yield IIIFServiceManagerImpl(iiifService)
}

private final case class IIIFServiceManagerImpl(iiifService: IIIFService) extends IIIFServiceManager {

override def receive(message: IIIFRequest) = message match {
case req: GetFileMetadataRequest => iiifService.getFileMetadata(req)
case req: MoveTemporaryFileToPermanentStorageRequest => iiifService.moveTemporaryFileToPermanentStorage(req)
case req: DeleteTemporaryFileRequest => iiifService.deleteTemporaryFile(req)
case req: SipiGetTextFileRequest => iiifService.getTextFileRequest(req)
case IIIFServiceGetStatus => iiifService.getStatus()
case other => ZIO.logError(s"IIIFServiceManager received an unexpected message: $other")
}
}
}