Skip to content

Commit

Permalink
Merge pull request #119 from ccims/feature/sync_check_perm
Browse files Browse the repository at this point in the history
Check for permission when syncing users
  • Loading branch information
chriku authored Aug 27, 2024
2 parents 279d1e2 + d41ca9e commit d1d1a8b
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 36 deletions.
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>
): 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,
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

0 comments on commit d1d1a8b

Please sign in to comment.