diff --git a/api-common/src/main/kotlin/gropius/graphql/GraphQLConfiguration.kt b/api-common/src/main/kotlin/gropius/graphql/GraphQLConfiguration.kt index 213c311d..588e398c 100644 --- a/api-common/src/main/kotlin/gropius/graphql/GraphQLConfiguration.kt +++ b/api-common/src/main/kotlin/gropius/graphql/GraphQLConfiguration.kt @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import graphql.Scalars import graphql.scalars.regex.RegexScalar import graphql.schema.* +import gropius.authorization.GropiusAuthorizationContext import gropius.authorization.checkPermission import gropius.authorization.gropiusAuthorizationContext import gropius.graphql.filter.* @@ -33,6 +34,7 @@ import io.github.graphglue.connection.filter.definition.scalars.StringFilterDefi import io.github.graphglue.definition.ExtensionFieldDefinition import io.github.graphglue.definition.NodeDefinition import io.github.graphglue.definition.NodeDefinitionCollection +import io.github.graphglue.graphql.extensions.authorizationContext import org.neo4j.cypherdsl.core.Cypher import org.neo4j.cypherdsl.core.Expression import org.neo4j.driver.Driver @@ -309,6 +311,56 @@ class GraphQLConfiguration { } } + /** + * Provides the [ExtensionFieldDefinition] for checking if the current user allows to sync their data to a target + */ + @Bean(SYNC_SELF_ALLOWED_FIELD_BEAN) + fun syncSelfAllowedField(): ExtensionFieldDefinition = syncAllowedField(true) + + /** + * Provides the [ExtensionFieldDefinition] for checking if the current user allows to sync other users' data to a target + */ + @Bean(SYNC_OTHERS_ALLOWED_FIELD_BEAN) + fun syncOthersAllowedField(): ExtensionFieldDefinition = syncAllowedField(false) + + /** + * Provides the [ExtensionFieldDefinition] for checking if the current user allows to sync data to a target + * + * @param self if the field is the self or other variant + * @return the generated field definition + */ + private fun syncAllowedField(self: Boolean): ExtensionFieldDefinition { + val field = GraphQLFieldDefinition.newFieldDefinition().name("isSync${if (self) "Self" else "Others"}Allowed") + .description("Checks if the current user allows to sync ${if (self) "their" else "other users'"} data to this target") + .type(GraphQLNonNull(Scalars.GraphQLBoolean)).build() + + return object : ExtensionFieldDefinition(field) { + override fun generateFetcher( + dfe: DataFetchingEnvironment, + arguments: Map, + node: org.neo4j.cypherdsl.core.Node, + nodeDefinition: NodeDefinition + ): Expression { + val authorizationContext = dfe.authorizationContext as? GropiusAuthorizationContext + return if (authorizationContext != null) { + Cypher.exists( + node.relationshipFrom( + authorizationContext.userNode, + if (self) GropiusUser.CAN_SYNC_SELF else GropiusUser.CAN_SYNC_OTHERS + ) + ) + } else { + Cypher.literalFalse() + } + } + + override fun transformResult(result: Value): Any { + return result.asBoolean() + } + + } + } + /** * Provides the [KotlinDataFetcherFactoryProvider] which generates a FunctionDataFetcher which handles * JSON input value injecting correctly. diff --git a/core/src/main/kotlin/gropius/dto/input/architecture/UpdateSyncPermissionsInput.kt b/core/src/main/kotlin/gropius/dto/input/architecture/UpdateSyncPermissionsInput.kt index 778474ed..9f78f5f4 100644 --- a/core/src/main/kotlin/gropius/dto/input/architecture/UpdateSyncPermissionsInput.kt +++ b/core/src/main/kotlin/gropius/dto/input/architecture/UpdateSyncPermissionsInput.kt @@ -9,7 +9,7 @@ class UpdateSyncPermissionsInput( @GraphQLDescription("The SyncPermissionTarget to update the sync permissions for the current user") val id: ID, @GraphQLDescription("Whether the sync service is allowed to sync content of the user") - val canSyncSelf: Boolean, + val canSyncSelf: Boolean?, @GraphQLDescription("Whether the sync service is allowed to sync content of other users") - val canSyncOthers: Boolean + val canSyncOthers: Boolean? ) : Input() \ No newline at end of file diff --git a/core/src/main/kotlin/gropius/model/architecture/IMS.kt b/core/src/main/kotlin/gropius/model/architecture/IMS.kt index a1781987..06a0971c 100644 --- a/core/src/main/kotlin/gropius/model/architecture/IMS.kt +++ b/core/src/main/kotlin/gropius/model/architecture/IMS.kt @@ -13,7 +13,7 @@ import gropius.model.user.permission.NodeWithPermissions import io.github.graphglue.model.* import org.springframework.data.neo4j.core.schema.CompositeProperty -@DomainNode("imss") +@DomainNode("imss", searchQueryName = "searchIMSs") @GraphQLDescription( """Entity which represents an issue management system (like GitHub, Jira, Redmine, ...). Trackables can be added to this via an IMSProject, so that their issues are synced to this IMS. diff --git a/core/src/main/kotlin/gropius/model/architecture/IMSProject.kt b/core/src/main/kotlin/gropius/model/architecture/IMSProject.kt index b8116671..e317d4de 100644 --- a/core/src/main/kotlin/gropius/model/architecture/IMSProject.kt +++ b/core/src/main/kotlin/gropius/model/architecture/IMSProject.kt @@ -11,7 +11,7 @@ import gropius.model.user.permission.TrackablePermission import io.github.graphglue.model.* import org.springframework.data.neo4j.core.schema.CompositeProperty -@DomainNode +@DomainNode(searchQueryName = "searchIMSProjects") @GraphQLDescription( """Project on an IMS, represents a Trackable synced to an IMS. The representation on the IMS depends on the type of IMS, e.g. for GitHub, a project is a repository. @@ -48,6 +48,7 @@ class IMSProject( @GraphQLDescription("The IMS this project is a part of.") @GraphQLNullable @FilterProperty + @OrderProperty val ims by NodeProperty() @NodeRelationship(IMSIssue.PROJECT, Direction.INCOMING) diff --git a/core/src/main/kotlin/gropius/model/architecture/SyncPermissionTarget.kt b/core/src/main/kotlin/gropius/model/architecture/SyncPermissionTarget.kt index 9eb5ff2b..4c20bb04 100644 --- a/core/src/main/kotlin/gropius/model/architecture/SyncPermissionTarget.kt +++ b/core/src/main/kotlin/gropius/model/architecture/SyncPermissionTarget.kt @@ -4,10 +4,23 @@ import com.expediagroup.graphql.generator.annotations.GraphQLDescription import gropius.model.common.NamedNode import gropius.model.user.GropiusUser import io.github.graphglue.model.Direction +import io.github.graphglue.model.ExtensionField import io.github.graphglue.model.FilterProperty import io.github.graphglue.model.NodeRelationship +/** + * Name of the bean which provides the sync allowed extension field + */ +const val SYNC_SELF_ALLOWED_FIELD_BEAN = "syncSelfAllowedFieldBean" + +/** + * Name of the bean which provides the sync allowed extension field + */ +const val SYNC_OTHERS_ALLOWED_FIELD_BEAN = "syncOthersAllowedFieldBean" + @GraphQLDescription("A target where users can configure how the sync should behave.") +@ExtensionField(SYNC_SELF_ALLOWED_FIELD_BEAN) +@ExtensionField(SYNC_OTHERS_ALLOWED_FIELD_BEAN) abstract class SyncPermissionTarget(name: String, description: String) : NamedNode(name, description) { @NodeRelationship(GropiusUser.CAN_SYNC_SELF, Direction.INCOMING) diff --git a/core/src/main/kotlin/gropius/model/template/IMSTemplate.kt b/core/src/main/kotlin/gropius/model/template/IMSTemplate.kt index 0438ce1a..c33df524 100644 --- a/core/src/main/kotlin/gropius/model/template/IMSTemplate.kt +++ b/core/src/main/kotlin/gropius/model/template/IMSTemplate.kt @@ -6,7 +6,7 @@ import io.github.graphglue.model.Direction import io.github.graphglue.model.DomainNode import io.github.graphglue.model.NodeRelationship -@DomainNode("imsTemplates") +@DomainNode("imsTemplates", searchQueryName = "searchIMSTemplates") @GraphQLDescription( """Template for imss Defines templated fields with specific types (defined using JSON schema). diff --git a/core/src/main/kotlin/gropius/service/architecture/SyncPermissionTargetService.kt b/core/src/main/kotlin/gropius/service/architecture/SyncPermissionTargetService.kt index 59c691ef..964db1f2 100644 --- a/core/src/main/kotlin/gropius/service/architecture/SyncPermissionTargetService.kt +++ b/core/src/main/kotlin/gropius/service/architecture/SyncPermissionTargetService.kt @@ -35,15 +35,19 @@ class SyncPermissionTargetService( val target = repository.findById(input.id) checkPermission(target, Permission(NodePermission.READ, authorizationContext), "use the SyncPermissionTarget") val user = getUser(authorizationContext) - if (input.canSyncSelf) { - target.syncSelfAllowedBy() += user - } else { - target.syncSelfAllowedBy() -= user + if (input.canSyncSelf != null) { + if (input.canSyncSelf) { + target.syncSelfAllowedBy() += user + } else { + target.syncSelfAllowedBy() -= user + } } - if (input.canSyncOthers) { - target.syncOthersAllowedBy() += user - } else { - target.syncOthersAllowedBy() -= user + if (input.canSyncOthers != null) { + if (input.canSyncOthers) { + target.syncOthersAllowedBy() += user + } else { + target.syncOthersAllowedBy() -= user + } } return repository.save(target).awaitSingle() } diff --git a/github/src/main/kotlin/gropius/sync/github/GithubDataService.kt b/github/src/main/kotlin/gropius/sync/github/GithubDataService.kt index 307d2987..9fcc4153 100644 --- a/github/src/main/kotlin/gropius/sync/github/GithubDataService.kt +++ b/github/src/main/kotlin/gropius/sync/github/GithubDataService.kt @@ -213,10 +213,11 @@ class GithubDataService( * @param imsProject The IMSProject to work on * @param users The users sorted with best first * @param body The content of the mutation + * @param owner The user that created the data, empty if fetching/other non-owned operations * @return The selected user and the response for the mutation */ final suspend inline fun mutation( - imsProject: IMSProject, users: List, body: Mutation + imsProject: IMSProject, users: List, body: Mutation, owner: List ): Pair> { val imsConfig = IMSConfig(helper, imsProject.ims().value, imsProject.ims().value.template().value) val userList = users.toMutableList() @@ -228,7 +229,7 @@ class GithubDataService( userList.add(imsUser) } logger.info("Requesting with users: $userList") - return tokenManager.executeUntilWorking(imsProject.ims().value, userList) { token -> + return tokenManager.executeUntilWorking(imsProject, userList, owner) { token -> val apolloClient = ApolloClient.Builder().serverUrl(imsConfig.graphQLUrl.toString()) .addHttpHeader("Authorization", "Bearer ${token.token}").build() val res = apolloClient.mutation(body).execute() @@ -248,6 +249,7 @@ class GithubDataService( * @param imsProject The IMSProject to work on * @param users The users sorted with best first * @param body The content of the query + * @param owner The user that created the data, empty if fetching/other non-owned operations * @return The selected user and the response for the query */ final suspend inline fun query( @@ -267,7 +269,7 @@ class GithubDataService( userList.add(imsUser) } logger.info("Requesting with users: $userList ") - return tokenManager.executeUntilWorking(imsProject.ims().value, userList) { token -> + return tokenManager.executeUntilWorking(imsProject, userList, listOf()) { token -> val apolloClient = ApolloClient.Builder().serverUrl(imsConfig.graphQLUrl.toString()) .addHttpHeader("Authorization", "Bearer ${token.token}").build() val res = apolloClient.query(body).execute() diff --git a/github/src/main/kotlin/gropius/sync/github/GithubSync.kt b/github/src/main/kotlin/gropius/sync/github/GithubSync.kt index a17a306c..1bcee93a 100644 --- a/github/src/main/kotlin/gropius/sync/github/GithubSync.kt +++ b/github/src/main/kotlin/gropius/sync/github/GithubSync.kt @@ -6,6 +6,8 @@ import gropius.model.issue.Label import gropius.model.issue.timeline.IssueComment import gropius.model.template.IMSTemplate import gropius.model.template.IssueState +import gropius.model.user.GropiusUser +import gropius.model.user.IMSUser import gropius.model.user.User import gropius.sync.* import gropius.sync.github.config.IMSConfigManager @@ -154,13 +156,15 @@ final class GithubSync( override suspend fun findUnsyncedIssues(imsProject: IMSProject): List { return issuePileService.findByImsProjectAndHasUnsyncedData(imsProject.rawId!!, true) } - + override suspend fun syncComment( imsProject: IMSProject, issueId: String, issueComment: IssueComment, users: List ): TimelineItemConversionInformation? { val body = issueComment.body if (body.isNullOrEmpty()) return null; - val response = githubDataService.mutation(imsProject, users, MutateCreateCommentMutation(issueId, body)).second + val response = githubDataService.mutation( + imsProject, users, MutateCreateCommentMutation(issueId, body), gropiusUserList(users) + ).second val item = response.data?.addComment?.commentEdge?.node?.asIssueTimelineItems() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.id) @@ -180,8 +184,9 @@ final class GithubSync( //TODO("Create label on remote") return null } - val response = - githubDataService.mutation(imsProject, users, MutateAddLabelMutation(issueId, labelInfo.githubId)).second + val response = githubDataService.mutation( + imsProject, users, MutateAddLabelMutation(issueId, labelInfo.githubId), gropiusUserList(users) + ).second val item = response.data?.addLabelsToLabelable?.labelable?.asIssue()?.timelineItems?.nodes?.lastOrNull() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.asNode()!!.id) @@ -194,8 +199,9 @@ final class GithubSync( override suspend fun syncTitleChange( imsProject: IMSProject, issueId: String, newTitle: String, users: List ): TimelineItemConversionInformation? { - val response = - githubDataService.mutation(imsProject, users, MutateChangeTitleMutation(issueId, newTitle)).second + val response = githubDataService.mutation( + imsProject, users, MutateChangeTitleMutation(issueId, newTitle), gropiusUserList(users) + ).second val item = response.data?.updateIssue?.issue?.timelineItems?.nodes?.lastOrNull() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.asNode()!!.id) @@ -209,7 +215,9 @@ final class GithubSync( imsProject: IMSProject, issueId: String, newState: IssueState, users: List ): TimelineItemConversionInformation? { if (newState.isOpen) { - val response = githubDataService.mutation(imsProject, users, MutateReopenIssueMutation(issueId)).second + val response = githubDataService.mutation( + imsProject, users, MutateReopenIssueMutation(issueId), gropiusUserList(users) + ).second val item = response.data?.reopenIssue?.issue?.timelineItems?.nodes?.lastOrNull() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.asNode()!!.id) @@ -218,7 +226,9 @@ final class GithubSync( //TODO("ERROR HANDLING") return null } else { - val response = githubDataService.mutation(imsProject, users, MutateCloseIssueMutation(issueId)).second + val response = githubDataService.mutation( + imsProject, users, MutateCloseIssueMutation(issueId), gropiusUserList(users) + ).second val item = response.data?.closeIssue?.issue?.timelineItems?.nodes?.lastOrNull() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.asNode()!!.id) @@ -234,8 +244,9 @@ final class GithubSync( ): TimelineItemConversionInformation? { val labelInfo = githubDataService.labelInfoRepository.findByImsProjectAndNeo4jId(imsProject.rawId!!, label.rawId!!)!! - val response = - githubDataService.mutation(imsProject, users, MutateRemoveLabelMutation(issueId, labelInfo.githubId)).second + val response = githubDataService.mutation( + imsProject, users, MutateRemoveLabelMutation(issueId, labelInfo.githubId), gropiusUserList(users) + ).second val item = response.data?.removeLabelsFromLabelable?.labelable?.asIssue()?.timelineItems?.nodes?.lastOrNull() if (item != null) { return TODOTimelineItemConversionInformation(imsProject.rawId!!, item.asNode()!!.id) @@ -258,7 +269,9 @@ final class GithubSync( imsProject, listOf(issue.createdBy().value, issue.lastModifiedBy().value) + issue.timelineItems() .map { it.createdBy().value }, - MutateCreateIssueMutation(repoId, issue.title, issue.bodyBody) + MutateCreateIssueMutation(repoId, issue.title, issue.bodyBody), + gropiusUserList(listOf(issue.createdBy().value, issue.lastModifiedBy().value) + issue.timelineItems() + .map { it.createdBy().value }) ).second val item = response.data?.createIssue?.issue if (item != null) { diff --git a/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt b/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt index 4618b906..46181564 100644 --- a/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt +++ b/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt @@ -296,11 +296,12 @@ class JiraDataService( imsProject: IMSProject, users: List, requestMethod: HttpMethod, + owner: List, body: T? = null, crossinline urlBuilder: URLBuilder .(URLBuilder) -> Unit ): Pair { val userList = collectRequestUsers(imsProject, users) - return tokenManager.executeUntilWorking(imsProject.ims().value, userList) { + return tokenManager.executeUntilWorking(imsProject, userList, owner) { sendRequest( imsProject, requestMethod, body, urlBuilder, it ) diff --git a/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt b/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt index 3150f9a0..a1e51905 100644 --- a/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -125,7 +125,7 @@ final class JiraSync( for (issueId in issueList) { var startAt = 0 while (true) { - val issueCommentList = jiraDataService.request(imsProject, listOf(), HttpMethod.Get) { + val issueCommentList = jiraDataService.request(imsProject, listOf(), HttpMethod.Get, listOf()) { appendPathSegments("issue") appendPathSegments(issueId) appendPathSegments("changelog") @@ -154,7 +154,7 @@ final class JiraSync( for (issueId in issueList) { var startAt = 0 while (true) { - val issueCommentList = jiraDataService.request(imsProject, listOf(), HttpMethod.Get) { + val issueCommentList = jiraDataService.request(imsProject, listOf(), HttpMethod.Get, listOf()) { appendPathSegments("issue") appendPathSegments(issueId) appendPathSegments("comment") @@ -206,7 +206,7 @@ final class JiraSync( while (true) { val imsProjectConfig = IMSProjectConfig(helper, imsProject) val userList = jiraDataService.collectRequestUsers(imsProject, listOf()) - val issueResponse = jiraDataService.tokenManager.executeUntilWorking(imsProject.ims().value, userList) { + val issueResponse = jiraDataService.tokenManager.executeUntilWorking(imsProject, userList, listOf()) { val userTimeZone = ZoneId.of( jiraDataService.sendRequest( imsProject, HttpMethod.Get, null, { @@ -280,7 +280,11 @@ final class JiraSync( return null } val response = jiraDataService.request( - imsProject, users, HttpMethod.Post, JsonObject(mapOf("body" to JsonPrimitive(issueComment.body))) + imsProject, + users, + HttpMethod.Post, + gropiusUserList(users), + JsonObject(mapOf("body" to JsonPrimitive(issueComment.body))) ) { appendPathSegments("issue") appendPathSegments(issueId) @@ -294,7 +298,7 @@ final class JiraSync( imsProject: IMSProject, issueId: String, newTitle: String, users: List ): TimelineItemConversionInformation? { val response = jiraDataService.request( - imsProject, users, HttpMethod.Put, JsonObject( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( mapOf( "fields" to JsonObject( mapOf( @@ -324,7 +328,7 @@ final class JiraSync( return null; } jiraDataService.request( - imsProject, users, HttpMethod.Put, JsonObject( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( mapOf( "fields" to JsonObject( mapOf( @@ -347,7 +351,7 @@ final class JiraSync( imsProject: IMSProject, issueId: String, label: Label, users: List ): TimelineItemConversionInformation? { val response = jiraDataService.request( - imsProject, users, HttpMethod.Put, JsonObject( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( mapOf( "update" to JsonObject( mapOf( @@ -392,7 +396,7 @@ final class JiraSync( imsProject: IMSProject, issueId: String, label: Label, users: List ): TimelineItemConversionInformation? { val response = jiraDataService.request( - imsProject, users, HttpMethod.Put, JsonObject( + imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject( mapOf( "update" to JsonObject( mapOf( @@ -431,6 +435,8 @@ final class JiraSync( listOf(issue.createdBy().value, issue.lastModifiedBy().value) + issue.timelineItems() .map { it.createdBy().value }, HttpMethod.Post, + gropiusUserList(listOf(issue.createdBy().value, issue.lastModifiedBy().value) + issue.timelineItems() + .map { it.createdBy().value }), IssueQueryRequest( IssueQueryRequestFields( issue.title, diff --git a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt index e46f6c9e..2847c651 100644 --- a/sync/src/main/kotlin/gropius/sync/AbstractSync.kt +++ b/sync/src/main/kotlin/gropius/sync/AbstractSync.kt @@ -10,6 +10,7 @@ import gropius.model.template.IMSTemplate import gropius.model.template.IssueState import gropius.model.template.IssueType import gropius.model.user.GropiusUser +import gropius.model.user.IMSUser import gropius.model.user.User import gropius.repository.common.NodeRepository import gropius.repository.issue.IssueRepository @@ -885,4 +886,23 @@ abstract class AbstractSync( } logger.info("Finished Sync Cycle") } + + /** + * Map list of User to GropiusUser + * @param users The list of users mixed of IMSUser and GropiusUser + * @return The list of GropiusUser + */ + suspend fun gropiusUserList(users: List): List { + val outputUsers = users.mapNotNull { + when (it) { + is GropiusUser -> it + is IMSUser -> it.gropiusUser().value + else -> null + } + } + if (outputUsers.isEmpty() && users.isNotEmpty()) { + throw IllegalStateException("No Gropius User left as owner") + } + return outputUsers + } } diff --git a/sync/src/main/kotlin/gropius/sync/TokenManager.kt b/sync/src/main/kotlin/gropius/sync/TokenManager.kt index 20e30bf5..c024cbf0 100644 --- a/sync/src/main/kotlin/gropius/sync/TokenManager.kt +++ b/sync/src/main/kotlin/gropius/sync/TokenManager.kt @@ -1,6 +1,7 @@ package gropius.sync import gropius.model.architecture.IMS +import gropius.model.architecture.IMSProject import gropius.model.user.GropiusUser import gropius.model.user.IMSUser import gropius.model.user.User @@ -145,47 +146,87 @@ abstract class TokenManager( return ret } + /** + * Check if a user is allowed to be used for syncing + * + * @param imsProject The IMS to work with + * @param user The user to check + * @param owner The user that created the data, empty if fetching/other non-owned operations + * @return true if the user is allowed + */ + private suspend fun isAllowed(imsProject: IMSProject, user: IMSUser, owner: List): Boolean { + val ownerSet = owner.toSet() + if ((owner.isEmpty() || ownerSet.contains(user.gropiusUser().value)) && imsProject.ims().value.syncSelfAllowedBy() + .contains(user.gropiusUser().value) + ) { + return true + } + if ((owner.isEmpty() || ownerSet.contains(user.gropiusUser().value)) && imsProject.syncSelfAllowedBy() + .contains(user.gropiusUser().value) + ) { + return true + } + if (imsProject.ims().value.syncOthersAllowedBy().contains(user.gropiusUser().value)) { + return true + } + if (imsProject.syncOthersAllowedBy().contains(user.gropiusUser().value)) { + return true + } + return false + } + /** * Attempt a query for a list of users until it works * + * @param imsProject The IMS to work with * @param users The list of users, sorted with best first * @param executor The function to execute + * @param owner The user that created the data, empty if fetching/other non-owned operations * * @return The user it worked with and the result of the executor */ private suspend fun executeUntilWorking( - users: List, executor: suspend (token: ResponseType) -> Optional + imsProject: IMSProject, + users: List, + executor: suspend (token: ResponseType) -> Optional, + owner: List ): Pair { for (user in users) { - val token = getUserToken(user) - if (token?.token != null) { - logger.trace("Trying token of user ${user.rawId}") - val ret = executor(token) - if (ret.isPresent) { - return user to ret.get() + if (isAllowed(imsProject, user, owner)) { + val token = getUserToken(user) + if (token?.token != null) { + logger.trace("Trying token of user ${user.rawId}") + val ret = executor(token) + if (ret.isPresent) { + return user to ret.get() + } + } else { + logger.trace("User ${user.rawId} had no token") } - } else { - logger.trace("User ${user.rawId} had no token") } } - TODO("Error Message") + TODO("Error Message for no working users") } /** * Attempt a query for a list of users until it works * - * @param ims The IMS to work with + * @param imsProject The IMS to work with * @param user The list of users, sorted with best first * @param executor The function to execute + * @param owner The user that created the data, empty if fetching/other non-owned operations * * @return The user it worked with and the result of the executor */ suspend fun executeUntilWorking( - ims: IMS, user: List, executor: suspend (token: ResponseType) -> Optional + imsProject: IMSProject, + user: List, + owner: List, + executor: suspend (token: ResponseType) -> Optional ): Pair { - val users = user.map { getPossibleUsersForUser(ims, it) }.flatten().distinct() + val users = user.map { getPossibleUsersForUser(imsProject.ims().value, it) }.flatten().distinct() logger.info("Expanding ${user.map { "${it::class.simpleName}:${it.rawId}(${it.username})" }} to ${users.map { "${it::class.simpleName}:${it.rawId}(${it.username})" }}") - return executeUntilWorking(users, executor) + return executeUntilWorking(imsProject, users, executor, owner) } /**