Skip to content

Commit

Permalink
update app strategy role permissions for API
Browse files Browse the repository at this point in the history
  • Loading branch information
rvowles committed Dec 9, 2024
1 parent 5c8afbd commit c8efc0d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ interface ApplicationApi {
fun personIsFeatureCreator(appId: UUID, personId: UUID): Boolean
fun findFeatureReaders(appId: UUID): Set<UUID>
fun personIsFeatureReader(appId: UUID, personId: UUID): Boolean
fun personApplicationRoles(appId: UUID, personId: UUID): Set<ApplicationRoleType>
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ interface Conversions {
fun toPortfolio(p: DbPortfolio?, opts: Opts?, person: Person?, personNotSuperAdmin: Boolean): Portfolio?
fun toPortfolio(p: DbPortfolio?, opts: Opts?, personId: UUID?, personNotSuperAdmin: Boolean): Portfolio?
fun toOrganization(org: DbOrganization?, opts: Opts?): Organization?
// actually isPersonPortfolioAdmin
fun isPersonApplicationAdmin(dbPerson: DbPerson?, app: DbApplication?): Boolean
// actually isPersonPortfolioAdmin
fun isPersonApplicationAdmin(personId: UUID, appId: UUID): Boolean
fun toServiceAccount(sa: DbServiceAccount?, opts: Opts?): ServiceAccount?
fun toServiceAccount(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,10 @@ class ApplicationSqlApi @Inject constructor(
override fun findApplicationPermissions(appId: UUID, personId: UUID): ApplicationPermissions {
// superusers get everything
if (convertUtils.personIsSuperAdmin(personId) || convertUtils.isPersonApplicationAdmin(personId, appId) ) {
val allRoles = RoleType.values().toList()
val allRoles = RoleType.entries

return ApplicationPermissions()
.applicationRoles(ApplicationRoleType.values().toList())
.applicationRoles(ApplicationRoleType.entries)
.environments(QDbEnvironment()
.select(QDbEnvironment.Alias.name, QDbEnvironment.Alias.id)
.whenArchived.isNull
Expand Down Expand Up @@ -517,6 +517,24 @@ class ApplicationSqlApi @Inject constructor(
return HashSet()
}

override fun personApplicationRoles(appId: UUID, personId: UUID): Set<ApplicationRoleType> {
// if they are a super user or portfolio admin, they have all roles
if (convertUtils.personIsSuperAdmin(personId) || convertUtils.isPersonApplicationAdmin(personId, appId)) {
return ApplicationRoleType.entries.toSet()
}

val roles = mutableSetOf<ApplicationRoleType>()

QDbAcl()
.application.id.eq(appId)
.group.whenArchived.isNull()
.group.groupMembers.person.id.eq(personId).findList().forEach { acl ->
roles.addAll(convertUtils.splitApplicationRoles(acl.roles))
}

return roles
}

override fun findFeatureReaders(appId: UUID): Set<UUID> {
Conversions.nonNullApplicationId(appId)
val featureReaders: MutableSet<UUID> = HashSet()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class ApplicationRolloutStrategyResource @Inject constructor(
holder: ApplicationRolloutStrategyServiceDelegate.CreateApplicationStrategyHolder,
securityContext: SecurityContext
): ApplicationRolloutStrategy {
val person = applicationUtils.featureCreatorCheck(securityContext, appId).current
val person = applicationUtils.applicationStrategyCreate(securityContext, appId)

try {
return applicationRolloutStrategyApi.createStrategy(
appId, createRolloutStrategy, person.id!!.id,
appId, createRolloutStrategy, person,
Opts().add(FillOpts.SimplePeople, holder.includeWhoChanged)
) ?: throw NotFoundException()
} catch (e: ApplicationRolloutStrategyApi.DuplicateNameException) {
Expand All @@ -42,9 +43,9 @@ class ApplicationRolloutStrategyResource @Inject constructor(
holder: ApplicationRolloutStrategyServiceDelegate.DeleteApplicationStrategyHolder,
securityContext: SecurityContext
) {
val person = applicationUtils.featureCreatorCheck(securityContext, appId).current
val person = applicationUtils.applicationStrategyDelete(securityContext, appId)

if (!applicationRolloutStrategyApi.archiveStrategy(appId, appStrategyId, person.id!!.id)) {
if (!applicationRolloutStrategyApi.archiveStrategy(appId, appStrategyId, person)) {
throw NotFoundException()
}
}
Expand All @@ -55,7 +56,8 @@ class ApplicationRolloutStrategyResource @Inject constructor(
holder: ApplicationRolloutStrategyServiceDelegate.GetApplicationStrategyHolder,
securityContext: SecurityContext
): ApplicationRolloutStrategy {
applicationUtils.featureReadCheck(securityContext, appId)
applicationUtils.applicationStrategyReadCheck(securityContext, appId)

log.info("getApplicationStrategy: {}", holder)

return applicationRolloutStrategyApi.getStrategy(
Expand All @@ -71,7 +73,8 @@ class ApplicationRolloutStrategyResource @Inject constructor(
holder: ApplicationRolloutStrategyServiceDelegate.ListApplicationStrategiesHolder,
securityContext: SecurityContext
): ApplicationRolloutStrategyList {
applicationUtils.featureReadCheck(securityContext, appId)
applicationUtils.applicationStrategyReadCheck(securityContext, appId)

return applicationRolloutStrategyApi.listStrategies(
appId,
holder.page ?: 0, holder.max ?: 20, holder.filter,
Expand Down
125 changes: 125 additions & 0 deletions backend/mr/src/main/kotlin/io/featurehub/mr/utils/ApplicationUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package io.featurehub.mr.utils

import io.featurehub.db.api.ApplicationApi
import io.featurehub.db.api.Opts
import io.featurehub.mr.auth.AuthManagerService
import io.featurehub.mr.model.Application
import io.featurehub.mr.model.ApplicationRoleType
import io.featurehub.mr.model.Person
import jakarta.inject.Inject
import jakarta.ws.rs.ForbiddenException
import jakarta.ws.rs.NotFoundException
import jakarta.ws.rs.WebApplicationException
import jakarta.ws.rs.core.SecurityContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.*

class ApplicationUtils @Inject constructor(
private val authManager: AuthManagerService,
private val applicationApi: ApplicationApi
) {
@JvmOverloads
fun check(securityContext: SecurityContext, id: UUID, opts: Opts = Opts.empty()): ApplicationPermissionCheck {
val current = authManager.from(securityContext)

return check(current, id, opts)
}

@Throws(WebApplicationException::class)
fun check(current: Person, id: UUID, opts: Opts): ApplicationPermissionCheck {
val app = applicationApi.getApplication(id, opts) ?: throw NotFoundException()

if (authManager.isOrgAdmin(current) || authManager.isPortfolioAdmin(app.portfolioId, current, null)) {
return ApplicationPermissionCheck(current, app)
} else {
throw ForbiddenException()
}
}

@Throws(WebApplicationException::class)
fun featureCreatorCheck(
securityContext: SecurityContext,
appId: UUID
): ApplicationPermissionCheck {
val current = authManager.from(securityContext)

if (!applicationApi.personIsFeatureCreator(appId, current.id!!.id)) {
log.warn(
"Attempt by person {} to edit features in application {}", current.id!!
.id, appId
)

return check(current, appId, Opts.empty())
} else {
return ApplicationPermissionCheck(current, Application().id(appId))
}
}

/**
* This just checks to see if a person has the Editor/Delete permission and if not, throws an exception. A portfolio
* admin or admin will always have it.
*
* @param securityContext
* @param appId
*/
fun featureEditorCheck(securityContext: SecurityContext, appId: UUID) {
val current = authManager.from(securityContext)

if (!applicationApi.personIsFeatureEditor(appId, current.id!!.id)) {
log.warn(
"Attempt by person {} to edt features in application {}", current.id!!
.id, appId
)

check(current, appId, Opts.empty())
}
}

fun applicationStrategyReadCheck(securityContext: SecurityContext, id: UUID): UUID {
val current = authManager.from(securityContext)
val personId = current.id!!.id

if (applicationApi.personIsFeatureReader(id, personId) || applicationApi.personApplicationRoles(id, personId).isEmpty()) {
return personId
}

throw ForbiddenException()
}

private fun appStrategyCheck(securityContext: SecurityContext, id: UUID, roles: Set<ApplicationRoleType>): UUID {
val current = authManager.from(securityContext).id!!.id

if (applicationApi.personApplicationRoles(id, current).any { roles.contains(it) }) {
throw ForbiddenException()
}

return current
}

fun applicationStrategyCreate(securityContext: SecurityContext, id: UUID): UUID {
return appStrategyCheck(securityContext, id, setOf(ApplicationRoleType.APP_STRATEGY_CREATE))
}

fun applicationStrategyEdit(securityContext: SecurityContext, id: UUID): UUID {
return appStrategyCheck(securityContext, id, setOf(ApplicationRoleType.APP_STRATEGY_EDIT, ApplicationRoleType.FEATURE_EDIT_AND_DELETE))
}

fun applicationStrategyDelete(securityContext: SecurityContext, id: UUID): UUID {
return appStrategyCheck(securityContext, id, setOf(ApplicationRoleType.FEATURE_EDIT_AND_DELETE))
}

fun featureReadCheck(securityContext: SecurityContext, id: UUID): Person {
val current = authManager.from(securityContext)

if (!applicationApi.personIsFeatureReader(id, current.id!!.id)) {
throw ForbiddenException()
}

return current
}

companion object {
private val log: Logger = LoggerFactory.getLogger(ApplicationUtils::class.java)
}
}

0 comments on commit c8efc0d

Please sign in to comment.