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

Check for permission when syncing users #119

Merged
merged 5 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 <reified D : Mutation.Data> mutation(
imsProject: IMSProject, users: List<User>, body: Mutation<D>
imsProject: IMSProject, users: List<User>, body: Mutation<D>, owner: List<GropiusUser>
nk-coding marked this conversation as resolved.
Show resolved Hide resolved
): Pair<IMSUser, ApolloResponse<D>> {
val imsConfig = IMSConfig(helper, imsProject.ims().value, imsProject.ims().value.template().value)
val userList = users.toMutableList()
Expand All @@ -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()
Expand All @@ -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 <reified D : Query.Data> query(
Expand All @@ -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()
Expand Down
35 changes: 24 additions & 11 deletions github/src/main/kotlin/gropius/sync/github/GithubSync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -154,13 +156,15 @@ final class GithubSync(
override suspend fun findUnsyncedIssues(imsProject: IMSProject): List<IncomingIssue> {
return issuePileService.findByImsProjectAndHasUnsyncedData(imsProject.rawId!!, true)
}

override suspend fun syncComment(
imsProject: IMSProject, issueId: String, issueComment: IssueComment, users: List<User>
): 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)
Expand All @@ -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)
Expand All @@ -194,8 +199,9 @@ final class GithubSync(
override suspend fun syncTitleChange(
imsProject: IMSProject, issueId: String, newTitle: String, users: List<User>
): 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)
Expand All @@ -209,7 +215,9 @@ final class GithubSync(
imsProject: IMSProject, issueId: String, newState: IssueState, users: List<User>
): 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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion jira/src/main/kotlin/gropius/sync/jira/JiraDataService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ class JiraDataService(
imsProject: IMSProject,
users: List<User>,
requestMethod: HttpMethod,
owner: List<GropiusUser>,
body: T? = null,
crossinline urlBuilder: URLBuilder .(URLBuilder) -> Unit
): Pair<IMSUser, HttpResponse> {
Expand All @@ -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<T>(
imsProject, requestMethod, body, urlBuilder, it
)
Expand Down
22 changes: 14 additions & 8 deletions jira/src/main/kotlin/gropius/sync/jira/JiraSync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ final class JiraSync(
for (issueId in issueList) {
var startAt = 0
while (true) {
val issueCommentList = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get) {
val issueCommentList = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get, listOf()) {
appendPathSegments("issue")
appendPathSegments(issueId)
appendPathSegments("changelog")
Expand All @@ -129,7 +129,7 @@ final class JiraSync(
for (issueId in issueList) {
var startAt = 0
while (true) {
val issueCommentList = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get) {
val issueCommentList = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get, listOf()) {
appendPathSegments("issue")
appendPathSegments(issueId)
appendPathSegments("comment")
Expand Down Expand Up @@ -163,7 +163,7 @@ final class JiraSync(
var startAt = 0
while (true) {
val imsProjectConfig = IMSProjectConfig(helper, imsProject)
val issueResponse = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get) {
val issueResponse = jiraDataService.request<Unit>(imsProject, listOf(), HttpMethod.Get, listOf()) {
appendPathSegments("search")
parameters.append("jql", "project=${imsProjectConfig.repo}")
parameters.append("expand", "names,schema,editmeta,changelog")
Expand All @@ -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)
Expand All @@ -203,7 +207,7 @@ final class JiraSync(
imsProject: IMSProject, issueId: String, newTitle: String, users: List<User>
): TimelineItemConversionInformation? {
val response = jiraDataService.request(
imsProject, users, HttpMethod.Put, JsonObject(
imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject(
mapOf(
"fields" to JsonObject(
mapOf(
Expand Down Expand Up @@ -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(
Expand All @@ -256,7 +260,7 @@ final class JiraSync(
imsProject: IMSProject, issueId: String, label: Label, users: List<User>
): TimelineItemConversionInformation? {
val response = jiraDataService.request(
imsProject, users, HttpMethod.Put, JsonObject(
imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject(
mapOf(
"update" to JsonObject(
mapOf(
Expand Down Expand Up @@ -301,7 +305,7 @@ final class JiraSync(
imsProject: IMSProject, issueId: String, label: Label, users: List<User>
): TimelineItemConversionInformation? {
val response = jiraDataService.request(
imsProject, users, HttpMethod.Put, JsonObject(
imsProject, users, HttpMethod.Put, gropiusUserList(users), JsonObject(
mapOf(
"update" to JsonObject(
mapOf(
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions sync/src/main/kotlin/gropius/sync/AbstractSync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<User>): List<GropiusUser> {
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
}
}
67 changes: 54 additions & 13 deletions sync/src/main/kotlin/gropius/sync/TokenManager.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -145,27 +146,63 @@ abstract class TokenManager<ResponseType : BaseResponseType>(
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<GropiusUser>): 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 <T> executeUntilWorking(
users: List<IMSUser>, executor: suspend (token: ResponseType) -> Optional<T>
imsProject: IMSProject,
nk-coding marked this conversation as resolved.
Show resolved Hide resolved
users: List<IMSUser>,
executor: suspend (token: ResponseType) -> Optional<T>,
owner: List<GropiusUser>
): Pair<IMSUser, T> {
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")
Expand All @@ -174,18 +211,22 @@ abstract class TokenManager<ResponseType : BaseResponseType>(
/**
* 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 <T> executeUntilWorking(
ims: IMS, user: List<User>, executor: suspend (token: ResponseType) -> Optional<T>
imsProject: IMSProject,
user: List<User>,
owner: List<GropiusUser>,
executor: suspend (token: ResponseType) -> Optional<T>
): Pair<IMSUser, T> {
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)
}

/**
Expand Down