Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Start applying privileges to resources.

  • Loading branch information...
commit 42c0efaad203a8d43a086a55270e104f92cab4d4 1 parent 1eb0dc6
@paulj paulj authored
Showing with 294 additions and 86 deletions.
  1. +1 −1  agent/src/main/scala/net/lshift/diffa/agent/auth/ExternalAuthenticationProviderSwitch.scala
  2. +75 −17 agent/src/main/scala/net/lshift/diffa/agent/auth/Privilege.scala
  3. +19 −0 agent/src/main/scala/net/lshift/diffa/agent/auth/TargetObject.scala
  4. +14 −10 agent/src/main/scala/net/lshift/diffa/agent/auth/UserDetailsAdapter.scala
  5. +25 −10 agent/src/main/scala/net/lshift/diffa/agent/rest/ActionsResource.scala
  6. +9 −3 agent/src/main/scala/net/lshift/diffa/agent/rest/ChangesResource.scala
  7. +8 −1 agent/src/main/scala/net/lshift/diffa/agent/rest/DiagnosticsResource.scala
  8. +24 −5 agent/src/main/scala/net/lshift/diffa/agent/rest/DifferencesResource.scala
  9. +28 −24 agent/src/main/scala/net/lshift/diffa/agent/rest/DomainResource.scala
  10. +9 −1 agent/src/main/scala/net/lshift/diffa/agent/rest/InventoryResource.scala
  11. +22 −0 agent/src/main/scala/net/lshift/diffa/agent/rest/PermissionUtils.scala
  12. +13 −3 agent/src/main/scala/net/lshift/diffa/agent/rest/ReportsResource.scala
  13. +17 −2 agent/src/main/scala/net/lshift/diffa/agent/rest/ScanningResource.scala
  14. +17 −1 agent/src/main/scala/net/lshift/diffa/agent/rest/UsersResource.scala
  15. +1 −1  agent/src/test/scala/net/lshift/diffa/agent/itest/auth/ExternalAuthTest.scala
  16. +1 −1  agent/src/test/scala/net/lshift/diffa/agent/itest/config/MembershipTest.scala
  17. +4 −1 kernel/src/main/scala/net/lshift/diffa/kernel/config/system/SystemConfigStore.scala
  18. +7 −5 schema/src/main/scala/net/lshift/diffa/schema/migrations/steps/Step0050.scala
