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 3fd233fa..bc79b51f 100644 --- a/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt +++ b/jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt @@ -271,6 +271,7 @@ class JiraDataService( imsProject: IMSProject, users: List, requestMethod: HttpMethod, + owner: List, body: T? = null, crossinline urlBuilder: URLBuilder .(URLBuilder) -> Unit ): Pair { @@ -286,7 +287,7 @@ class JiraDataService( } val userList = rawUserList.distinct() logger.info("Requesting with users: $userList") - 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 1c2804f5..2e3bf451 100644 --- a/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt +++ b/jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt @@ -105,7 +105,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") @@ -129,7 +129,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") @@ -163,7 +163,7 @@ final class JiraSync( var startAt = 0 while (true) { val imsProjectConfig = IMSProjectConfig(helper, imsProject) - val issueResponse = jiraDataService.request(imsProject, listOf(), HttpMethod.Get) { + val issueResponse = jiraDataService.request(imsProject, listOf(), HttpMethod.Get, listOf()) { appendPathSegments("search") parameters.append("jql", "project=${imsProjectConfig.repo}") parameters.append("expand", "names,schema,editmeta,changelog") @@ -189,7 +189,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) @@ -203,7 +207,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( @@ -233,7 +237,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( @@ -256,7 +260,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( @@ -301,7 +305,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( @@ -340,6 +344,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..4cf171ee 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,27 +146,63 @@ 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") @@ -174,18 +211,22 @@ abstract class TokenManager( /** * 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) } /**