View
2  agent/src/main/scala/net/lshift/diffa/agent/auth/ExternalAuthenticationProviderSwitch.scala
@@ -171,7 +171,7 @@ class ExternalAuthenticationProviderSwitch(val configStore:SystemConfigStore)
val identities = Seq(username) ++ res.getAuthorities.map(a => a.getAuthority)
val memberships = identities.flatMap(i => configStore.listDomainMemberships(i))
val domainAuthorities = memberships.flatMap(m =>
- configStore.lookupPolicyStatements(PolicyKey(m.policySpace, m.policy)).map(p => DomainAuthority(m.domain, p)))
+ configStore.lookupPolicyStatements(PolicyKey(m.policySpace, m.policy)).map(p => SpaceAuthority(m.space, m.domain, p)))
val isRoot = configStore.containsRootUser(identities)
val authorities = domainAuthorities ++ Seq(new SimpleGrantedAuthority("user")) ++ (isRoot match {
case true => Seq(new SimpleGrantedAuthority("root"))
View
92 agent/src/main/scala/net/lshift/diffa/agent/auth/Privilege.scala
@@ -1,43 +1,101 @@
package net.lshift.diffa.agent.auth
+import net.lshift.diffa.kernel.config.system.PolicyStatement
+
/**
* Definitions of the various supported Diffa privileges.
*/
trait Privilege {
/** The name of the privilege **/
def name:String
+
+ /** Determines whether the given statement provides access to the given target for this privilege */
+ def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject):Boolean
+}
+
+case class UserPrivilege(val name:String) extends Privilege {
+ // Target evaluation is handled specially for users
+ def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = false
}
/**
* A privilege that is applied against a space.
*/
-case class SpacePrivilege(name:String) extends Privilege
+class SpacePrivilege(val name:String) extends Privilege {
+ def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = {
+ target match {
+ case st:SpaceTarget => st.space == space
+ case _ => false
+ }
+ }
+}
/**
- * A privilege that is applied against an action.
+ * A privilege that is applied against a pair.
*/
-case class ActionPrivilege(name:String) extends Privilege
+class PairPrivilege(name:String) extends SpacePrivilege(name) {
+ override def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = {
+ super.isValidForTarget(space, stmt, target) && (target match {
+ case pt:PairTarget => stmt.appliesTo("pair", pt.pair)
+ case _ => true // If the target wasn't a pair, then allow
+ })
+ }
+}
/**
- * A privilege that is applied against a pair.
+ * A privilege that is applied against an endpoint.
*/
-case class PairPrivilege(name:String) extends Privilege
+class EndpointPrivilege(name:String) extends SpacePrivilege(name) {
+ override def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = {
+ super.isValidForTarget(space, stmt, target) && (target match {
+ case et:EndpointTarget => stmt.appliesTo("endpoint", et.endpoint)
+ case _ => true // If the target wasn't an endpoint, then allow
+ })
+ }
+}
/**
- * A privilege that is applied against an endpoint.
+ * A privilege that is applied against an action.
*/
-case class EndpointPrivilege(name:String) extends Privilege
+class ActionPrivilege(name:String) extends PairPrivilege(name) {
+ override def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = {
+ super.isValidForTarget(space, stmt, target) && (target match {
+ case at:ActionTarget => stmt.appliesTo("action", at.action)
+ case _ => true // If the target wasn't an action, then allow
+ })
+ }
+}
+
+/**
+ * A privilege that is applied against a report.
+ */
+class ReportPrivilege(name:String) extends PairPrivilege(name) {
+ override def isValidForTarget(space:Long, stmt:PolicyStatement, target:TargetObject) = {
+ super.isValidForTarget(space, stmt, target) && (target match {
+ case rt:ReportTarget => stmt.appliesTo("report", rt.report)
+ case _ => true // If the target wasn't a report, then allow
+ })
+ }
+}
/** Catalogue of privileges supported by Diffa */
object Privileges {
- val READ_DIFFS = PairPrivilege("read-diffs") // Ability to query for differences, access overviews, etc
- val CONFIGURE = SpacePrivilege("configure") // Ability to configure a space
- val INITIATE_SCAN = PairPrivilege("initiate-scan")
- val POST_CHANGE_EVENT = EndpointPrivilege("post-change-event")
- val POST_INVENTORY = EndpointPrivilege("post-inventory")
- val SCAN_STATUS = PairPrivilege("view-scan-status")
- val DIAGNOSTICS = PairPrivilege("view-diagnostics")
- val INVOKE_ACTIONS = ActionPrivilege("invoke-actions")
- val IGNORE_DIFFS = PairPrivilege("ignore-diffs")
- val VIEW_EXPLANATIONS = PairPrivilege("view-explanations")
+ val SPACE_USER = new SpacePrivilege("space-user")
+ val READ_DIFFS = new PairPrivilege("read-diffs") // Ability to query for differences, access overviews, etc
+ val CONFIGURE = new SpacePrivilege("configure") // Ability to configure a space
+ val INITIATE_SCAN = new PairPrivilege("initiate-scan")
+ val CANCEL_SCAN = new PairPrivilege("cancel-scan")
+ val POST_CHANGE_EVENT = new EndpointPrivilege("post-change-event")
+ val POST_INVENTORY = new EndpointPrivilege("post-inventory")
+ val SCAN_STATUS = new PairPrivilege("view-scan-status")
+ val DIAGNOSTICS = new PairPrivilege("view-diagnostics")
+ val INVOKE_ACTIONS = new ActionPrivilege("invoke-actions")
+ val IGNORE_DIFFS = new PairPrivilege("ignore-diffs")
+ val VIEW_EXPLANATIONS = new PairPrivilege("view-explanations")
+ val EXECUTE_REPORT = new ReportPrivilege("execute-report")
+ val VIEW_ACTIONS = new PairPrivilege("view-actions")
+ val VIEW_REPORTS = new PairPrivilege("view-reports")
+ val READ_EVENT_DETAILS = new PairPrivilege("read-event-details")
+
+ val USER_PREFERENCES = new UserPrivilege("user-preferences")
}
View
19 agent/src/main/scala/net/lshift/diffa/agent/auth/TargetObject.scala
@@ -0,0 +1,19 @@
+package net.lshift.diffa.agent.auth
+
+/**
+ * Describes an object that can be the target of a privilege check.
+ */
+trait TargetObject
+class SpaceTarget(val space:Long) extends TargetObject
+class PairTarget(space:Long, val pair:String) extends SpaceTarget(space)
+class EndpointTarget(space:Long, val endpoint:String) extends SpaceTarget(space)
+class ActionTarget(space:Long, pair:String, val action:String) extends PairTarget(space, pair)
+class ReportTarget(space:Long, pair:String, val report:String) extends PairTarget(space, pair)
+
+/**
+ * TODO: This should actually include a reference to the owning pair, but we don't tend to have that in API calls. So
+ * this target provides a typed reference to all locations that need to be corrected.
+ */
+class DiffTarget(space:Long, val evtSeqId:String) extends SpaceTarget(space)
+
+class UserTarget(val username:String) extends TargetObject
View
24 agent/src/main/scala/net/lshift/diffa/agent/auth/UserDetailsAdapter.scala
@@ -55,20 +55,20 @@ class UserDetailsAdapter(val systemConfigStore:SystemConfigStore)
def hasPermission(auth: Authentication, targetDomainObject: AnyRef, permission: AnyRef) = {
permission match {
// Tests to see whether the requesting user is the owner of the requested object
- case "user-preferences" =>
+ case UserPrivilege("user-preferences") =>
/*
os.ten.si.ble [o-sten-suh-buhl]
adjective
1. outwardly appearing as such; professed; pretended: an ostensible cheerfulness concealing sadness.
2.apparent, evident, or conspicuous: the ostensible truth of their theories.
*/
- val ostensibleUsername = targetDomainObject.asInstanceOf[String]
+ val ostensibleUsername = targetDomainObject.asInstanceOf[UserTarget].username
isUserWhoTheyClaimToBe(auth, ostensibleUsername)
// For any other permission, check to see if the user has the privilege, or if they are a root user.
- case permissionString:String =>
- val domain = targetDomainObject.asInstanceOf[String]
- isRoot(auth) || hasDomainPrivilege(auth, domain, permissionString)
+ case privilege:Privilege =>
+ val target = targetDomainObject.asInstanceOf[TargetObject]
+ isRoot(auth) || hasTargetPrivilege(auth, target, privilege)
// Unknown permission request type
@@ -83,7 +83,7 @@ class UserDetailsAdapter(val systemConfigStore:SystemConfigStore)
val isRoot = user.superuser
val memberships = systemConfigStore.listDomainMemberships(user.name)
val domainAuthorities = memberships.flatMap(m =>
- systemConfigStore.lookupPolicyStatements(PolicyKey(m.policySpace, m.policy)).map(p => DomainAuthority(m.domain, p)))
+ systemConfigStore.lookupPolicyStatements(PolicyKey(m.policySpace, m.policy)).map(p => SpaceAuthority(m.space, m.domain, p)))
val authorities = domainAuthorities ++
Seq(new SimpleGrantedAuthority("user"), new UserAuthority(user.name)) ++
(isRoot match {
@@ -102,9 +102,13 @@ class UserDetailsAdapter(val systemConfigStore:SystemConfigStore)
}
}
def isRoot(auth: Authentication) = auth.getAuthorities.find(_.getAuthority == "root").isDefined
- def hasDomainPrivilege(auth: Authentication, domain:String, privilege:String) = auth.getAuthorities.find {
- case DomainAuthority(grantedDomain, grantedPrivilegeStmt) =>
- domain == grantedDomain && privilege == grantedPrivilegeStmt.privilege
+ def hasTargetPrivilege(auth: Authentication, target:TargetObject, privilege:Privilege) = auth.getAuthorities.find {
+ case SpaceAuthority(grantedSpace, grantedDomain, grantedPrivilegeStmt) =>
+ if (grantedPrivilegeStmt.privilege == privilege.name) {
+ privilege.isValidForTarget(grantedSpace, grantedPrivilegeStmt, target)
+ } else {
+ false
+ }
case _ =>
false
}.isDefined
@@ -115,7 +119,7 @@ class UserDetailsAdapter(val systemConfigStore:SystemConfigStore)
}.isDefined
}
-case class DomainAuthority(domain:String, statement:PolicyStatement) extends GrantedAuthority {
+case class SpaceAuthority(space:Long, domain:String, statement:PolicyStatement) extends GrantedAuthority {
def getAuthority = statement + "@" + domain
}
View
35 agent/src/main/scala/net/lshift/diffa/agent/rest/ActionsResource.scala
@@ -21,34 +21,49 @@ import core.UriInfo
import net.lshift.diffa.kernel.frontend.wire.InvocationResult
import net.lshift.diffa.kernel.client.{Actionable, ActionableRequest, ActionsClient}
import net.lshift.diffa.kernel.config.{PairRef, RepairAction}
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{ActionTarget, PairTarget, Privileges}
class ActionsResource(val proxy:ActionsClient,
val space:Long,
- val uriInfo:UriInfo) {
+ val uriInfo:UriInfo,
+ val permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
@GET
@Path("/{pairId}")
@Produces(Array("application/json"))
def listActions(@PathParam("pairId") pairId: String,
- @QueryParam("scope") scope: String): Array[Actionable] = (scope match {
- case RepairAction.ENTITY_SCOPE => proxy.listEntityScopedActions(PairRef(pairId, space))
- case RepairAction.PAIR_SCOPE => proxy.listPairScopedActions(PairRef(pairId, space))
- case _ => proxy.listActions(PairRef(pairId,space))
- }).toArray
+ @QueryParam("scope") scope: String): Array[Actionable] = {
+ ensurePrivilege(permissionEvaluator, Privileges.VIEW_ACTIONS, new PairTarget(space, pairId))
+
+ (scope match {
+ case RepairAction.ENTITY_SCOPE => proxy.listEntityScopedActions(PairRef(pairId, space))
+ case RepairAction.PAIR_SCOPE => proxy.listPairScopedActions(PairRef(pairId, space))
+ case _ => proxy.listActions(PairRef(pairId,space))
+ }).toArray
+ }
@POST
@Path("/{pairId}/{actionId}")
@Produces(Array("application/json"))
def invokeAction(@PathParam("pairId") pairId:String,
- @PathParam("actionId") actionId:String)
- = proxy.invoke(ActionableRequest(pairId, space, actionId, null))
+ @PathParam("actionId") actionId:String) = {
+ ensurePrivilege(permissionEvaluator, Privileges.INVOKE_ACTIONS, new ActionTarget(space, pairId, actionId))
+
+ proxy.invoke(ActionableRequest(pairId, space, actionId, null))
+ }
@POST
@Path("/{pairId}/{actionId}/{entityId}")
@Produces(Array("application/json"))
def invokeAction(@PathParam("pairId") pairId:String,
@PathParam("actionId") actionId:String,
- @PathParam("entityId") entityId:String) : InvocationResult
- = proxy.invoke(ActionableRequest(pairId, space, actionId, entityId))
+ @PathParam("entityId") entityId:String) : InvocationResult = {
+ ensurePrivilege(permissionEvaluator, Privileges.INVOKE_ACTIONS, new ActionTarget(space, pairId, actionId))
+
+ proxy.invoke(ActionableRequest(pairId, space, actionId, entityId))
+ }
}
View
12 agent/src/main/scala/net/lshift/diffa/agent/rest/ChangesResource.scala
@@ -23,19 +23,25 @@ import net.lshift.diffa.schema.servicelimits.ChangeEventRate
import net.lshift.diffa.kernel.limiting.{DomainRateLimiterFactory, ServiceLimiterKey, ServiceLimiterRegistry}
import net.lshift.diffa.participant.common.{InvalidEntityException, ScanEntityValidator}
import net.lshift.diffa.kernel.differencing.EntityValidator
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{EndpointTarget, Privileges}
/**
* Resource allowing participants to provide details of changes that have occurred.
*/
class ChangesResource(changes:Changes, space:Long, rateLimiterFactory: DomainRateLimiterFactory,
- validator: ScanEntityValidator) {
+ validator: ScanEntityValidator, permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
- def this(changes:Changes, space:Long, rateLimiterFactory: DomainRateLimiterFactory) =
- this(changes, space, rateLimiterFactory, EntityValidator)
+ def this(changes:Changes, space:Long, rateLimiterFactory: DomainRateLimiterFactory, permissionEvaluator:PermissionEvaluator) =
+ this(changes, space, rateLimiterFactory, EntityValidator, permissionEvaluator)
@POST
@Path("/{endpoint}")
@Consumes(Array("application/json"))
def submitChange(@PathParam("endpoint") endpoint: String, e:ChangeEvent) = {
+ ensurePrivilege(permissionEvaluator, Privileges.POST_CHANGE_EVENT, new EndpointTarget(space, endpoint))
+
val limiter = ServiceLimiterRegistry.get(
ServiceLimiterKey(ChangeEventRate, Some(space), None),
() => rateLimiterFactory.createRateLimiter(space))
View
9 agent/src/main/scala/net/lshift/diffa/agent/rest/DiagnosticsResource.scala
@@ -20,19 +20,26 @@ import javax.ws.rs.core.Response
import javax.ws.rs._
import net.lshift.diffa.kernel.frontend.Configuration
import net.lshift.diffa.kernel.config.PairRef
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{PairTarget, Privileges}
/**
* Resource providing REST-based access to diagnostic data.
*/
class DiagnosticsResource(val diagnostics: DiagnosticsManager,
val config: Configuration,
- val space:Long) {
+ val space:Long,
+ val permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
@GET
@Path("/{pairKey}/log")
@Produces(Array("application/json"))
def getPairStates(@PathParam("pairKey") pairKey: String, @QueryParam("maxItems") maxItems:java.lang.Integer): Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.DIAGNOSTICS, new PairTarget(space, pairKey))
+
val actualMaxItems = if (maxItems == null) 20 else maxItems.intValue()
val pair = PairRef(pairKey,space)
val events = diagnostics.queryEvents(pair, actualMaxItems)
View
29 agent/src/main/scala/net/lshift/diffa/agent/rest/DifferencesResource.scala
@@ -26,11 +26,16 @@ import org.joda.time.{DateTime, Interval}
import net.lshift.diffa.kernel.differencing.{EventOptions, DifferencesManager}
import javax.servlet.http.HttpServletRequest
import net.lshift.diffa.kernel.config.{DomainConfigStore, PairRef}
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{DiffTarget, PairTarget, Privileges}
class DifferencesResource(val differencesManager: DifferencesManager,
val domainConfigStore:DomainConfigStore,
val space:Long,
- val uriInfo:UriInfo) {
+ val uriInfo:UriInfo,
+ val permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
private val log: Logger = LoggerFactory.getLogger(getClass)
@@ -47,6 +52,8 @@ class DifferencesResource(val differencesManager: DifferencesManager,
@QueryParam("include-ignored") includeIgnored:java.lang.Boolean,
@Context request: Request) = {
+ ensurePrivilege(permissionEvaluator, Privileges.READ_DIFFS, new PairTarget(space, pairKey))
+
try {
val domainVsn = validateETag(request)
@@ -82,9 +89,12 @@ class DifferencesResource(val differencesManager: DifferencesManager,
val domainVsn = validateETag(request)
val requestedAggregates = parseAggregates(servletRequest)
- val aggregates = domainConfigStore.listPairs(space).map(p => {
- p.key -> mapAsJavaMap(processAggregates(PairRef(space = space, name = p.key), requestedAggregates))
- }).toMap
+ val aggregates = domainConfigStore.listPairs(space).
+ filter(p => {
+ hasPrivilege(permissionEvaluator, Privileges.READ_DIFFS, new PairTarget(space, p.key))
+ }).map(p => {
+ p.key -> mapAsJavaMap(processAggregates(PairRef(space = space, name = p.key), requestedAggregates))
+ }).toMap
Response.ok(mapAsJavaMap(aggregates)).tag(domainVsn).build()
}
@@ -93,6 +103,8 @@ class DifferencesResource(val differencesManager: DifferencesManager,
@Path("/aggregates/{pair}")
@Produces(Array("application/json"))
def getAggregates(@PathParam("pair") pair:String, @Context request: Request, @Context servletRequest:HttpServletRequest): Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.READ_DIFFS, new PairTarget(space, pair))
+
val domainVsn = validateETag(request)
val requestedAggregates = parseAggregates(servletRequest)
@@ -105,13 +117,18 @@ class DifferencesResource(val differencesManager: DifferencesManager,
@Path("/events/{evtSeqId}/{participant}")
@Produces(Array("text/plain"))
def getDetail(@PathParam("evtSeqId") evtSeqId:String,
- @PathParam("participant") participant:String) : String =
+ @PathParam("participant") participant:String) : String = {
+ ensurePrivilege(permissionEvaluator, Privileges.READ_EVENT_DETAILS, new DiffTarget(space, evtSeqId))
+
differencesManager.retrieveEventDetail(space, evtSeqId, ParticipantType.withName(participant))
+ }
@DELETE
@Path("/events/{evtSeqId}")
@Produces(Array("application/json"))
def ignoreDifference(@PathParam("evtSeqId") evtSeqId:String):Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.IGNORE_DIFFS, new DiffTarget(space, evtSeqId))
+
val ignored = differencesManager.ignoreDifference(space, evtSeqId).toExternalFormat
Response.ok(ignored).build
@@ -121,6 +138,8 @@ class DifferencesResource(val differencesManager: DifferencesManager,
@Path("/events/{evtSeqId}")
@Produces(Array("application/json"))
def unignoreDifference(@PathParam("evtSeqId") evtSeqId:String):Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.IGNORE_DIFFS, new DiffTarget(space, evtSeqId))
+
val restored = differencesManager.unignoreDifference(space, evtSeqId).toExternalFormat
Response.ok(restored).build
View
52 agent/src/main/scala/net/lshift/diffa/agent/rest/DomainResource.scala
@@ -36,6 +36,8 @@ import net.lshift.diffa.kernel.frontend.EscalationDef
import net.lshift.diffa.kernel.differencing.{DomainDifferenceStore, DifferencesManager}
import net.lshift.diffa.kernel.config.{BreakerHelper, DomainCredentialsManager, User, DomainConfigStore}
import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{SpaceTarget, Privileges, SpacePrivilege}
/**
* The policy is that we will publish spaces as the replacement term for domains
@@ -95,16 +97,16 @@ class DomainResource {
case _ => null
}
- private def withSpace[T](path: String, f: Long => T) = {
- val authentication = SecurityContextHolder.getContext.getAuthentication
- val hasPermission = permissionEvaluator.hasPermission(authentication, path, "domain-user")
- if (hasPermission) {
- val space = systemConfigStore.lookupSpaceByPath(path)
- f(space.id)
- }
- else {
- throw new WebApplicationException(403)
- }
+ private def withSpace[T](path: String, privilege:SpacePrivilege, f: Long => T) = {
+ val space = systemConfigStore.lookupSpaceByPath(path)
+
+ ensurePrivilege(permissionEvaluator, privilege, new SpaceTarget(space.id))
+ f(space.id)
+ }
+
+ private def withSpace[T <: IndividuallySecuredResource](path:String, f: Long => T) = {
+ val space = systemConfigStore.lookupSpaceByPath(path)
+ f(space.id)
}
@@ -124,7 +126,7 @@ class DomainResource {
@PathParam("space") space:String,
@PathParam("id") id:String,
e: EscalationDef) = {
- withSpace(space, (spaceId:Long) => {
+ withSpace(space, Privileges.CONFIGURE, (spaceId:Long) => {
config.createOrUpdateEscalation(spaceId, id, e)
resourceCreated(e.name, uri)
})
@@ -135,7 +137,7 @@ class DomainResource {
def deleteEscalation(@PathParam("space") space:String,
@PathParam("name") name: String,
@PathParam("pairKey") pairKey: String) = {
- withSpace(space, (id:Long) => {
+ withSpace(space, Privileges.CONFIGURE, (id:Long) => {
config.deleteEscalation(id, name, pairKey)
})
}
@@ -147,50 +149,52 @@ class DomainResource {
@Path("/{space:.+}/config")
def getConfigResource(@Context uri:UriInfo,
@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new ConfigurationResource(config, breakers, id, getCurrentUser(space), uri))
+ withSpace(space, Privileges.CONFIGURE, (id:Long) => new ConfigurationResource(config, breakers, id, getCurrentUser(space), uri))
@Path("/{space:.+}/credentials")
def getCredentialsResource(@Context uri:UriInfo,
@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new CredentialsResource(credentialsManager, id, uri))
+ withSpace(space, Privileges.CONFIGURE, (id:Long) => new CredentialsResource(credentialsManager, id, uri))
@Path("/{space:.+}/diffs")
def getDifferencesResource(@Context uri:UriInfo,
@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new DifferencesResource(differencesManager, domainConfigStore, id, uri))
+ withSpace(space, (id:Long) => new DifferencesResource(differencesManager, domainConfigStore, id, uri, permissionEvaluator))
@Path("/{space:.+}/escalations")
def getEscalationsResource(@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new EscalationsResource(config, diffStore, id))
+ withSpace(space, Privileges.CONFIGURE, (id:Long) => new EscalationsResource(config, diffStore, id))
@Path("/{space:.+}/actions")
def getActionsResource(@Context uri:UriInfo,
@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new ActionsResource(actionsClient, id, uri))
+ withSpace(space, (id:Long) => new ActionsResource(actionsClient, id, uri, permissionEvaluator))
@Path("/{space:.+}/reports")
def getReportsResource(@Context uri:UriInfo,
@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new ReportsResource(domainConfigStore, reports, id, uri))
+ withSpace(space, (id:Long) => new ReportsResource(domainConfigStore, reports, id, uri, permissionEvaluator))
@Path("/{space:.+}/diagnostics")
def getDiagnosticsResource(@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new DiagnosticsResource(diagnosticsManager, config, id))
+ withSpace(space, (id:Long) => new DiagnosticsResource(diagnosticsManager, config, id, permissionEvaluator))
@Path("/{space:.+}/scanning")
def getScanningResource(@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new ScanningResource(pairPolicyClient, config, domainConfigStore, diagnosticsManager, id, getCurrentUser(space)))
+ withSpace(space, (id:Long) => new ScanningResource(pairPolicyClient, config, domainConfigStore, diagnosticsManager, id, getCurrentUser(space), permissionEvaluator))
@Path("/{space:.+}/changes")
def getChangesResource(@PathParam("space") space:String) = {
- withSpace(space, (id:Long) => new ChangesResource(changes, id, changeEventRateLimiterFactory))
+ withSpace(space, (id:Long) => new ChangesResource(changes, id, changeEventRateLimiterFactory, permissionEvaluator))
}
@Path("/{space:.+}/inventory")
def getInventoryResource(@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new InventoryResource(changes, domainConfigStore, id))
+ withSpace(space, (id:Long) => new InventoryResource(changes, domainConfigStore, id, permissionEvaluator))
@Path("/{space:.+}/limits")
def getLimitsResource(@PathParam("space") space:String) =
- withSpace(space, (id:Long) => new DomainServiceLimitsResource(config, id))
-}
+ withSpace(space, Privileges.CONFIGURE, (id:Long) => new DomainServiceLimitsResource(config, id))
+}
+
+trait IndividuallySecuredResource
View
10 agent/src/main/scala/net/lshift/diffa/agent/rest/InventoryResource.scala
@@ -25,11 +25,15 @@ import com.sun.jersey.core.util.MultivaluedMapImpl
import net.lshift.diffa.client.RequestBuildingHelper
import java.net.URLEncoder
import net.lshift.diffa.participant.scanning.{ScanRequest, AggregationBuilder, ConstraintsBuilder}
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.auth.{EndpointTarget, Privileges}
/**
* Resource allowing participants to provide bulk details of their current status.
*/
-class InventoryResource(changes:Changes, configStore:DomainConfigStore, space:Long) {
+class InventoryResource(changes:Changes, configStore:DomainConfigStore, space:Long, permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
@GET
@Path("/{endpoint}")
def startInventory(@PathParam("endpoint") endpoint: String):Response = startInventory(endpoint, null)
@@ -37,6 +41,8 @@ class InventoryResource(changes:Changes, configStore:DomainConfigStore, space:Lo
@GET
@Path("/{endpoint}/{view}")
def startInventory(@PathParam("endpoint") endpoint: String, @PathParam("view") view:String):Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.POST_INVENTORY, new EndpointTarget(space, endpoint))
+
val requests = changes.startInventory(space, endpoint, if (view != null) Some(view) else None)
Response.status(Response.Status.OK).
@@ -54,6 +60,8 @@ class InventoryResource(changes:Changes, configStore:DomainConfigStore, space:Lo
@Path("/{endpoint}/{view}")
@Consumes(Array("text/csv"))
def submitInventory(@PathParam("endpoint") endpoint: String, @PathParam("view") view: String, @Context request:HttpServletRequest, content:ScanResultList):Response = {
+ ensurePrivilege(permissionEvaluator, Privileges.POST_INVENTORY, new EndpointTarget(space, endpoint))
+
val constraintsBuilder = new ConstraintsBuilder(request)
val aggregationBuilder = new AggregationBuilder(request)
View
22 agent/src/main/scala/net/lshift/diffa/agent/rest/PermissionUtils.scala
@@ -0,0 +1,22 @@
+package net.lshift.diffa.agent.rest
+
+import javax.ws.rs.WebApplicationException
+import org.springframework.security.access.PermissionEvaluator
+import org.springframework.security.core.context.SecurityContextHolder
+import net.lshift.diffa.agent.auth.{TargetObject, Privilege}
+
+/**
+ * Utilities for ensuring that users have appropriate permissions.
+ */
+object PermissionUtils {
+ def ensurePrivilege(permissionEvaluator:PermissionEvaluator, privilege:Privilege, targetObj:TargetObject) {
+ if (!hasPrivilege(permissionEvaluator, privilege, targetObj)) {
+ throw new WebApplicationException(403)
+ }
+ }
+
+ def hasPrivilege(permissionEvaluator:PermissionEvaluator, privilege:Privilege, targetObj:TargetObject) = {
+ val authentication = SecurityContextHolder.getContext.getAuthentication
+ permissionEvaluator.hasPermission(authentication, targetObj, privilege)
+ }
+}
View
16 agent/src/main/scala/net/lshift/diffa/agent/rest/ReportsResource.scala
@@ -22,24 +22,34 @@ import net.lshift.diffa.kernel.reporting.ReportManager
import net.lshift.diffa.kernel.config.{DomainConfigStore, PairRef}
import net.lshift.diffa.kernel.frontend.PairReportDef
import scala.collection.JavaConversions._
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{PairTarget, Privileges}
class ReportsResource(val config:DomainConfigStore,
val reports:ReportManager,
val space:Long,
- val uriInfo:UriInfo) {
+ val uriInfo:UriInfo,
+ val permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
@GET
@Path("/{pairId}")
@Produces(Array("application/json"))
def listReports(@PathParam("pairId") pairId: String,
- @QueryParam("scope") scope: String): Array[PairReportDef] =
- config.getPairDef(space, pairId).reports.toSeq.toArray[PairReportDef]
+ @QueryParam("scope") scope: String): Array[PairReportDef] = {
+ ensurePrivilege(permissionEvaluator, Privileges.VIEW_REPORTS, new PairTarget(space, pairId))
+
+ config.getPairDef(space, pairId).reports.toSeq.toArray[PairReportDef]
+ }
@POST
@Path("/{pairId}/{reportId}")
@Produces(Array("application/json"))
def executeReport(@PathParam("pairId") pairId:String,
@PathParam("reportId") reportId:String) = {
+ ensurePrivilege(permissionEvaluator, Privileges.EXECUTE_REPORT, new PairTarget(space, pairId))
+
reports.executeReport(PairRef(name = pairId, space = space), reportId)
Response.status(Response.Status.OK).build
}
View
19 agent/src/main/scala/net/lshift/diffa/agent/rest/ScanningResource.scala
@@ -24,26 +24,39 @@ import net.lshift.diffa.kernel.diag.DiagnosticsManager
import net.lshift.diffa.kernel.config.{PairRef, DomainConfigStore}
import org.slf4j.{LoggerFactory, Logger}
import net.lshift.diffa.kernel.util.AlertCodes._
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{SpaceTarget, PairTarget, Privileges}
class ScanningResource(val pairPolicyClient:PairPolicyClient,
val config:Configuration,
val domainConfigStore:DomainConfigStore,
val diagnostics:DiagnosticsManager,
val space:Long,
- val currentUser:String) {
+ val currentUser:String,
+ val permissionEvaluator:PermissionEvaluator)
+ extends IndividuallySecuredResource {
private val log: Logger = LoggerFactory.getLogger(getClass)
@GET
@Path("/states")
def getAllPairStates = {
+ // Ensure that we're allowed to see at least some pair states
+ ensurePrivilege(permissionEvaluator, Privileges.SCAN_STATUS, new SpaceTarget(space))
+
val states = diagnostics.retrievePairScanStatesForDomain(space)
- Response.ok(scala.collection.JavaConversions.mapAsJavaMap(states)).build
+ val filteredStates = states.filter {
+ case (pair, state) => hasPrivilege(permissionEvaluator, Privileges.SCAN_STATUS, new PairTarget(space, pair))
+ }
+
+ Response.ok(scala.collection.JavaConversions.mapAsJavaMap(filteredStates)).build
}
@POST
@Path("/pairs/{pairKey}/scan")
def startScan(@PathParam("pairKey") pairKey:String, @FormParam("view") view:String) = {
+ ensurePrivilege(permissionEvaluator, Privileges.INITIATE_SCAN, new PairTarget(space, pairKey))
val ref = PairRef(pairKey, space)
@@ -77,6 +90,8 @@ class ScanningResource(val pairPolicyClient:PairPolicyClient,
@DELETE
@Path("/pairs/{pairKey}/scan")
def cancelScanning(@PathParam("pairKey") pairKey:String) = {
+ ensurePrivilege(permissionEvaluator, Privileges.CANCEL_SCAN, new PairTarget(space, pairKey))
+
pairPolicyClient.cancelScans(PairRef(pairKey, space))
Response.status(Response.Status.OK).build
}
View
18 agent/src/main/scala/net/lshift/diffa/agent/rest/UsersResource.scala
@@ -24,14 +24,18 @@ import net.lshift.diffa.kernel.config.system.CachedSystemConfigStore
import com.sun.jersey.api.NotFoundException
import net.lshift.diffa.kernel.preferences.{UserPreferencesStore, FilteredItemType}
import net.lshift.diffa.kernel.config.PairRef
+import org.springframework.security.access.PermissionEvaluator
+import net.lshift.diffa.agent.rest.PermissionUtils._
+import net.lshift.diffa.agent.auth.{SpaceTarget, UserTarget, Privileges}
+
@Path("/users/{user}/{domain}")
@Component
-@PreAuthorize("hasPermission(#user, 'user-preferences') and hasPermission(#domain, 'domain-user')")
class UsersResource {
@Autowired var systemConfigStore:CachedSystemConfigStore = null
@Autowired var userPreferences:UserPreferencesStore = null
+ @Autowired var permissionEvaluator:PermissionEvaluator = null
@GET
@Path("/filter/{itemType}")
@@ -40,7 +44,11 @@ class UsersResource {
@PathParam("domain") domain:String,
@PathParam("itemType") itemType:String,
@Context request: Request) = {
+ ensurePrivilege(permissionEvaluator, Privileges.USER_PREFERENCES, new UserTarget(user))
+
val space = systemConfigStore.lookupSpaceByPath(domain)
+ ensurePrivilege(permissionEvaluator, Privileges.SPACE_USER, new SpaceTarget(space.id))
+
val filterType = getFilterType(itemType)
val filters = userPreferences.listFilteredItems(space.id, user, filterType).toArray
@@ -64,7 +72,11 @@ class UsersResource {
@PathParam("domain") domain:String,
@PathParam("pair") pair:String,
@PathParam("itemType") itemType:String) {
+ ensurePrivilege(permissionEvaluator, Privileges.USER_PREFERENCES, new UserTarget(user))
+
val space = systemConfigStore.lookupSpaceByPath(domain)
+ ensurePrivilege(permissionEvaluator, Privileges.SPACE_USER, new SpaceTarget(space.id))
+
val filterType = getFilterType(itemType)
userPreferences.createFilteredItem(PairRef(pair,space.id), user, filterType)
}
@@ -75,7 +87,11 @@ class UsersResource {
@PathParam("domain") domain:String,
@PathParam("pair") pair:String,
@PathParam("itemType") itemType:String) {
+ ensurePrivilege(permissionEvaluator, Privileges.USER_PREFERENCES, new UserTarget(user))
+
val space = systemConfigStore.lookupSpaceByPath(domain)
+ ensurePrivilege(permissionEvaluator, Privileges.SPACE_USER, new SpaceTarget(space.id))
+
val filterType = getFilterType(itemType)
userPreferences.removeFilteredItem(PairRef(pair,space.id), user, filterType)
}
View
2  agent/src/test/scala/net/lshift/diffa/agent/itest/auth/ExternalAuthTest.scala
@@ -96,7 +96,7 @@ class ExternalAuthTest {
// Create an internal user, and make it a member of a domain. We'll use our new external superuser to do it.
externalAdminUsersClient.declareUser(UserDef(name = "External User", email = "external-user@diffa.io", superuser = false, external = true))
- externalAdminDomainConfigClient.makeDomainMember("External User", "User")
+ externalAdminDomainConfigClient.makeDomainMember("External User", "Admin")
// Try to make a call within the domain for the external user
assertEquals(0, externalUserScanningClient.getScanStatus.size)
View
2  agent/src/test/scala/net/lshift/diffa/agent/itest/config/MembershipTest.scala
@@ -85,7 +85,7 @@ class MembershipTest extends IsolatedDomainTest {
def shouldBeAbleToAccessDomainConfigurationWhenDomainMember() {
securityClient.declareUser(UserDef(username,email,false,password))
- configClient.makeDomainMember(username, "User")
+ configClient.makeDomainMember(username, "Admin")
userConfigClient.listDomainMembers
}
View
5 kernel/src/main/scala/net/lshift/diffa/kernel/config/system/SystemConfigStore.scala
@@ -101,4 +101,7 @@ trait SystemConfigStore {
}
case class PolicyKey(space:java.lang.Long, name:String)
-case class PolicyStatement(privilege:String, target:String)
+case class PolicyStatement(privilege:String, target:String) {
+ // TODO: Breakdown the target string, and allow matching against components
+ def appliesTo(objType:String, objName:String) = true
+}
View
12 schema/src/main/scala/net/lshift/diffa/schema/migrations/steps/Step0050.scala
@@ -42,15 +42,17 @@ object Step0050 extends VerifiedMigrationStep {
addForeignKey("fk_plcy_stmts_priv", "privilege", "privileges", "name")
definePrivileges(migration,
- "domain-user", "read-diffs", "configure", "initiate-scan", "post-change-event",
- "post-inventory", "view-scan-status", "view-diagnostics", "invoke-actions", "ignore-diffs", "view-explanations")
+ "space-user", "read-diffs", "configure", "initiate-scan", "post-change-event",
+ "post-inventory", "view-scan-status", "view-diagnostics", "invoke-actions", "ignore-diffs", "view-explanations",
+ "execute-report", "view-actions")
// Replacement policy for indicating a domain user
- createPolicy(migration, "0", "User", "domain-user")
+ createPolicy(migration, "0", "User", "space-user")
// Full-access admin policy
- createPolicy(migration, "0", "Admin", "read-diffs", "configure", "initiate-scan", "post-change-event",
- "post-inventory", "view-scan-status", "view-diagnostics", "invoke-actions", "ignore-diffs", "view-explanations")
+ createPolicy(migration, "0", "Admin", "space-user", "read-diffs", "configure", "initiate-scan", "post-change-event",
+ "post-inventory", "view-scan-status", "view-diagnostics", "invoke-actions", "ignore-diffs", "view-explanations",
+ "execute-report", "view-actions")
// We can no longer create a foreign key based purely upon the user being a member of the space. Instead, just
// ensure the user exists.
Please sign in to comment.
Something went wrong with that request. Please try again.