├── .gitignore
├── src
├── main
│ ├── scala
│ │ └── stapl
│ │ │ └── core
│ │ │ ├── dsl
│ │ │ ├── package.scala
│ │ │ └── DSL.scala
│ │ │ ├── pdp
│ │ │ ├── Modules.scala
│ │ │ ├── TimestampGenerator.scala
│ │ │ ├── ObligationService.scala
│ │ │ ├── RequestCtx.scala
│ │ │ ├── AttributeFinder.scala
│ │ │ ├── RemoteEvaluator.scala
│ │ │ ├── PDP.scala
│ │ │ └── EvaluationCtx.scala
│ │ │ ├── BasicPolicy.scala
│ │ │ ├── Exceptions.scala
│ │ │ ├── Result.scala
│ │ │ ├── DurationBuilder.scala
│ │ │ ├── util
│ │ │ └── SynchronousKeptPromise.scala
│ │ │ ├── parser
│ │ │ └── PolicyParser.scala
│ │ │ ├── Expressions.scala
│ │ │ ├── Attributes.scala
│ │ │ ├── Obligations.scala
│ │ │ ├── AttributeContainer.scala
│ │ │ ├── Value.scala
│ │ │ ├── Operations.scala
│ │ │ ├── package.scala
│ │ │ ├── CombinationAlgorithms.scala
│ │ │ ├── Policy.scala
│ │ │ ├── Types.scala
│ │ │ └── ConcreteValues.scala
│ └── resources
│ │ └── logback.xml
└── test
│ └── scala
│ └── stapl
│ └── core
│ └── pdp
│ ├── RemoteTest.scala
│ └── PolicyIdTest.scala
├── header.txt
├── pom.xml
├── resources
└── policies
│ ├── ehealth-natural.stapl
│ └── ehealth.stapl
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .*
2 | *.class
3 | target/
4 | *~
5 | build.sbt
6 | project
7 | stapl-core.iml
8 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/dsl/package.scala:
--------------------------------------------------------------------------------
1 | package stapl.core
2 |
3 | package object dsl {
4 |
5 | implicit def obligationAction2ObligationActionWithOn(oa: ObligationAction): ObligationActionWithOn = new ObligationActionWithOn(oa)
6 |
7 | }
--------------------------------------------------------------------------------
/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/Modules.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.pdp
2 |
3 | /**
4 | * A trait providing utilities for adding modules.
5 | */
6 | trait Modules[T] {
7 |
8 | private var _modules: List[T] = Nil
9 |
10 | def modules : List[T] = _modules
11 | /*def modules_=(modules: List[T]) {
12 | _modules = modules
13 | }*/
14 |
15 | /**
16 | * Add a module.
17 | */
18 | def addModule(module: T) {
19 | _modules = module :: _modules
20 | }
21 |
22 | /**
23 | * Short notation for adding modules.
24 | */
25 | def +=(module: T) = addModule(module)
26 | }
--------------------------------------------------------------------------------
/header.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 | Administrative Contact: dnet-project-office@cs.kuleuven.be
16 | Technical Contact: maarten.decat@cs.kuleuven.be
17 | Author: maarten.decat@cs.kuleuven.be
18 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/BasicPolicy.scala:
--------------------------------------------------------------------------------
1 | package stapl.core
2 |
3 | /**
4 | * A simple base class to extend from and start writing policies.
5 | * Extending this class imports subject, resource, action and env into
6 | * your scope, avoiding writing this boiler plate code yourself.
7 | *
8 | * Example: see staple.core.examples.PolicyFromReadMe
9 | */
10 | trait BasicPolicy {
11 |
12 | // repeat the definitions from stapl.core so that we do not work
13 | // on a single subject/resource/action/environment object
14 | val (subject, action, resource, environment) = BasicPolicy.containers
15 |
16 | }
17 |
18 |
19 | object BasicPolicy {
20 |
21 | /**
22 | * USAGE: val (subject, action, resource, environment) = BasicPolicy.containers
23 | */
24 | def containers = {
25 | val subject = new SubjectAttributeContainer
26 | subject.id = SimpleAttribute(String)
27 | val resource = new ResourceAttributeContainer
28 | resource.id = SimpleAttribute(String)
29 | val action = new ActionAttributeContainer
30 | action.id = SimpleAttribute(String)
31 | val environment = new EnvironmentAttributeContainer
32 |
33 | (subject, action, resource, environment)
34 | }
35 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/TimestampGenerator.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.pdp
2 |
3 | import java.util.concurrent.atomic.AtomicLong
4 |
5 | /**
6 | * Class responsible for generating a globally unique timestamp. These timestamps
7 | * are longs that have an ascending order. Notice that these timestamps do not
8 | * represent actual time, but logical time. *
9 | */
10 | trait TimestampGenerator {
11 |
12 | /**
13 | * Returns a unique timestamp that is guaranteed to be higher than all previously
14 | * generated timestamps.
15 | */
16 | def getTimestamp(): String
17 | }
18 |
19 | /**
20 | * Simple implementation of a timing server.
21 | * Not a robust implementation for distributed scenarios: it is hard to replace it
22 | * by another timing server because the count is not known externally.
23 | */
24 | class SimpleTimestampGenerator extends TimestampGenerator {
25 |
26 | private val counter = new AtomicLong(0)
27 |
28 | /**
29 | * Simple implementation using an internal counter. This leads to a possible
30 | * exception in uniqueness in case of long roll-over, but this is assumed to fall
31 | * outside the expected lifespan of any request that might still be executing system-wide.
32 | */
33 | override def getTimestamp(): String = counter.incrementAndGet().toString
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Exceptions.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | class TypeCheckException(message: String) extends RuntimeException(message) {
23 |
24 | def this(found: AttributeType, expected: AttributeType) = this(s"Found AttributeType '$found', but expected AttributeType '$expected'.")
25 | }
26 |
27 | class AttributeNotFoundException(evaluationId: String, entityId: String, attribute: Attribute) extends RuntimeException(s"[Evaluation $evaluationId] $attribute wasn't found for entity $entityId.")
28 |
29 | class AttributeDoesNotExistException(name: String) extends RuntimeException(s"No attribute with name '$name' has been defined.")
30 |
31 | class RemotePolicyNotFoundException(policyId: String) extends RuntimeException(s"The remote policy with id $policyId wasn't found.")
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Result.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | /**
23 | * Class used for representing the result of a policy evaluation by a PDP.
24 | * A result contains a decision and possibly obligation actions to be fulfilled.
25 | */
26 | case class Result(val decision: Decision,
27 | val obligationActions: List[ConcreteObligationAction] = List.empty,
28 | val employedAttributes: Map[Attribute,ConcreteValue] = Map())
29 |
30 | /**
31 | * Trait for representing a Decision ( = Permit, Deny or NotApplicable).
32 | */
33 | sealed trait Decision
34 |
35 | /**
36 | * Trait for representing the Effect of a Rule.
37 | * Only two Decisions are also Effects: Permit and Deny
38 | */
39 | sealed trait Effect extends Decision {
40 |
41 | /**
42 | * Returns the reverse of this effect: Permit -> Deny and Deny -> Permit.
43 | */
44 | def reverse(): Effect = this match {
45 | case Permit => Deny
46 | case Deny => Permit
47 | }
48 | }
49 |
50 | case object Permit extends Effect
51 |
52 | case object Deny extends Effect
53 |
54 | case object NotApplicable extends Decision
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/ObligationService.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.pdp
2 |
3 | import scala.annotation.tailrec
4 | import stapl.core.LogObligationAction
5 | import stapl.core.LogObligationAction
6 | import stapl.core.LogObligationAction
7 | import grizzled.slf4j.Logging
8 | import stapl.core.ConcreteObligationAction
9 | import stapl.core.ConcreteLogObligationAction
10 |
11 | /**
12 | * Class used for representing an obligation service. An obligation service
13 | * is used by a PDP to fulfill obligations. An obligation service consists
14 | * of multiple ObligationServiceModules that each can fulfill some specific
15 | * obligations.
16 | */
17 | class ObligationService extends Modules[ObligationServiceModule] {
18 |
19 | /**
20 | * Tries to fulfill the given ObligationAction using its
21 | * ObligationServiceModules and returns whether a module
22 | * was able to fulfill the obligation. More precisely, this method
23 | * iterates over all modules and returns when the first module has
24 | * handled the ObligationAction.
25 | */
26 | def fulfill(obl: ConcreteObligationAction): Boolean = {
27 | @tailrec
28 | def fulfill(modules: List[ObligationServiceModule]): Boolean = modules match {
29 | case module :: tail => module.fulfill(obl) match {
30 | case true => true
31 | case false => fulfill(tail) // continue
32 | }
33 | case Nil => false
34 | }
35 | fulfill(modules)
36 | }
37 | }
38 |
39 | /**
40 | * The general interface of an obligation service module.
41 | */
42 | trait ObligationServiceModule {
43 |
44 | /**
45 | * Tries to fulfill the given ObligationAction and returns whether
46 | * this succeeded.
47 | */
48 | def fulfill(obl: ConcreteObligationAction): Boolean
49 | }
50 |
51 | class LogObligationServiceModule extends ObligationServiceModule with Logging {
52 |
53 | override def fulfill(obl: ConcreteObligationAction) = {
54 | // we only support LogObligationActions
55 | obl match {
56 | case log: ConcreteLogObligationAction =>
57 | info(s"Log obligation: ${log.msg}")
58 | true
59 | case _ => false
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/DurationBuilder.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import org.joda.time.Period
23 |
24 | class DurationBuilder(val number: Int) extends AnyVal {
25 |
26 | def years = Years(number)
27 |
28 | def months = Months(number)
29 |
30 | def days = Days(number)
31 |
32 | def hours = Hours(number)
33 |
34 | def minutes = Minutes(number)
35 |
36 | def seconds = Seconds(number)
37 |
38 | def millis = Millis(number)
39 | }
40 |
41 | object Years {
42 | def apply(number: Int): Duration = new DayDurationImpl(Period.years(number))
43 | }
44 |
45 | object Months {
46 | def apply(number: Int): Duration = new DayDurationImpl(Period.months(number))
47 | }
48 |
49 | object Days {
50 | def apply(number: Int): Duration = new DayDurationImpl(Period.days(number))
51 | }
52 |
53 | object Hours {
54 | def apply(number: Int): Duration = new TimeDurationImpl(Period.hours(number))
55 | }
56 |
57 | object Minutes {
58 | def apply(number: Int): Duration = new TimeDurationImpl(Period.minutes(number))
59 | }
60 |
61 | object Seconds {
62 | def apply(number: Int): Duration = new TimeDurationImpl(Period.seconds(number))
63 | }
64 |
65 | object Millis {
66 | def apply(number: Int): Duration = new TimeDurationImpl(Period.millis(number))
67 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/util/SynchronousKeptPromise.scala:
--------------------------------------------------------------------------------
1 | package scala.concurrent.impl
2 |
3 | import scala.concurrent.ExecutionContext
4 | import scala.util.{ Try, Success, Failure }
5 | import scala.concurrent.duration.Duration
6 | import scala.concurrent.CanAwait
7 | import java.util.concurrent.ExecutionException
8 |
9 | /**
10 | * An implementation of a KeptPromise that executes callbacks, map and flatMap synchronously,
11 | * i.e., in the current thread. This is useful for policy evaluation since the callbacks are all
12 | * very low-latency and we want to avoid the overhead of creating and scheduling new runnables.
13 | *
14 | * Most code copied from KeptPromise since we cannot inherit from this class.
15 | *
16 | * NOTE: THIS CODE IS NEVER USED BECAUSE THE PERFORMANCE TESTS EVENTUALLY SHOWED THAT THE
17 | * OVERHEAD OF CREATING THE NEW RUNNABLES IS NOT STATISTICALLY SIGNIFANT. Still keeping
18 | * this code in the repo for future reference.
19 | */
20 | class SynchronousKeptPromise[T](suppliedValue: Try[T]) extends Promise[T] {
21 |
22 | private def resolveTry[T](source: Try[T]): Try[T] = source match {
23 | case Failure(t) => resolver(t)
24 | case _ => source
25 | }
26 |
27 | private def resolver[T](throwable: Throwable): Try[T] = throwable match {
28 | case t: scala.runtime.NonLocalReturnControl[_] => Success(t.value.asInstanceOf[T])
29 | case t: scala.util.control.ControlThrowable => Failure(new ExecutionException("Boxed ControlThrowable", t))
30 | case t: InterruptedException => Failure(new ExecutionException("Boxed InterruptedException", t))
31 | case e: Error => Failure(new ExecutionException("Boxed Error", e))
32 | case t => Failure(t)
33 | }
34 |
35 | val value = Some(resolveTry(suppliedValue))
36 |
37 | override def isCompleted: Boolean = true
38 |
39 | def tryComplete(value: Try[T]): Boolean = false
40 |
41 | def onComplete[U](func: Try[T] => U)(implicit executor: ExecutionContext): Unit = {
42 | val completedAs = value.get
43 | // this is the difference: just call the callback synchronously without scheduling
44 | // it as a separate runnable
45 | // Note that this method is called by map, onSuccess etc, so this should be enough
46 | func(completedAs)
47 | }
48 |
49 | def ready(atMost: Duration)(implicit permit: CanAwait): this.type = this
50 |
51 | def result(atMost: Duration)(implicit permit: CanAwait): T = value.get.get
52 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/parser/PolicyParser.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.parser
2 |
3 | import scala.tools.nsc.Settings
4 | import scala.tools.nsc.interpreter.IMain
5 | import stapl.core.AbstractPolicy
6 |
7 | /**
8 | * A class for parsing policies dynamically from strings.
9 | * This parser employs the Scala interpreter.
10 | */
11 | class PolicyParser {
12 | val settings = new Settings
13 | settings.usejavacp.value = true
14 | settings.nowarnings.value = true
15 | val interpreter = new IMain(settings)
16 | interpreter.beQuietDuring({
17 | interpreter.addImports(
18 | "stapl.core.{subject => _, resource => _, action => _, _}",
19 | "stapl.core.templates._")
20 | })
21 |
22 | /**
23 | * Add another import to the Scala interpreter employed by this parser.
24 | */
25 | def addImport(i: String) = {
26 | interpreter.beQuietDuring({
27 | interpreter.addImports(i)
28 | })
29 | }
30 |
31 | /**
32 | * Parse the given policy string and return the resulting policy.
33 | *
34 | * @param policyString: String
35 | * The string containing the STAPL policy. This policy should not
36 | * contain attribute definitions and should just contain the policy
37 | * specification, not an assignment to a val.
38 | * For example: policyString = "Policy(...) := ..."
39 | *
40 | * @throws RuntimeException
41 | * When the parser did not success in getting
42 | * an abstract policy from the given string.
43 | */
44 | def parse(policyString: String): AbstractPolicy = {
45 | val completePolicy = s"val policy = $policyString"
46 | interpreter.beQuietDuring({
47 | interpreter.interpret(completePolicy)
48 | })
49 |
50 | val somePolicy = interpreter.valueOfTerm("policy")
51 |
52 | somePolicy match {
53 | case None => throw new RuntimeException("Could not load policy from given policyString (returned None)")
54 | case Some(policy: AbstractPolicy) => policy
55 | case _ => throw new RuntimeException("Could not load policy from given policyString (result was not an AbstractPolicy)")
56 | }
57 | }
58 |
59 | /**
60 | * Parse the contents of the given file as a policy string and return
61 | * the resulting policy.
62 | *
63 | * More details: see parse(policyString: String)
64 | */
65 | def parseFile(path: String): AbstractPolicy = {
66 | val source = io.Source.fromFile(path)
67 | val policyString = source.mkString
68 | source.close
69 | parse(policyString)
70 | }
71 | }
--------------------------------------------------------------------------------
/src/test/scala/stapl/core/pdp/RemoteTest.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import org.junit.Before
23 | import org.junit.BeforeClass
24 | import org.junit.Test
25 | import org.junit.Assert._
26 | import org.scalatest.junit.AssertionsForJUnit
27 | import stapl.core._
28 |
29 | object RemoteTest {
30 |
31 | @BeforeClass def setup() {
32 | // nothing to do
33 | }
34 | }
35 | /**
36 | * Some tests about the handling of remote policies.
37 | * There are no real remote policies involved in these tests.
38 | */
39 | class RemoteTest extends AssertionsForJUnit {
40 |
41 | // a RemoteEvaluator with a stub module
42 | val evaluator = new RemoteEvaluator
43 | evaluator += new RemoteEvaluatorModule{
44 | override def findAndEvaluate(policyId: String, ctx: EvaluationCtx) = policyId match {
45 | case "always-permit-policy" => Some(Result(Permit))
46 | case "always-deny-policy" => Some(Result(Deny))
47 | case _ => None
48 | }
49 |
50 | override def findAndIsApplicable(policyId: String, ctx: EvaluationCtx) = ??? // shouldn't be called anyway
51 | }
52 |
53 | @Before def setup() {
54 | // nothing to do
55 | }
56 |
57 | @Test def testPermit() {
58 | val policy = RemotePolicy("always-permit-policy")
59 |
60 | val pdp = new PDP(policy, evaluator)
61 | assertEquals(Result(Permit), pdp.evaluate("subjectId", "actionId", "resourceId"))
62 | }
63 |
64 | @Test def testDeny() {
65 | val policy = RemotePolicy("always-deny-policy")
66 |
67 | val pdp = new PDP(policy, evaluator)
68 | assertEquals(Result(Deny), pdp.evaluate("subjectId", "actionId", "resourceId"))
69 | }
70 |
71 | @Test def testCannotFindPolicy() {
72 | intercept[RemotePolicyNotFoundException] {
73 | val policy = RemotePolicy("unknown-policy")
74 |
75 | val pdp = new PDP(policy, evaluator)
76 | pdp.evaluate("subjectId", "actionId", "resourceId")
77 | }
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/src/test/scala/stapl/core/pdp/PolicyIdTest.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import org.junit.Before
23 | import org.junit.BeforeClass
24 | import org.junit.Test
25 | import org.junit.Assert._
26 | import org.scalatest.junit.AssertionsForJUnit
27 | import stapl.core._
28 |
29 | object PolicyIdTest {
30 |
31 | @BeforeClass def setup() {
32 | // nothing to do
33 | }
34 | }
35 | /**
36 | * Some tests to see whether policy ids are generated correctly.
37 | */
38 | class PolicyIdTest extends AssertionsForJUnit{
39 |
40 | import stapl.core.dsl._
41 |
42 | @Before def setup() {
43 | // nothing to do
44 | }
45 |
46 | @Test def testFqidOfRule() {
47 | val rule1 = Rule("rule1") := deny
48 |
49 | assertEquals("rule1", rule1.fqid)
50 | }
51 |
52 | @Test def testFqidOfSimplePolicy() {
53 | val rule1 = Rule("rule1") := deny
54 | val rule2 = Rule("rule2") := deny
55 |
56 | val policy1 =
57 | Policy("policy1") := apply PermitOverrides to (
58 | rule1,
59 | rule2
60 | )
61 |
62 | assertEquals("policy1", policy1.fqid)
63 | }
64 |
65 | @Test def testFqidOfRulesInSimplePolicy() {
66 | val rule1 = Rule("rule1") := deny
67 | val rule2 = Rule("rule2") := deny
68 |
69 | val policy1 =
70 | Policy("policy1") := apply PermitOverrides to (
71 | rule1,
72 | rule2
73 | )
74 |
75 | assertEquals("policy1>rule1", rule1.fqid)
76 | assertEquals("policy1>rule2", rule2.fqid)
77 | }
78 |
79 | @Test def testFqidChangesIfneeded() {
80 | val rule1 = Rule("rule1") := deny
81 | val rule2 = Rule("rule2") := deny
82 |
83 | assertEquals("rule1", rule1.fqid)
84 | assertEquals("rule2", rule2.fqid)
85 |
86 | val policy1 =
87 | Policy("policy1") := apply PermitOverrides to (
88 | rule1,
89 | rule2
90 | )
91 |
92 | assertEquals("policy1", policy1.fqid)
93 | assertEquals("policy1>rule1", rule1.fqid)
94 | assertEquals("policy1>rule2", rule2.fqid)
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/RequestCtx.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import scala.collection.mutable.Map
23 | import stapl.core.subject
24 | import stapl.core.resource
25 | import stapl.core.action
26 | import stapl.core.Attribute
27 | import stapl.core.ConcreteValue
28 | import stapl.core.string2Value
29 | import stapl.core.AttributeContainerType
30 | import stapl.core.SUBJECT
31 | import stapl.core.RESOURCE
32 | import stapl.core.ACTION
33 |
34 | /**
35 | * A class used for representing the context of a request.
36 | * For now, this is nothing more than the ids of the subject,
37 | * the action and the resource and any other attribute values given
38 | * with the request.
39 | *
40 | * Constructor: Initialize this new RequestCtx with given values.
41 | * The ids of the subject, action and resource should always be
42 | * given. Optionally, extra attributes can be provided (which should NOT
43 | * contain the ids of the subject, action and resource again).
44 | */
45 | class RequestCtx(val subjectId: String, val actionId: String,
46 | val resourceId: String, extraAttributes: (Attribute,ConcreteValue)*) {
47 |
48 | /*def this(subjectId: String, actionId: String, resourceId: String, extraAttributes: (Attribute,ConcreteValue)*) {
49 | this(subjectId, actionId, resourceId, extraAttributes.map{ case (attr, c) => ((attr.name, attr.cType), c) }: _*)
50 | }*/
51 |
52 | val allAttributes: Map[Attribute, ConcreteValue] = Map(extraAttributes: _*)
53 |
54 | // FIXME: these are not the same subject, resource and action as defined in BasicPolicy
55 | // For now, this works because the repeated definitions are the same, but we should'nt replicate
56 | // this definition. => idea: create a function to generate the subject, resoruce and action
57 | // and use this everwhere where you need subject/resource/action.id
58 | allAttributes += stapl.core.subject.id -> subjectId
59 | allAttributes += stapl.core.resource.id -> resourceId
60 | allAttributes += stapl.core.action.id -> actionId
61 |
62 | override def toString(): String = f"${this.subjectId}--${this.actionId}->${this.resourceId} + ${this.allAttributes}"
63 |
64 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/AttributeFinder.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import scala.annotation.tailrec
23 | import stapl.core.Attribute
24 | import stapl.core.AttributeContainerType
25 | import stapl.core.AttributeNotFoundException
26 | import stapl.core.AttributeType
27 | import stapl.core.ConcreteValue
28 | import stapl.core.ListAttribute
29 | import stapl.core.SimpleAttribute
30 | import stapl.core.TypeCheckException
31 |
32 | /**
33 | * Class used for representing an attribute finder used by a PDP. An attribute
34 | * finder tries to find values for a certain value during policy evaluation.
35 | * An attribute finder contains multiple attribute finder modules that each
36 | * connect to a certain attribute value source, such as a hard-coded list, a file or
37 | * a database.
38 | */
39 | sealed class AttributeFinder extends Modules[AttributeFinderModule] {
40 |
41 | /**
42 | * Tries to find the value of a certain attribute in the given evaluation context.
43 | */
44 | def find(ctx: EvaluationCtx, attribute: Attribute): Option[ConcreteValue] = {
45 | @tailrec
46 | def find(modules: List[AttributeFinderModule]): Option[ConcreteValue] = modules match {
47 | case module :: tail => module.find(ctx, attribute) match {
48 | case Some(result) => Some(result)
49 | case None => find(tail)
50 | }
51 | case Nil => None
52 | }
53 | find(modules)
54 | }
55 | }
56 |
57 | /**
58 | * Trait for all attribute finder modules passed to an attribute finder.
59 | */
60 | trait AttributeFinderModule {
61 |
62 | /**
63 | * The public method for trying to find the value of a certain attribute.
64 | *
65 | * TODO why is this private[core]?
66 | */
67 | private[core] def find(ctx: EvaluationCtx, attribute: Attribute): Option[ConcreteValue] = attribute match {
68 | case SimpleAttribute(cType,name,aType) => find(ctx, cType, name, aType, false)
69 | case ListAttribute(cType,name,aType) => find(ctx, cType, name, aType, true)
70 | }
71 |
72 | /**
73 | * The actual implementation for trying to find the value of a certain attribute.
74 | */
75 | protected def find(ctx: EvaluationCtx, cType: AttributeContainerType, name: String, aType: AttributeType, multiValued: Boolean): Option[ConcreteValue]
76 |
77 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/RemoteEvaluator.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.pdp
2 |
3 | import stapl.core.Result
4 | import scala.annotation.tailrec
5 | import stapl.core.RemotePolicyNotFoundException
6 |
7 | /**
8 | * A class used for remotely evaluating a policy.
9 | * A RemoteEvaluator consists of (possibly) multiple RemoteEvaluatorModules, each of which are
10 | * capable of finding and evaluating certain policies.
11 | */
12 | class RemoteEvaluator extends Modules[RemoteEvaluatorModule]{
13 |
14 | /**
15 | * Tries to find and evaluate the remote policy with id policyId in this EvaluationCtx.
16 | * The result of the first RemoteEvaluatorModule that returns Some result is returned. The
17 | * others are ignored.
18 | *
19 | * @return the result of evaluating the remote policy
20 | * @throws RemotePolicyNotFoundException if none of the RemoteEvaluatorModules can find the
21 | * remote policy
22 | */
23 | def findAndEvaluate(policyId: String, ctx: EvaluationCtx): Result = {
24 | @tailrec
25 | def findAndEvaluate(modules: List[RemoteEvaluatorModule]): Result = modules match {
26 | case module :: tail => module.findAndEvaluate(policyId, ctx) match {
27 | case Some(result) => result
28 | case None => findAndEvaluate(tail)
29 | }
30 | case Nil => throw new RemotePolicyNotFoundException(policyId)
31 | }
32 | findAndEvaluate(modules)
33 | }
34 |
35 | /**
36 | * Tries to find the remote policy with id policyId and test whether it is applicable in this
37 | * EvaluationCtx. The result of the first RemoteEvaluatorModule that returns Some result is returned.
38 | * The others are ignored.
39 | *
40 | * @return true if the remote policy is applicable, false otherwise
41 | * @throws RemotePolicyNotFoundException if none of the RemoteEvaluatorModules can find the remote policy
42 | */
43 | def findAndIsApplicable(policyId: String, ctx: EvaluationCtx): Boolean = {
44 | @tailrec
45 | def findAndIsApplicable(modules: List[RemoteEvaluatorModule]): Boolean = modules match {
46 | case module :: tail => module.findAndIsApplicable(policyId, ctx) match {
47 | case Some(result) => result
48 | case None => findAndIsApplicable(tail)
49 | }
50 | case Nil => throw new RemotePolicyNotFoundException(policyId)
51 | }
52 | findAndIsApplicable(modules)
53 | }
54 |
55 | }
56 |
57 |
58 | /**
59 | * Trait for all RemoteEvaluatorModules passed to a RemoteEvaluator.
60 | */
61 | trait RemoteEvaluatorModule {
62 |
63 | /**
64 | * Returns the result of evaluating the remote policy with id policyId in this
65 | * EvaluationCtx if he can find said policy. If the policy can't be found None
66 | * will be returned.
67 | */
68 | def findAndEvaluate(policyId: String, ctx: EvaluationCtx): Option[Result]
69 |
70 | /**
71 | * Returns the result of testing the applicability of the remote policy with id policyId
72 | * in this EvaluationCtx if he can find said policy. If the policy can't be found None
73 | * will be returned.
74 | */
75 | def findAndIsApplicable(policyId: String, ctx: EvaluationCtx): Option[Boolean]
76 |
77 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Expressions.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import java.util.Date
23 | import stapl.core.pdp.EvaluationCtx
24 | import scala.concurrent.Future
25 | import concurrent.ExecutionContext.Implicits.global
26 | import scala.util.{Try, Success, Failure}
27 |
28 | abstract class Expression {
29 | def evaluate(implicit ctx: EvaluationCtx): Boolean
30 |
31 | final def &(that: Expression): Expression = And(this, that)
32 |
33 | final def |(that: Expression): Expression = Or(this, that)
34 |
35 | final def unary_!(): Expression = Not(this)
36 | }
37 |
38 | case object AlwaysTrue extends Expression {
39 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = true
40 | }
41 | case object AlwaysFalse extends Expression {
42 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = false
43 | }
44 | case class GreaterThanValue(value1: Value, value2: Value) extends Expression {
45 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = {
46 | val c1 = value1.getConcreteValue(ctx)
47 | val c2 = value2.getConcreteValue(ctx)
48 | c1.reprGreaterThan(c2)
49 | }
50 | }
51 | case class BoolExpression(attribute: SimpleAttribute) extends Expression {
52 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = {
53 | val bool = attribute.getConcreteValue(ctx).representation
54 | bool.asInstanceOf[Boolean]
55 | }
56 | }
57 |
58 | case class EqualsValue(value1: Value, value2: Value) extends Expression {
59 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = {
60 | val c1 = value1.getConcreteValue(ctx)
61 | val c2 = value2.getConcreteValue(ctx)
62 | c1.equalRepr(c2)
63 | }
64 | }
65 | case class ValueIn(value: Value, list: Value) extends Expression {
66 | override def evaluate(implicit ctx: EvaluationCtx): Boolean = {
67 | val c = value.getConcreteValue(ctx)
68 | val l = list.getConcreteValue(ctx)
69 | l.reprContains(c)
70 | }
71 | }
72 | case class And(expression1: Expression, expression2: Expression) extends Expression {
73 | override def evaluate(implicit ctx: EvaluationCtx) = expression1.evaluate && expression2.evaluate
74 | }
75 | case class Or(expression1: Expression, expression2: Expression) extends Expression {
76 | override def evaluate(implicit ctx: EvaluationCtx) = expression1.evaluate || expression2.evaluate
77 | }
78 | case class Not(expression: Expression) extends Expression {
79 | override def evaluate(implicit ctx: EvaluationCtx) = !expression.evaluate
80 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Attributes.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import AttributeType._
23 | import stapl.core.pdp.EvaluationCtx
24 | import scala.concurrent.Future
25 | import scala.concurrent.ExecutionContext.Implicits.global
26 | import scala.util.{ Try, Success, Failure }
27 |
28 | sealed abstract class Attribute(val cType: AttributeContainerType, val name: String, val aType: AttributeType)
29 | extends Value with Serializable {
30 |
31 | override def getConcreteValue(ctx: EvaluationCtx) =
32 | ctx.findAttribute(this)
33 | }
34 | object Attribute {
35 |
36 | /**
37 | * A constructor to get a ListAttribute or SimpleAttribute depending on the
38 | * given multiplicity.
39 | */
40 | def apply(cType: AttributeContainerType, name: String, aType: AttributeType, multiValued: Boolean): Attribute = {
41 | if (multiValued) {
42 | new ListAttribute(cType, name, aType)
43 | } else {
44 | new SimpleAttribute(cType, name, aType)
45 | }
46 | }
47 | }
48 |
49 | case class ListAttribute(ct: AttributeContainerType, n: String, at: AttributeType)
50 | extends Attribute(ct, n, at) {
51 |
52 | override val isList = true
53 |
54 | override def toString(): String = s"$cType.$name:List[$aType]"
55 |
56 | }
57 |
58 | object ListAttribute {
59 | import AttributeConstruction.UninitializedAttribute
60 | def apply(sType: AttributeType): UninitializedAttribute =
61 | (None, new ListAttribute(_: AttributeContainerType, _: String, sType))
62 |
63 | def apply(name: String, sType: AttributeType): UninitializedAttribute =
64 | (Option(name), new ListAttribute(_: AttributeContainerType, _: String, sType))
65 | }
66 |
67 | case class SimpleAttribute(ct: AttributeContainerType, n: String, at: AttributeType)
68 | extends Attribute(ct, n, at) {
69 |
70 | override val isList = false
71 |
72 | override def toString(): String = s"$cType.$name:$aType"
73 |
74 | }
75 |
76 | object SimpleAttribute {
77 | import AttributeConstruction.UninitializedAttribute
78 | def apply(sType: AttributeType): UninitializedAttribute =
79 | (None, new SimpleAttribute(_: AttributeContainerType, _: String, sType))
80 |
81 | def apply(name: String, sType: AttributeType): UninitializedAttribute =
82 | (Option(name), new SimpleAttribute(_: AttributeContainerType, _: String, sType))
83 | }
84 |
85 | object AttributeConstruction {
86 | private type AttributeConstructor = (AttributeContainerType, String) => Attribute
87 | type UninitializedAttribute = (Option[String], AttributeConstructor)
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Obligations.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import stapl.core.pdp.EvaluationCtx
23 |
24 | /**
25 | * An obligation consists of an action that should be fulfilled and the
26 | * effect on which the action should be fulfilled.
27 | */
28 | case class Obligation(val action: ObligationAction, val fulfillOn: Effect)
29 |
30 | /**
31 | * Traits for representing obligations:
32 | *
33 | * - ObligationAction: the obligation actions that can be specified in policies,
34 | * but can still contain attribute references that should be concretized
35 | * using the evaluation context.
36 | *
37 | * - ConcreteObligationAction: the concretized obligation actions
38 | *
39 | * - SimpleObligationAction: a simple trait for obligation actions that are can be specified
40 | * in policies, but do not need special logic to be concretized (e.g., they do not
41 | * contain attribute references, only literal values)
42 | */
43 | trait ObligationAction {
44 |
45 | def getConcrete(implicit ctx: EvaluationCtx): ConcreteObligationAction
46 | }
47 | trait ConcreteObligationAction
48 | trait SimpleObligationAction extends ObligationAction with ConcreteObligationAction {
49 |
50 | override def getConcrete(implicit ctx: EvaluationCtx) = this
51 | }
52 |
53 | /**
54 | * Logging
55 | */
56 | case class LogObligationAction(val msg: Value) extends ObligationAction {
57 |
58 | def getConcrete(implicit ctx: EvaluationCtx) = ConcreteLogObligationAction(msg.getConcreteValue(ctx).representation.toString)
59 | }
60 | case class ConcreteLogObligationAction(val msg: String) extends ConcreteObligationAction
61 |
62 |
63 | /**
64 | * Mailing
65 | */
66 | case class MailObligationAction(val to: String, val msg: String) extends SimpleObligationAction
67 |
68 |
69 | /**
70 | * The multiple ways of changing attribute values
71 | */
72 | sealed abstract class AttributeChangeType
73 | case object Update extends AttributeChangeType
74 | case object Append extends AttributeChangeType
75 | case class ChangeAttributeObligationAction(val attribute: Attribute, val value: Value,
76 | val changeType: AttributeChangeType) extends ObligationAction {
77 |
78 | def getConcrete(implicit ctx: EvaluationCtx) = {
79 | val entityId = attribute.cType match {
80 | case SUBJECT => ctx.subjectId
81 | case RESOURCE => ctx.resourceId
82 | case _ => throw new IllegalArgumentException(s"You can only update SUBJECT and RESOURCE attributes. Given attribute: $attribute")
83 | }
84 | ConcreteChangeAttributeObligationAction(entityId, attribute, value.getConcreteValue(ctx), changeType)
85 | }
86 | }
87 | case class ConcreteChangeAttributeObligationAction(val entityId: String, val attribute: Attribute,
88 | val value: ConcreteValue, val changeType: AttributeChangeType) extends ConcreteObligationAction
89 |
90 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/AttributeContainer.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import scala.language.dynamics
23 | import scala.collection.mutable.Map
24 | import scala.collection.mutable.Buffer
25 |
26 | class AttributeDeclarationException(message: String = null, cause: Throwable = null) extends RuntimeException(message, cause)
27 |
28 | /**
29 | * Base class for all attribute containers, such as the subject, resource, action and environment
30 | * in most STAPL policies.
31 | *
32 | * By extending Dynamic, we can assign attributes to this attribute container as a variable. For example:
33 | * subject.roles = ListAttribute(String) -> subject.set("roles", ListAttribute(String))
34 | *
35 | * TODO mechanism is needed so attribute types are known (to the compiler) at compile time
36 | */
37 | class AttributeContainer private(cType: AttributeContainerType, attributes: Map[String, Attribute]) extends Dynamic {
38 |
39 | final def this(cType: AttributeContainerType) = this(cType, Map())
40 |
41 | // import the type alias for uninitialized attributes
42 | import AttributeConstruction.UninitializedAttribute
43 |
44 | final def set(name: String, attribute: UninitializedAttribute) {
45 | val (optionName, attributeConstructor) = attribute
46 | val actualAttribute = optionName match {
47 | case Some(someName) => attributeConstructor(cType, someName)
48 | case None => attributeConstructor(cType, name)
49 | }
50 | if(attributes.contains(name)) {
51 | throw new AttributeDeclarationException(s"Error when assigning $cType.$name: already assigned")
52 | }
53 | attributes += name -> actualAttribute
54 | refinements.foreach(_.updateDynamic(name)(attribute))
55 | }
56 |
57 | final def get(name: String): Attribute = {
58 | try attributes(name)
59 | catch {
60 | case _: NoSuchElementException => throw new AttributeDoesNotExistException(name)
61 | }
62 | }
63 |
64 | final def selectDynamic(name: String): Attribute = get(name)
65 |
66 | final def updateDynamic(name: String)(attribute: UninitializedAttribute){
67 | set(name, attribute)
68 | }
69 |
70 | private val refinements = Buffer.empty[AttributeContainer]
71 |
72 | final def refine(): AttributeContainer = {
73 | val refinement = new AttributeContainer(cType, attributes.clone)
74 | refinements += refinement
75 | refinement
76 | }
77 |
78 | def allAttributes: Seq[Attribute] = attributes.values.toSeq
79 | }
80 |
81 | /**
82 | * All attribute container types for now.
83 | *
84 | * TODO: extend this to support the container hierarchies as well?
85 | */
86 | sealed abstract class AttributeContainerType
87 | case object SUBJECT extends AttributeContainerType
88 | case object RESOURCE extends AttributeContainerType
89 | case object ENVIRONMENT extends AttributeContainerType
90 | case object ACTION extends AttributeContainerType
91 |
92 | class SubjectAttributeContainer extends AttributeContainer(SUBJECT)
93 | class ResourceAttributeContainer extends AttributeContainer(RESOURCE)
94 | class EnvironmentAttributeContainer extends AttributeContainer(ENVIRONMENT)
95 | class ActionAttributeContainer extends AttributeContainer(ACTION)
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Value.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import stapl.core.pdp.EvaluationCtx
23 | import scala.concurrent.Future
24 | import scala.util.{Try, Success, Failure}
25 |
26 | /**
27 | * The base trait for all attributes in policies. Each Value has a
28 | * type and multiplicity (list or not).
29 | */
30 | trait Value {
31 |
32 | val aType: AttributeType
33 |
34 | val isList: Boolean
35 |
36 | def getConcreteValue(ctx: EvaluationCtx): ConcreteValue
37 |
38 | private def typeCheck(that: Value) {
39 | AttributeType.checkType(that.aType, this.aType)
40 | }
41 |
42 | def in(that: Value): Expression = {
43 | if (this.isList || !that.isList)
44 | throw new UnsupportedOperationException("An in operation is only possible between a simple value and a list.")
45 | typeCheck(that)
46 | if (List(DateTimeDuration, DayDuration, TimeDuration) contains this.aType)
47 | throw new UnsupportedOperationException("An in operation is not possible for durations.")
48 | ValueIn(this, that)
49 | }
50 |
51 | /*def contains(that: Value): Expression = {
52 | if (!this.isList || that.isList)
53 | throw new UnsupportedOperationException("An in/contains operation is only possible between a list and a simple value.")
54 | typeCheck(that)
55 | if (List(DateTimeDuration, DayDuration, TimeDuration) contains this.aType)
56 | throw new UnsupportedOperationException("An in/contains operation is not possible for durations.")
57 | ValueIn(that, this)
58 | }*/
59 |
60 |
61 | def ===(that: Value): Expression = {
62 | if (this.isList || that.isList)
63 | throw new UnsupportedOperationException("An equals operation is only possible between simple values.")
64 | typeCheck(that)
65 | if (List(DateTimeDuration, DayDuration, TimeDuration) contains this.aType)
66 | throw new UnsupportedOperationException("An equals operation is not possible for durations.")
67 | EqualsValue(this, that)
68 | }
69 |
70 | // XXX added !==
71 | def !==(that: Value): Expression = !(this === that)
72 |
73 | def gt(that: Value): Expression = {
74 | if (this.isList || that.isList)
75 | throw new UnsupportedOperationException("A comparison operation is only possible between simple values.")
76 | typeCheck(that)
77 | if (List(DateTimeDuration, DayDuration, TimeDuration) contains this.aType)
78 | throw new UnsupportedOperationException("A comparison operation is not possible for durations.")
79 | GreaterThanValue(this, that)
80 | }
81 |
82 | def lt(that: Value): Expression = that gt this
83 |
84 | def gteq(that: Value): Expression = (this gt that) | (this === that)
85 |
86 | def lteq(that: Value): Expression = (this lt that) | (this === that)
87 |
88 | def +(that: Value): Operation = {
89 | Addition(this, that)
90 | }
91 |
92 | def -(that: Value): Operation = {
93 | Subtraction(this, that)
94 | }
95 |
96 | def *(that: Value): Operation = {
97 | Multiplication(this, that)
98 | }
99 |
100 | def /(that: Value): Operation = {
101 | Division(this, that)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Operations.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import stapl.core.pdp.EvaluationCtx
23 | import scala.concurrent.Future
24 | import scala.concurrent.ExecutionContext.Implicits.global
25 | import scala.util.{Try, Success, Failure}
26 |
27 | sealed abstract class Operation extends Value
28 |
29 | case class Addition(left: Value, right: Value) extends Operation {
30 |
31 | override val aType =
32 | if (!left.isList && !right.isList)
33 | left.aType.addition(right.aType).getOrElse {
34 | val lType = left.aType
35 | val rType = right.aType
36 | throw new TypeCheckException(s"Cannot add a $rType to a $lType.")
37 | }
38 | else
39 | throw new UnsupportedOperationException("Cannot add lists.")
40 |
41 | override val isList = false
42 |
43 | override def getConcreteValue(ctx: EvaluationCtx) = {
44 | val leftValue = left.getConcreteValue(ctx)
45 | val rightValue = right.getConcreteValue(ctx)
46 | leftValue.add(rightValue)
47 | }
48 | }
49 |
50 | case class Subtraction(left: Value, right: Value) extends Operation {
51 |
52 | override val aType =
53 | if (!left.isList && !right.isList)
54 | left.aType.subtraction(right.aType).getOrElse {
55 | val lType = left.aType
56 | val rType = right.aType
57 | throw new TypeCheckException(s"Cannot subtract a $rType from a $lType.")
58 | }
59 | else
60 | throw new UnsupportedOperationException("Cannot subtract lists.")
61 |
62 | override val isList = false
63 |
64 | override def getConcreteValue(ctx: EvaluationCtx) = {
65 | val leftValue = left.getConcreteValue(ctx)
66 | val rightValue = right.getConcreteValue(ctx)
67 |
68 | leftValue.subtract(rightValue)
69 | }
70 | }
71 |
72 | case class Multiplication(left: Value, right: Value) extends Operation {
73 |
74 | override val aType =
75 | if (!left.isList && !right.isList)
76 | left.aType.multiplication(right.aType).getOrElse {
77 | val lType = left.aType
78 | val rType = right.aType
79 | throw new TypeCheckException(s"Cannot multiply a $rType with a $lType.")
80 | }
81 | else
82 | throw new UnsupportedOperationException("Cannot multiply lists.")
83 |
84 | override val isList = false
85 |
86 | override def getConcreteValue(ctx: EvaluationCtx) = {
87 | val leftValue = left.getConcreteValue(ctx)
88 | val rightValue = right.getConcreteValue(ctx)
89 |
90 | leftValue.multiply(rightValue)
91 | }
92 | }
93 |
94 | case class Division(left: Value, right: Value) extends Operation {
95 |
96 | override val aType =
97 | if (!left.isList && !right.isList)
98 | left.aType.division(right.aType).getOrElse {
99 | val lType = left.aType
100 | val rType = right.aType
101 | throw new TypeCheckException(s"Cannot divide a $rType by a $lType.")
102 | }
103 | else
104 | throw new UnsupportedOperationException("Cannot divide lists.")
105 |
106 | override val isList = false
107 |
108 | override def getConcreteValue(ctx: EvaluationCtx) = {
109 | val leftValue = left.getConcreteValue(ctx)
110 | val rightValue = right.getConcreteValue(ctx)
111 |
112 | leftValue.divide(rightValue)
113 | }
114 | }
115 |
116 | case class AbsoluteValue(value: Value) extends Operation {
117 |
118 | override val aType =
119 | if (!value.isList)
120 | value.aType.absoluteValue().getOrElse {
121 | val vType = value.aType
122 | throw new TypeCheckException(s"Cannot take the absolute value of a $vType.")
123 | }
124 | else
125 | throw new UnsupportedOperationException("Cannot take the absolute value of a list.")
126 |
127 | override val isList = false
128 |
129 | override def getConcreteValue(ctx: EvaluationCtx) = {
130 | val cValue = value.getConcreteValue(ctx)
131 |
132 | cValue.abs()
133 | }
134 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/PDP.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import stapl.core.AbstractPolicy
23 | import stapl.core.Attribute
24 | import stapl.core.ConcreteValue
25 | import stapl.core.Decision
26 | import stapl.core.Result
27 | import scala.concurrent.Future
28 | import scala.concurrent.ExecutionContext.Implicits.global
29 | import scala.concurrent.blocking
30 | import scala.concurrent.Await
31 | import scala.concurrent.duration._
32 | import scala.util.{ Try, Success, Failure }
33 | import scala.collection.mutable.ListBuffer
34 | import stapl.core.ObligationAction
35 | import stapl.core.ConcreteObligationAction
36 |
37 | /**
38 | * Class used for representing a policy decision point (PDP). A PDP provides
39 | * access decisions by evaluating a policy.
40 | */
41 | class PDP(policy: AbstractPolicy,
42 | attributeFinder: AttributeFinder = new AttributeFinder,
43 | obligationService: ObligationService = new ObligationService,
44 | remoteEvaluator: RemoteEvaluator = new RemoteEvaluator) {
45 |
46 | private val timestampGenerator = new SimpleTimestampGenerator
47 |
48 | /**
49 | * Set up this new PDP with an empty attribute finder (which does not find
50 | * any attributes) and empty remote evaluator.
51 | */
52 | def this(policy: AbstractPolicy) = this(policy, new AttributeFinder, new ObligationService, new RemoteEvaluator)
53 |
54 | /**
55 | * Set up this new PDP with an empty attribute finder (which does not find
56 | * any attributes).
57 | */
58 | def this(policy: AbstractPolicy, remoteEvaluator: RemoteEvaluator) = this(policy, new AttributeFinder, new ObligationService, remoteEvaluator)
59 |
60 | /**
61 | * Set up this new PDP with an empty remote evaluator.
62 | */
63 | def this(policy: AbstractPolicy, attributeFinder: AttributeFinder) = this(policy, attributeFinder, new ObligationService, new RemoteEvaluator)
64 |
65 | /**
66 | * Evaluate the policy of this PDP with given subject id, action id, resource id
67 | * and possibly extra attributes and return the result.
68 | * This will employ the attribute finder of this PDP.
69 | */
70 | def evaluate(subjectId: String, actionId: String,
71 | resourceId: String, extraAttributes: (Attribute, ConcreteValue)*): Result =
72 | evaluate(new RequestCtx(subjectId, actionId, resourceId, extraAttributes: _*))
73 |
74 | /**
75 | * Evaluate the policy of this PDP with given request context and generated incrementing
76 | * evaluation id and return the result. This will employ the attribute finder of this PDP.
77 | */
78 | def evaluate(ctx: RequestCtx): Result =
79 | evaluate(new BasicEvaluationCtx(timestampGenerator.getTimestamp, ctx, attributeFinder, remoteEvaluator))
80 |
81 | /**
82 | * Evaluate the policy of this PDP with given evaluation id and request context
83 | * and return the result. This will employ the attribute finder of this PDP.
84 | */
85 | def evaluate(evaluationId: String, ctx: RequestCtx): Result =
86 | evaluate(new BasicEvaluationCtx(evaluationId, ctx, attributeFinder, remoteEvaluator))
87 |
88 | /**
89 | * Evaluate the policy of this PDP with given evaluation context and return
90 | * the result.
91 | * This allows you to specify another attribute finder than the one of this PDP.
92 | *
93 | * The PDP will try to fulfill all obligations using the ObligationService and removes
94 | * the fulfilled obligations from the result.
95 | */
96 | def evaluate(ctx: EvaluationCtx): Result = {
97 | val result = policy.evaluate(ctx)
98 | // try to fulfill the obligations
99 | val remainingObligations = ListBuffer[ConcreteObligationAction]()
100 | for (obl <- result.obligationActions) {
101 | if (!obligationService.fulfill(obl)) {
102 | remainingObligations += obl
103 | }
104 | }
105 | // return the result with the remaining obligations
106 | new Result(result.decision, remainingObligations.toList, ctx.employedAttributes)
107 | }
108 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/package.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl
21 |
22 | import scala.language.implicitConversions
23 | import stapl.core.SimpleAttribute
24 | import org.joda.time.LocalDateTime
25 |
26 | package object core {
27 |
28 | /**
29 | * Some implicits for converting standard Scala types to STAPL values.
30 | */
31 |
32 | implicit def boolean2Value(boolean: Boolean): ConcreteValue = new BoolImpl(boolean)
33 |
34 | implicit def int2Value(int: Int): ConcreteValue = new NumberImpl(Left(int))
35 |
36 | implicit def double2Value(double: Double): ConcreteValue = new NumberImpl(Right(double))
37 |
38 | implicit def long2Value(long: Long): ConcreteValue = new NumberImpl(Left(long))
39 |
40 | implicit def string2Value(string: String): ConcreteValue = new StringImpl(string)
41 |
42 | implicit def dateTime2Value(dt: LocalDateTime): ConcreteValue = new DateTimeImpl(dt)
43 |
44 | implicit def stringSeq2Value(seq: Seq[String]): ConcreteValue = new StringSeqImpl(seq)
45 |
46 | implicit def booleanSeq2Value(seq: Seq[Boolean]): ConcreteValue = new BoolSeqImpl(seq)
47 |
48 | implicit def intSeq2Value(seq: Seq[Int]): ConcreteValue = new IntSeqImpl(seq)
49 |
50 | implicit def doubleSeq2Value(seq: Seq[Double]): ConcreteValue = new DoubleSeqImpl(seq)
51 |
52 | implicit def longSeq2Value(seq: Seq[Long]): ConcreteValue = new LongSeqImpl(seq)
53 |
54 | implicit def jodaDateTimeSeq2Value(seq: Seq[LocalDateTime]): ConcreteValue = new DateTimeSeqImpl(seq.map(l => new DateTimeImpl(l)))
55 |
56 | implicit def dateTimeSeq2Value(seq: Seq[DateTimeImpl]): ConcreteValue = new DateTimeSeqImpl(seq)
57 |
58 | implicit def timeSeq2Value(seq: Seq[TimeImpl]): ConcreteValue = new TimeSeqImpl(seq)
59 |
60 | implicit def daySeq2Value(seq: Seq[DayImpl]): ConcreteValue = new DaySeqImpl(seq)
61 |
62 | implicit def dateTimeDurSeq2Value(seq: Seq[DateTimeDurationImpl]): ConcreteValue = new DateTimeDurSeqImpl(seq)
63 |
64 | implicit def timeDurSeq2Value(seq: Seq[TimeDurationImpl]): ConcreteValue = new TimeDurSeqImpl(seq)
65 |
66 | implicit def dayDurSeq2Value(seq: Seq[DayDurationImpl]): ConcreteValue = new DayDurSeqImpl(seq)
67 |
68 | /**
69 | * Some implicits for converting standard Scala types to STAPL expressions.
70 | */
71 |
72 | implicit def boolAttributeToExpression(attribute: Attribute): Expression = attribute match {
73 | case x @ SimpleAttribute(_, _, Bool) => BoolExpression(x)
74 | case SimpleAttribute(_, _, aType) => throw new TypeCheckException(aType, Bool)
75 | case _ => throw new IllegalArgumentException("Found a list, but expected a Bool.")
76 | }
77 |
78 | implicit def boolean2Expression(bool: Boolean): Expression = if (bool) AlwaysTrue else AlwaysFalse
79 |
80 | implicit def int2DurationBuilder(int: Int) = new DurationBuilder(int)
81 |
82 | def abs(value: Value): Operation = AbsoluteValue(value)
83 |
84 | /**
85 | * Implicit for converting a Decision to a Result without obligations.
86 | */
87 | implicit def decision2Result(decision: Decision): Result = Result(decision)
88 |
89 |
90 |
91 | /**
92 | * The definitions of the standard subject, action, resource and environment.
93 | *
94 | * Important: always create a new instance so multiple policies can work on
95 | * multiple instances of these objects, but testing equality with the id attribute
96 | * of every instance will always work.
97 | *
98 | * TODO remove these default objects?
99 | */
100 | def subject: SubjectAttributeContainer = {
101 | val subject = new SubjectAttributeContainer
102 | subject.id = SimpleAttribute(String)
103 | subject
104 | }
105 | def resource: ResourceAttributeContainer = {
106 | val resource = new ResourceAttributeContainer
107 | resource.id = SimpleAttribute(String)
108 | resource
109 | }
110 | def action: ActionAttributeContainer = {
111 | val action = new ActionAttributeContainer
112 | action.id = SimpleAttribute(String)
113 | action
114 | }
115 | def environment: EnvironmentAttributeContainer = new EnvironmentAttributeContainer
116 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | stapl
5 | stapl-core
6 | 0.0.1-SNAPSHOT
7 | ${project.artifactId}
8 | The core of the STAPL language for access control policies
9 | 2014
10 |
11 |
12 | 1.6
13 | 1.6
14 | UTF-8
15 | 2.10
16 | 2.10.4
17 |
18 |
19 |
20 |
21 | org.scala-lang
22 | scala-library
23 | ${scala.version}
24 |
25 |
26 |
27 |
28 | junit
29 | junit
30 | 4.11
31 | test
32 |
33 |
34 | org.scalatest
35 | scalatest_${scala.tools.version}
36 | 2.2.2
37 | test
38 |
39 |
40 | joda-time
41 | joda-time
42 | 2.2
43 |
44 |
45 | org.joda
46 | joda-convert
47 | 1.7
48 |
49 |
50 | org.scala-lang
51 | scala-compiler
52 | 2.10.4
53 |
54 |
55 | ch.qos.logback
56 | logback-classic
57 | 1.1.2
58 |
59 |
60 | org.clapper
61 | grizzled-slf4j_2.10
62 | 1.0.2
63 |
64 |
65 | org.clapper
66 | grizzled-scala_2.10
67 | 1.2
68 |
69 |
70 | org.scala-lang
71 | scala-swing
72 | 2.10.4
73 |
74 |
75 |
76 |
77 | src/main/scala
78 | src/test/scala
79 |
80 |
81 | org.apache.maven.plugins
82 | maven-surefire-plugin
83 | 2.13
84 |
85 | false
86 | true
87 |
88 |
89 |
90 | **/*Test.*
91 | **/*Suite.*
92 |
93 |
94 |
95 |
96 | com.mycila.maven-license-plugin
97 | maven-license-plugin
98 | 1.10.b1
99 |
100 |
101 |
102 | **/*.scala
103 |
104 |
105 |
106 |
107 | net.alchim31.maven
108 | scala-maven-plugin
109 | 3.1.6
110 |
111 | incremental
112 |
113 | -deprecation
114 | -explaintypes
115 | -target:jvm-1.7
116 |
117 |
118 |
119 |
120 | scala-compile-first
121 | process-resources
122 |
123 | add-source
124 | compile
125 |
126 |
127 |
128 | scala-test-compile
129 | process-test-resources
130 |
131 | add-source
132 | testCompile
133 |
134 |
135 |
136 |
137 |
138 | maven-assembly-plugin
139 |
140 |
141 |
142 | ${main}
143 |
144 |
145 |
146 | jar-with-dependencies
147 |
148 |
149 |
150 |
151 | make-assembly
152 | package
153 |
154 | single
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/CombinationAlgorithms.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import scala.annotation.tailrec
23 | import stapl.core.pdp.EvaluationCtx
24 | import scala.concurrent.Future
25 | import scala.concurrent.Promise
26 | import scala.concurrent.ExecutionContext.Implicits.global
27 | import scala.collection.mutable.Map
28 | import scala.util.{ Try, Success, Failure }
29 |
30 | /**
31 | * *************************
32 | * WRAPPERS
33 | *
34 | * We separate the keywords from the implementation so that these can be overridden
35 | * at runtime by passing them to the evaluation context.
36 | */
37 | sealed trait CombinationAlgorithm {
38 |
39 | def combine(policies: List[AbstractPolicy], ctx: EvaluationCtx): Result =
40 | ctx.getCombinationAlgorithmImplementation(this).combine(policies, ctx)
41 | }
42 |
43 | case object PermitOverrides extends CombinationAlgorithm
44 | case object DenyOverrides extends CombinationAlgorithm
45 | case object FirstApplicable extends CombinationAlgorithm
46 |
47 | /**
48 | * **********************
49 | * IMPLEMENTATIONS
50 | */
51 | trait CombinationAlgorithmImplementation {
52 |
53 | def combine(policies: List[AbstractPolicy], ctx: EvaluationCtx): Result
54 | }
55 |
56 | trait CombinationAlgorithmImplementationBundle {
57 | def PermitOverrides: CombinationAlgorithmImplementation
58 | def DenyOverrides: CombinationAlgorithmImplementation
59 | def FirstApplicable: CombinationAlgorithmImplementation
60 | }
61 |
62 | /**
63 | * The bundle of simple implementations: sequential evaluation.
64 | */
65 | object SimpleCombinationAlgorithmImplementationBundle extends CombinationAlgorithmImplementationBundle {
66 |
67 | object PermitOverrides extends CombinationAlgorithmImplementation {
68 |
69 | override def combine(policies: List[AbstractPolicy], ctx: EvaluationCtx): Result = {
70 |
71 | var tmpResult = Result(NotApplicable)
72 |
73 | for (policy <- policies) {
74 | val Result(decision, obligationActions, _) = policy.evaluate(ctx)
75 | // If a subpolicy returns Permit: return this result with its obligations,
76 | // do not evaluate the rest for other obligations.
77 | // TODO is this correct?
78 | // If all subpolicies return Deny: combine all their obligations and return
79 | // them with Deny
80 | // If all subpolicies return NotApplicable: return NotApplicable without obligations
81 | // See XACML2 specs, Section 7.14
82 | decision match {
83 | case Permit =>
84 | // we only need one Permit and only retain those obligations => jump out of the loop
85 | return Result(decision, obligationActions)
86 | case Deny =>
87 | // retain all obligations of previous Denies
88 | tmpResult = Result(Deny, tmpResult.obligationActions ::: obligationActions)
89 | case NotApplicable => // nothing to do
90 | }
91 | }
92 | // if we got here: return the tmpResult with Deny or NotApplicable
93 | tmpResult
94 | }
95 | }
96 |
97 | object DenyOverrides extends CombinationAlgorithmImplementation {
98 |
99 | override def combine(policies: List[AbstractPolicy], ctx: EvaluationCtx): Result = {
100 |
101 | var tmpResult = Result(NotApplicable)
102 |
103 | for (policy <- policies) {
104 | val Result(decision, obligationActions, _) = policy.evaluate(ctx)
105 | // If a subpolicy returns Deny: return this result with its obligations,
106 | // do not evaluate the rest for other obligations.
107 | // TODO is this correct?
108 | // If all subpolicies return Permit: combine all their obligations and return
109 | // them with Permit
110 | // If all subpolicies return NotApplicable: return NotApplicable without obligations
111 | // See XACML2 specs, Section 7.14
112 | decision match {
113 | case Deny =>
114 | // we only need one Deny and only retain those obligations => jump out of the loop
115 | return Result(decision, obligationActions)
116 | case Permit =>
117 | // retain all obligations of previous Permits
118 | tmpResult = Result(Permit, tmpResult.obligationActions ::: obligationActions)
119 | case NotApplicable => // nothing to do
120 | }
121 | }
122 | // if we got here: return the tmpResult with Permit or NotApplicable
123 | tmpResult
124 | }
125 | }
126 |
127 | object FirstApplicable extends CombinationAlgorithmImplementation {
128 |
129 | override def combine(policies: List[AbstractPolicy], ctx: EvaluationCtx): Result = {
130 |
131 | var tmpResult = Result(NotApplicable)
132 |
133 | for (policy <- policies) {
134 | val Result(decision, obligationActions, _) = policy.evaluate(ctx)
135 | decision match {
136 | case Permit | Deny =>
137 | // we only need one Deny or Permit and only retain those obligations => jump out of the loop
138 | return Result(decision, obligationActions)
139 | case NotApplicable => // nothing to do
140 | }
141 | }
142 | // if we got here: return the tmpResult with Permit or NotApplicable
143 | tmpResult
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/resources/policies/ehealth-natural.stapl:
--------------------------------------------------------------------------------
1 | // The policy set for "view patient status".
2 | PolicySet("jisa13-final3") := when (action.id === "view" & resource.type_ === "patientstatus") apply DenyOverrides to (
3 | // The consent policy.
4 | PolicySet("policy:1") := when ("medical_personnel" in subject.roles) apply PermitOverrides to (
5 | Policy("consent") := deny iff (subject.id in resource.owner_withdrawn_consents),
6 | Policy("breaking-glass") := permit iff (subject.triggered_breaking_glass) performing (log(subject.id + " performed breaking-the-glass procedure"))
7 | ) performing (log("just another log on Permit") on Permit),
8 |
9 | // Only physicians, nurses and patients can access the monitoring system.
10 | Policy("policy:2") := deny iff !(("nurse" in subject.roles) | ("physician" in subject.roles) | ("patient" in subject.roles)),
11 |
12 | // For physicians.
13 | PolicySet("policyset:2") := when ("physician" in subject.roles) apply FirstApplicable to (
14 | // Of the physicians, only gps, physicians of the cardiology department, physicians of the elder care department and physicians of the emergency department can access the monitoring system.
15 | Policy("policy:3") := deny iff !((subject.department === "cardiology") | (subject.department === "elder_care") | (subject.department === "emergency") | ("gp" in subject.roles)),
16 |
17 | // All of the previous physicians can access the monitoring system in case of emergency.
18 | Policy("policy:4") := when ((subject.department === "cardiology") | (subject.department === "elder_care") | (subject.department === "emergency"))
19 | permit iff (subject.triggered_breaking_glass | resource.operator_triggered_emergency | resource.indicates_emergency),
20 |
21 | // For GPs: only permit if in consultation or treated in the last six months or primary physician or responsible in the system.
22 | OnlyPermitIff("policyset:3")(
23 | target = "gp" in subject.roles,
24 | (resource.owner_id === subject.current_patient_in_consultation)
25 | | (resource.owner_id in subject.treated_in_last_six_months)
26 | | (resource.owner_id in subject.primary_patients)
27 | | (subject.id in resource.owner_responsible_physicians)
28 | ),
29 |
30 | // For cardiologists.
31 | PolicySet("policyset:4") := when (subject.department === "cardiology") apply PermitOverrides to (
32 | // Permit for head physician.
33 | Policy("policy:7") := when (subject.is_head_physician) permit,
34 |
35 | // Permit if treated the patient or treated in team.
36 | Policy("policy:8") := permit iff (resource.owner_id in subject.treated) | (resource.owner_id in subject.treated_by_team),
37 |
38 | Policy("policy:9") := deny
39 | ),
40 |
41 | // For physicians of elder care department: only permit if admitted in care unit or treated in the last six months.
42 | OnlyPermitIff("policyset:5")(
43 | target = subject.department === "elder_care",
44 | (resource.owner_id in subject.admitted_patients_in_care_unit)
45 | | (resource.owner_id in subject.treated_in_last_six_months)
46 | ),
47 |
48 | // For physicians of emergency department: only permit if patient status is bad (or the above).
49 | OnlyPermitIff("policyset:6")(
50 | target = subject.department === "emergency",
51 | resource.patient_status === "bad"
52 | )
53 | ),
54 |
55 | // For nurses.
56 | PolicySet("policyset:7") := when ("nurse" in subject.roles) apply FirstApplicable to (
57 | // Of the nurses, only nurses of the cardiology department or the elder care department can access the PMS.
58 | Policy("policy:14") := deny iff !((subject.department === "cardiology") | (subject.department === "elder_care")),
59 |
60 | // Nurses can only access the PMS during their shifts.
61 | Policy("policy:15") := deny iff !((env.currentDateTime gteq subject.shift_start) & (env.currentDateTime lteq subject.shift_stop)),
62 |
63 | // Nurses can only access the PMS from the hospital.
64 | Policy("policy:16") := deny iff !(subject.location === "hospital"),
65 |
66 | // Nurses can only view the patient's status of the last five days.
67 | Policy("policy:17") := deny iff !(env.currentDateTime lteq (resource.created + 5.days)),
68 |
69 | // For nurses of cardiology department: they can only view the patient status of a patient
70 | // in their nurse unit for whom they are assigned responsible, up to three days after they were discharged.
71 | OnlyPermitIff("policyset:8")(
72 | target = subject.department === "cardiology",
73 | (resource.owner_id in subject.admitted_patients_in_nurse_unit)
74 | & (!resource.owner_discharged | (env.currentDateTime lteq (resource.owner_discharged_dateTime + 3.days)))
75 | ),
76 |
77 | // For nurses of the elder care department.
78 | PolicySet("policyset:9") := when (subject.department === "elder_care") apply DenyOverrides to (
79 | // Of the nurses of the elder care department, only nurses who have been allowed to use the PMS can access the PMS.
80 | Policy("policy:20") := deny iff !subject.allowed_to_access_pms,
81 |
82 | // Nurses of the elder care department can only view the patient status of a patient
83 | // who is currently admitted to their nurse unit and for whome they are assigned responsible.
84 | OnlyPermitIff("policySet:10")(
85 | target = AlwaysTrue,
86 | (resource.owner_id in subject.admitted_patients_in_nurse_unit)
87 | & (resource.owner_id in subject.responsible_patients)
88 | )
89 | )
90 | ),
91 | // For patients
92 | PolicySet("policyset:11") := when ("patient" in subject.roles) apply FirstApplicable to (
93 | // A patient can only access the PMS if (still) allowed by the hospital (e.g., has
94 | // subscribed to the PMS, but is not paying any more).
95 | Policy("policy:23") := deny iff !subject.allowed_to_access_pms,
96 |
97 | // A patient can only view his own status.
98 | Policy("policy:24") := deny iff !(resource.owner_id === subject.id),
99 |
100 | Policy("policy:25") := permit
101 | )
102 | )
103 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/dsl/DSL.scala:
--------------------------------------------------------------------------------
1 | package stapl.core.dsl
2 |
3 | import stapl.core._
4 |
5 | /**
6 | * **************************************
7 | * The more natural DSL for policies and policy sets
8 | *
9 | * Examples for policies:
10 | * Policy("policy1") := when ("role" in subject.roles) deny iff (subject.allowed === false)
11 | * Policy("policy2") := deny iff (subject.allowed === false)
12 | * Policy("policy3") := when ("role" in subject.roles) deny
13 | * Policy("policy4") := deny
14 | *
15 | * Examples for policy sets:
16 | * TODO
17 | *
18 | * FIXME the "policy" in this line should not be possible:
19 | * Policy("view document") := when (action.id === "view" & resource.type_ === "document") permit
20 | * ====== Why not? ====== => because this is a policy and the keyword "permit" should not be in here
21 | *
22 | * TODO does a rule need a target?
23 | */
24 | class OnlyIdRule(private val id: String) {
25 |
26 | def :=(t: EffectConditionAndObligationActions): Rule =
27 | new Rule(id)(t.effect, t.condition, List(t.obligationActions: _*))
28 |
29 | def :=(t: EffectAndCondition): Rule =
30 | new Rule(id)(t.effect, t.condition)
31 |
32 | def :=(t: EffectAndObligationActions): Rule =
33 | new Rule(id)(t.effect, AlwaysTrue, List(t.obligationActions: _*))
34 |
35 | def :=(effectKeyword: EffectKeyword): Rule = effectKeyword match {
36 | case `deny` => new Rule(id)(Deny)
37 | case `permit` => new Rule(id)(Permit)
38 | }
39 |
40 | }
41 |
42 | class OnlyIdPolicy(private val id: String) {
43 |
44 | def :=(t: TargetPCASubpoliciesAndObligations): Policy =
45 | new Policy(id)(t.target, t.pca, t.subpolicies, t.obligations)
46 |
47 | def :=(t: TargetPCAAndSubpolicies): Policy =
48 | new Policy(id)(t.target, t.pca, List(t.subpolicies: _*))
49 | }
50 |
51 | class ObligationActionWithOn(val obligationAction: ObligationAction) {
52 |
53 | def on(effect: Effect): Obligation =
54 | new Obligation(obligationAction, effect)
55 | }
56 |
57 | class EffectAndCondition(val effect: Effect, val condition: Expression) {
58 |
59 | def performing(obligationActions: ObligationAction*): EffectConditionAndObligationActions =
60 | new EffectConditionAndObligationActions(effect, condition, obligationActions: _*)
61 | }
62 |
63 | class EffectConditionAndObligationActions(
64 | val effect: Effect, val condition: Expression, val obligationActions: ObligationAction*)
65 |
66 | class EffectAndObligationActions(
67 | val effect: Effect, val obligationActions: ObligationAction*)
68 |
69 | class EffectKeyword // FIXME this cannot be the best way to do this...
70 | case object deny extends EffectKeyword {
71 | /**
72 | * Needed if no target is given
73 | */
74 | def iff(condition: Expression): EffectAndCondition =
75 | new EffectAndCondition(Deny, condition)
76 |
77 | def performing(obligationActions: ObligationAction*): EffectAndObligationActions =
78 | new EffectAndObligationActions(Deny, obligationActions: _*)
79 | }
80 | case object permit extends EffectKeyword {
81 | /**
82 | * Needed if no target is given
83 | */
84 | def iff(condition: Expression): EffectAndCondition =
85 | new EffectAndCondition(Permit, condition)
86 |
87 | def performing(obligationActions: ObligationAction*): EffectAndObligationActions =
88 | new EffectAndObligationActions(Permit, obligationActions: _*)
89 | }
90 |
91 | class TargetPCAAndSubpolicies(val target: Expression, val pca: CombinationAlgorithm, val subpolicies: AbstractPolicy*) {
92 |
93 | def performing(obligations: Obligation*): TargetPCASubpoliciesAndObligations =
94 | new TargetPCASubpoliciesAndObligations(target, pca, List(subpolicies: _*), List(obligations: _*))
95 | }
96 |
97 | class TargetPCASubpoliciesAndObligations(val target: Expression, val pca: CombinationAlgorithm,
98 | val subpolicies: List[AbstractPolicy], val obligations: List[Obligation])
99 |
100 | class TargetAndPCA(val target: Expression, val pca: CombinationAlgorithm) {
101 |
102 | def to(subpolicies: AbstractPolicy*): TargetPCAAndSubpolicies =
103 | new TargetPCAAndSubpolicies(target, pca, subpolicies: _*)
104 | }
105 |
106 | class OnlyTarget(val target: Expression) {
107 |
108 | def apply(pca: CombinationAlgorithm): TargetAndPCA =
109 | new TargetAndPCA(target, pca)
110 |
111 | }
112 | object when {
113 | def apply(target: Expression = AlwaysTrue): OnlyTarget =
114 | new OnlyTarget(target)
115 | }
116 | object apply {
117 |
118 | /**
119 | * If no target is given for a policy set
120 | */
121 | def apply(pca: CombinationAlgorithm): TargetAndPCA =
122 | new TargetAndPCA(AlwaysTrue, pca)
123 |
124 | def PermitOverrides(subpolicies: OnlySubpolicies): TargetPCAAndSubpolicies =
125 | new TargetPCAAndSubpolicies(AlwaysTrue, stapl.core.PermitOverrides, subpolicies.subpolicies: _*)
126 |
127 | def DenyOverrides(subpolicies: OnlySubpolicies): TargetPCAAndSubpolicies =
128 | new TargetPCAAndSubpolicies(AlwaysTrue, stapl.core.DenyOverrides, subpolicies.subpolicies: _*)
129 |
130 | def FirstApplicable(subpolicies: OnlySubpolicies): TargetPCAAndSubpolicies =
131 | new TargetPCAAndSubpolicies(AlwaysTrue, stapl.core.FirstApplicable, subpolicies.subpolicies: _*)
132 | }
133 | class OnlySubpolicies(val subpolicies: AbstractPolicy*)
134 | object to {
135 |
136 | def apply(subpolicies: AbstractPolicy*): OnlySubpolicies =
137 | new OnlySubpolicies(subpolicies: _*)
138 | }
139 | object iff {
140 | /**
141 | * Just to add the keyword "iff"
142 | */
143 | def apply(condition: Expression): Expression =
144 | condition
145 | }
146 | object Rule { // not really a companion object of Rule, but the start of the natural DSL for policies
147 | def apply(id: String) =
148 | new OnlyIdRule(id)
149 | }
150 | object Policy {
151 | def apply(id: String) =
152 | new OnlyIdPolicy(id)
153 | }
154 |
155 |
156 | object log {
157 | def apply(msg: Value) = new LogObligationAction(msg)
158 | }
159 | object mail {
160 | def apply(to: String, msg: String) = new MailObligationAction(to, msg)
161 | }
162 | /**
163 | * Updating attribute values
164 | */
165 | object update {
166 | def apply(attribute: Attribute, value: Value) =
167 | new ChangeAttributeObligationAction(attribute, value, Update)
168 | }
169 |
170 | /**
171 | * Appending to attribute values
172 | */
173 | object append {
174 | def apply(attribute: Attribute, value: Value) =
175 | new ChangeAttributeObligationAction(attribute, value, Append)
176 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Policy.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import grizzled.slf4j.Logging
23 | import stapl.core.pdp.EvaluationCtx
24 | import scala.concurrent.Future
25 | import concurrent.ExecutionContext.Implicits.global
26 | import scala.concurrent.Promise
27 | import scala.util.{ Try, Success, Failure }
28 |
29 | /**
30 | * *******************************************
31 | * The basic constructors
32 | */
33 | abstract class AbstractPolicy(val id: String) {
34 | var parent: Option[Policy] = None
35 |
36 | /**
37 | * Each element in the policy tree should only return Obligations which
38 | * apply its decision.
39 | */
40 | def evaluate(implicit ctx: EvaluationCtx): Result
41 |
42 | // TODO remove this from AbstractPolicy, Policy and RemotePolicy (this is only to be used
43 | // internally in a Policy)
44 | def isApplicable(implicit ctx: EvaluationCtx): Boolean
45 |
46 | //def allIds: List[String]
47 |
48 | /**
49 | * Returns the ordered list of all ids from the top of the policy tree
50 | * to this element of the policy tree, this element first and working to the top.
51 | */
52 | def treePath: List[String] = parent match {
53 | case Some(parent) => id :: parent.treePath
54 | case None => List(id)
55 | }
56 |
57 | /**
58 | * Returns the fully qualified id of this element of the policy tree.
59 | * This id is the concatenation of all ids of the elements on the tree
60 | * path of this element, starting from the top and working down.
61 | */
62 | def fqid: String = treePath.reverse.mkString(">") // TODO performance optimization: cache this stuff
63 | }
64 |
65 | /**
66 | * Represents one rule.
67 | */
68 | class Rule(id: String)(val effect: Effect,
69 | val condition: Expression = AlwaysTrue, val obligationActions: List[ObligationAction] = List.empty)
70 | extends AbstractPolicy(id) with Logging {
71 |
72 | override def evaluate(implicit ctx: EvaluationCtx): Result = {
73 | debug(s"FLOW: starting evaluation of Policy #$fqid (evaluation id #${ctx.evaluationId})")
74 | if (!isApplicable) {
75 | debug(s"FLOW: Rule #$fqid was NotApplicable because of target")
76 | NotApplicable
77 | } else {
78 | if (condition.evaluate) {
79 | debug(s"FLOW: Rule #$fqid returned $effect with obligations $obligationActions")
80 | Result(effect, obligationActions map { _.getConcrete })
81 | } else {
82 | debug(s"FLOW: Rule #$fqid was NotApplicable because of condition")
83 | NotApplicable
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * Rules always apply
90 | */
91 | override def isApplicable(implicit ctx: EvaluationCtx): Boolean = true
92 |
93 | //override def allIds: List[String] = List(id)
94 |
95 | override def toString = s"Policy #$fqid"
96 | }
97 |
98 | /**
99 | * Represents a policy of one or more rules and/or subpolicies.
100 | */
101 | class Policy(id: String)(val target: Expression = AlwaysTrue, val pca: CombinationAlgorithm,
102 | val subpolicies: List[AbstractPolicy], val obligations: List[Obligation] = List.empty)
103 | extends AbstractPolicy(id) with Logging {
104 |
105 | // assign this PolicySet as parent to the children
106 | subpolicies.foreach(_.parent = Some(this))
107 |
108 | require(!subpolicies.isEmpty, "A PolicySet needs at least one SubPolicy")
109 | //require(uniqueIds, "All policies require a unique ID")
110 |
111 | /*private def uniqueIds(): Boolean = {
112 | val ids = allIds
113 | val distinctIds = ids.distinct
114 | distinctIds.size == ids.size
115 | }*/
116 |
117 | override def evaluate(implicit ctx: EvaluationCtx): Result = {
118 | debug(s"FLOW: starting evaluation of PolicySet #$fqid")
119 | if (isApplicable) {
120 | val result = pca.combine(subpolicies, ctx)
121 | // add applicable obligations of our own
122 | val applicableObligationActions = result.obligationActions ::: obligations.filter(_.fulfillOn == result.decision).map(_.action.getConcrete)
123 | val finalResult = Result(result.decision, applicableObligationActions)
124 | debug(s"FLOW: PolicySet #$fqid returned $finalResult")
125 | finalResult
126 | } else {
127 | debug(s"FLOW: PolicySet #$fqid was NotApplicable because of target")
128 | NotApplicable
129 | }
130 | }
131 |
132 | override def isApplicable(implicit ctx: EvaluationCtx): Boolean = target.evaluate
133 |
134 | //override def allIds: List[String] = id :: subpolicies.flatMap(_.allIds)
135 |
136 | override def toString = {
137 | val subs = subpolicies.toString
138 | s"PolicySet #$id = [${subs.substring(5, subs.length - 1)}]"
139 | }
140 | }
141 |
142 | /**
143 | * Represents a reference to a policy that resides at a remote location and should be evaluated
144 | * at that remote location.
145 | *
146 | * TODO Do remote policy references reference the id or the fqid?
147 | */
148 | case class RemotePolicy(override val id: String) extends AbstractPolicy(id) with Logging {
149 |
150 | override def evaluate(implicit ctx: EvaluationCtx): Result = {
151 | debug(s"FLOW: starting evaluation of Remote Policy #$fqid (evaluation id #${ctx.evaluationId})")
152 | val result = ctx.remoteEvaluator.findAndEvaluate(id, ctx)
153 | // TODO Filter obligations?
154 | debug(s"FLOW: Remote Policy #$fqid returned $result")
155 | result
156 | }
157 |
158 | /**
159 | * This method shouldn't be called during the evaluation of this policy, but an implementation is
160 | * provided for the case where one would like to know whether a policy is applicable in a certain
161 | * EvalutionCtx without evaluating it (???).
162 | */
163 | override def isApplicable(implicit ctx: EvaluationCtx): Boolean = {
164 | warn("We are checking whether a remote policy is applicable without evaluating the policy. Are you sure this is what you want to do?")
165 | ctx.remoteEvaluator.findAndIsApplicable(id, ctx)
166 | }
167 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/pdp/EvaluationCtx.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core.pdp
21 |
22 | import grizzled.slf4j.Logging
23 | import stapl.core.ConcreteValue
24 | import stapl.core.Attribute
25 | import stapl.core.AttributeContainerType
26 | import stapl.core.AttributeNotFoundException
27 | import stapl.core.PermitOverrides
28 | import stapl.core.DenyOverrides
29 | import stapl.core.FirstApplicable
30 | import stapl.core.CombinationAlgorithm
31 | import stapl.core.CombinationAlgorithmImplementation
32 | import stapl.core.CombinationAlgorithmImplementationBundle
33 | import stapl.core.SimpleCombinationAlgorithmImplementationBundle
34 | import scala.concurrent.Future
35 | import scala.concurrent.blocking
36 | import scala.concurrent.ExecutionContext.Implicits.global
37 | import stapl.core.CombinationAlgorithmImplementationBundle
38 | import scala.util.{ Try, Success, Failure }
39 | import stapl.core.SUBJECT
40 | import stapl.core.RESOURCE
41 | import stapl.core.ACTION
42 | import stapl.core.ENVIRONMENT
43 |
44 | /**
45 | * The base class of the context for evaluating a policy. This context
46 | * represents all information for that policy evaluation, such as the
47 | * id of the subject, the id of the resource, the id of the action and
48 | * a method to find attributes.
49 | *
50 | * The method to find attributes is required in the evaluation context
51 | * because certain aspects such as an attribute cache are specific for
52 | * each individual evaluation context.
53 | */
54 | trait EvaluationCtx {
55 |
56 | def evaluationId: String
57 | def subjectId: String
58 | def resourceId: String
59 | def actionId: String
60 | def remoteEvaluator: RemoteEvaluator
61 | def cachedAttributes: Map[Attribute, ConcreteValue]
62 | def employedAttributes: Map[Attribute, ConcreteValue]
63 | protected[core] def findAttribute(attribute: Attribute): ConcreteValue
64 | protected[core] def getCombinationAlgorithmImplementation(algo: CombinationAlgorithm): CombinationAlgorithmImplementation
65 |
66 | // TODO add type checking here
67 | //final def findAttribute(attribute: Attribute): ConcreteValue =
68 | }
69 |
70 | /**
71 | * An implementation of a basic evaluation context. This evaluation context
72 | * stores the subject id, the resource id, the action id and stores found
73 | * attribute values in a cache for this evaluation context.
74 | */
75 | class BasicEvaluationCtx(override val evaluationId: String, request: RequestCtx,
76 | finder: AttributeFinder, override val remoteEvaluator: RemoteEvaluator,
77 | bundle: CombinationAlgorithmImplementationBundle = SimpleCombinationAlgorithmImplementationBundle) extends EvaluationCtx with Logging {
78 |
79 | override val subjectId: String = request.subjectId
80 |
81 | override val resourceId: String = request.resourceId
82 |
83 | override val actionId: String = request.actionId
84 |
85 | protected val attributeCache: scala.collection.mutable.Map[Attribute, ConcreteValue] = scala.collection.mutable.Map() //scala.collection.concurrent.TrieMap()
86 |
87 | override def cachedAttributes: Map[Attribute, ConcreteValue] = attributeCache.toMap
88 |
89 | // add all attributes given in the request to the attribute cache
90 | for ((attribute, value) <- request.allAttributes) {
91 | attributeCache(attribute) = value
92 | }
93 |
94 | protected val _employedAttributes: scala.collection.mutable.Map[Attribute, ConcreteValue] = scala.collection.mutable.Map()
95 |
96 | override def employedAttributes = _employedAttributes.toMap
97 |
98 | /**
99 | * Try to find the value of the given attribute. If the value is already
100 | * in the attribute cache, that value is returned. Otherwise, the attribute
101 | * finder is checked and the found value is stored in the attribute cache if
102 | * a value is found.
103 | *
104 | * @throws AttributeNotFoundException If the attribute value isn't found
105 | */
106 | @throws[AttributeNotFoundException]("if the attribute value isn't found")
107 | override def findAttribute(attribute: Attribute): ConcreteValue = {
108 | attributeCache.get(attribute) match {
109 | case Some(value) => {
110 | debug("FLOW: found value of " + attribute + " in cache: " + value)
111 | _employedAttributes(attribute) = value
112 | value
113 | }
114 | case None => { // Not in the cache
115 | finder.find(this, attribute) match {
116 | case None =>
117 | val entityId = attribute.cType match {
118 | case SUBJECT => subjectId
119 | case RESOURCE => resourceId
120 | case ACTION => "ACTION??" // we don't support this
121 | case ENVIRONMENT => "ENVIRONMENT??" // we don't support this
122 | }
123 | debug(s"Didn't find value of $attribute for entity $entityId anywhere, throwing exception")
124 | throw new AttributeNotFoundException(evaluationId, entityId, attribute)
125 | case Some(value) =>
126 | attributeCache(attribute) = value // add to cache
127 | _employedAttributes(attribute) = value
128 | debug("FLOW: retrieved value of " + attribute + ": " + value + " and added to cache")
129 | value
130 | }
131 | }
132 | }
133 | }
134 |
135 | /**
136 | * To make sure that we only request an attribute once from the database, we
137 | * store all futures regarding a certain attribute. This way, we can return this
138 | * future if an attribute is requested again after the first time.
139 | */
140 | private val attributeFutures = scala.collection.mutable.Map[Attribute, Future[Try[ConcreteValue]]]()
141 |
142 | // immediately fill these attribute futures with the futures for the cached attributes
143 | // to simplify the rest of the code
144 | for ((attribute, value) <- request.allAttributes) {
145 | attributeFutures(attribute) = Future successful Success(value)
146 | }
147 |
148 | /**
149 | * Return the implementation of the requested combination algorithm.
150 | */
151 | def getCombinationAlgorithmImplementation(algo: CombinationAlgorithm): CombinationAlgorithmImplementation = algo match {
152 | case PermitOverrides => bundle.PermitOverrides
153 | case DenyOverrides => bundle.DenyOverrides
154 | case FirstApplicable => bundle.FirstApplicable
155 | }
156 | }
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/Types.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import java.util.Date
23 |
24 |
25 | abstract class AttributeType {
26 |
27 | def addition(t: AttributeType): Option[AttributeType] = None
28 | def subtraction(t: AttributeType): Option[AttributeType] = None
29 | def multiplication(t: AttributeType): Option[AttributeType] = None
30 | def division(t: AttributeType): Option[AttributeType] = None
31 | def absoluteValue(): Option[AttributeType] = None
32 | }
33 |
34 | object AttributeType {
35 |
36 | @throws[TypeCheckException]("if type found doesn't conform to type expected")
37 | def checkType(found: AttributeType, expected: AttributeType) {
38 | if(found != expected) throw new TypeCheckException(found, expected)
39 | }
40 | }
41 |
42 |
43 |
44 | case object String extends AttributeType {
45 |
46 | override def addition(t: AttributeType): Option[AttributeType] = t match {
47 | case String => Some(String)
48 | case _ => None
49 | }
50 | }
51 |
52 | case object Number extends AttributeType {
53 |
54 | override def addition(t: AttributeType): Option[AttributeType] = t match {
55 | case Number => Some(Number)
56 | case _ => None
57 | }
58 |
59 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
60 | case Number => Some(Number)
61 | case _ => None
62 | }
63 |
64 | override def multiplication(t: AttributeType): Option[AttributeType] = t match {
65 | case Number => Some(Number)
66 | case _ => None
67 | }
68 |
69 | override def division(t: AttributeType): Option[AttributeType] = t match {
70 | case Number => Some(Number)
71 | case _ => None
72 | }
73 |
74 | override def absoluteValue(): Option[AttributeType] = Some(Number)
75 | }
76 |
77 | case object Bool extends AttributeType
78 |
79 |
80 | case object DateTime extends AttributeType {
81 | def apply(year:Int, month:Int, day:Int, hours:Int, minutes:Int, seconds:Int, millis:Int) : DateTimeImpl =
82 | new DateTimeImpl(year,month,day,hours,minutes,seconds,millis)
83 |
84 | def apply(year:Int, month:Int, day:Int, hours:Int, minutes:Int, seconds:Int) : DateTimeImpl =
85 | apply(year, month, day, hours, minutes, seconds, 0)
86 |
87 |
88 | override def addition(t: AttributeType): Option[AttributeType] = t match {
89 | case DateTimeDuration => Some(DateTime)
90 | case DayDuration => Some(DateTime)
91 | case TimeDuration => Some(DateTime)
92 | case _ => None
93 | }
94 |
95 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
96 | case DateTimeDuration => Some(DateTime)
97 | case DayDuration => Some(DateTime)
98 | case TimeDuration => Some(DateTime)
99 | case DateTime => Some(DateTimeDuration)
100 | case _ => None
101 | }
102 | }
103 |
104 | case object Time extends AttributeType {
105 | def apply(hours:Int, minutes:Int, seconds:Int, millis:Int) : TimeImpl =
106 | new TimeImpl(hours,minutes,seconds,millis)
107 |
108 | def apply(hours:Int, minutes:Int, seconds:Int) : TimeImpl =
109 | new TimeImpl(hours,minutes,seconds,0)
110 |
111 |
112 | override def addition(t: AttributeType): Option[AttributeType] = t match {
113 | case TimeDuration => Some(Time)
114 | case _ => None
115 | }
116 |
117 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
118 | case TimeDuration => Some(Time)
119 | case Time => Some(TimeDuration)
120 | case _ => None
121 | }
122 | }
123 |
124 | case object Day extends AttributeType {
125 | def apply(year:Int, month:Int, day:Int): DayImpl = new DayImpl(year,month,day)
126 |
127 |
128 | override def addition(t: AttributeType): Option[AttributeType] = t match {
129 | case DayDuration => Some(Day)
130 | case _ => None
131 | }
132 |
133 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
134 | case DayDuration => Some(Day)
135 | case Day => Some(DayDuration)
136 | case _ => None
137 | }
138 | }
139 |
140 |
141 | case object DateTimeDuration extends AttributeType {
142 | def apply(years: Int, months: Int, days: Int, hours: Int, minutes: Int, seconds: Int, millis: Int): DateTimeDurationImpl =
143 | new DateTimeDurationImpl(years,months,days,hours,minutes,seconds,millis)
144 |
145 | def apply(years: Int, months: Int, days: Int, hours: Int, minutes: Int, seconds: Int): DateTimeDurationImpl =
146 | apply(years, months, days, hours, minutes, seconds, 0)
147 |
148 |
149 | override def addition(t: AttributeType): Option[AttributeType] = t match {
150 | case DateTimeDuration => Some(DateTimeDuration)
151 | case DayDuration => Some(DateTimeDuration)
152 | case TimeDuration => Some(DateTimeDuration)
153 | case _ => None
154 | }
155 |
156 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
157 | case DateTimeDuration => Some(DateTimeDuration)
158 | case DayDuration => Some(DateTimeDuration)
159 | case TimeDuration => Some(DateTimeDuration)
160 | case _ => None
161 | }
162 | }
163 |
164 | case object TimeDuration extends AttributeType {
165 | def apply(hours: Int, minutes: Int, seconds: Int, millis: Int): TimeDurationImpl =
166 | new TimeDurationImpl(hours,minutes,seconds,millis)
167 |
168 | def apply(hours: Int, minutes: Int, seconds: Int): TimeDurationImpl =
169 | apply(hours,minutes,seconds,0)
170 |
171 |
172 | override def addition(t: AttributeType): Option[AttributeType] = t match {
173 | case DateTimeDuration => Some(DateTimeDuration)
174 | case DayDuration => Some(DateTimeDuration)
175 | case TimeDuration => Some(TimeDuration)
176 | case _ => None
177 | }
178 |
179 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
180 | case DateTimeDuration => Some(DateTimeDuration)
181 | case DayDuration => Some(DateTimeDuration)
182 | case TimeDuration => Some(TimeDuration)
183 | case _ => None
184 | }
185 | }
186 |
187 | case object DayDuration extends AttributeType {
188 | def apply(years:Int, months:Int, days:Int): DayDurationImpl = new DayDurationImpl(years,months,days)
189 |
190 |
191 | override def addition(t: AttributeType): Option[AttributeType] = t match {
192 | case DateTimeDuration => Some(DateTimeDuration)
193 | case DayDuration => Some(DayDuration)
194 | case TimeDuration => Some(DateTimeDuration)
195 | case _ => None
196 | }
197 |
198 | override def subtraction(t: AttributeType): Option[AttributeType] = t match {
199 | case DateTimeDuration => Some(DateTimeDuration)
200 | case DayDuration => Some(DayDuration)
201 | case TimeDuration => Some(DateTimeDuration)
202 | case _ => None
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/resources/policies/ehealth.stapl:
--------------------------------------------------------------------------------
1 | new PolicySet("jisa13-final3")(
2 | target = action.id === "view" & resource.type_ === "patientstatus",
3 | pca = DenyOverrides,
4 | subpolicies = List(
5 | // The consent policy.
6 | new PolicySet("policy:1")(
7 | target = "medical_personnel" in subject.roles,
8 | pca = PermitOverrides,
9 | subpolicies = List(
10 | new Policy("consent")(
11 | target = AlwaysTrue,
12 | effect = Deny,
13 | condition = subject.id in resource.owner_withdrawn_consents),
14 | new Policy("breaking-glass")(
15 | target = AlwaysTrue,
16 | effect = Permit,
17 | condition = subject.triggered_breaking_glass,
18 | obligationActions = List(log(subject.id + " performed breaking-the-glass procedure")))
19 | ),
20 | obligations = List(
21 | new Obligation(log("just another log on Permit"), Permit)
22 | )
23 | ),
24 |
25 | // Only physicians, nurses and patients can access the monitoring system.
26 | new Policy("policy:2")(
27 | target = AlwaysTrue,
28 | effect = Deny,
29 | condition = !(("nurse" in subject.roles) | ("physician" in subject.roles) | ("patient" in subject.roles))),
30 |
31 | // For physicians.
32 | new PolicySet("policyset:2")(
33 | target = "physician" in subject.roles,
34 | pca = FirstApplicable,
35 | subpolicies = List(
36 | // Of the physicians, only gps, physicians of the cardiology department, physicians of the elder care department and physicians of the emergency department can access the monitoring system.
37 | new Policy("policy:3")(
38 | target = AlwaysTrue,
39 | effect = Deny,
40 | condition = !((subject.department === "cardiology") | (subject.department === "elder_care") | (subject.department === "emergency") | ("gp" in subject.roles))),
41 |
42 | // All of the previous physicians can access the monitoring system in case of emergency.
43 | new Policy("policy:4")(
44 | target = (subject.department === "cardiology") | (subject.department === "elder_care") | (subject.department === "emergency"),
45 | effect = Permit,
46 | condition = (subject.triggered_breaking_glass | resource.operator_triggered_emergency | resource.indicates_emergency)),
47 |
48 | // For GPs: only permit if in consultation or treated in the last six months or primary physician or responsible in the system.
49 | OnlyPermitIff("policyset:3")(
50 | target = "gp" in subject.roles,
51 | (resource.owner_id === subject.current_patient_in_consultation)
52 | | (resource.owner_id in subject.treated_in_last_six_months)
53 | | (resource.owner_id in subject.primary_patients)
54 | | (subject.id in resource.owner_responsible_physicians)
55 | ),
56 |
57 | // For cardiologists.
58 | new PolicySet("policyset:4")(
59 | target = subject.department === "cardiology",
60 | pca = PermitOverrides,
61 | subpolicies = List(
62 | // Permit for head physician.
63 | new Policy("policy:7")(
64 | target = subject.is_head_physician,
65 | effect = Permit),
66 |
67 | // Permit if treated the patient or treated in team.
68 | new Policy("policy:8")(
69 | target = (resource.owner_id in subject.treated) | (resource.owner_id in subject.treated_by_team),
70 | effect = Permit),
71 |
72 | defaultDeny("policy:9")
73 | )
74 | ),
75 |
76 | // For physicians of elder care department: only permit if admitted in care unit or treated in the last six months.
77 | OnlyPermitIff("policyset:5")(
78 | target = subject.department === "elder_care",
79 | (resource.owner_id in subject.admitted_patients_in_care_unit)
80 | | (resource.owner_id in subject.treated_in_last_six_months)
81 | ),
82 |
83 | // For physicians of emergency department: only permit if patient status is bad (or the above).
84 | OnlyPermitIff("policyset:6")(
85 | target = subject.department === "emergency",
86 | resource.patient_status === "bad"
87 | )
88 | )
89 | ),
90 |
91 | // For nurses.
92 | new PolicySet("policyset:7")(
93 | target = "nurse" in subject.roles,
94 | pca = FirstApplicable,
95 | subpolicies = List(
96 | // Of the nurses, only nurses of the cardiology department or the elder care department can access the PMS.
97 | new Policy("policy:14")(
98 | target = !((subject.department === "cardiology") | (subject.department === "elder_care")),
99 | effect = Deny),
100 |
101 | // Nurses can only access the PMS during their shifts.
102 | new Policy("policy:15")(
103 | target = !((env.currentDateTime gteq subject.shift_start) & (env.currentDateTime lteq subject.shift_stop)),
104 | effect = Deny),
105 |
106 | // Nurses can only access the PMS from the hospital.
107 | new Policy("policy:16")(
108 | target = !(subject.location === "hospital"),
109 | effect = Deny),
110 |
111 | // Nurses can only view the patient's status of the last five days.
112 | new Policy("policy:17")(
113 | target = !(env.currentDateTime lteq (resource.created + 5.days)),
114 | effect = Deny),
115 |
116 | // For nurses of cardiology department: they can only view the patient status of a patient
117 | // in their nurse unit for whom they are assigned responsible, up to three days after they were discharged.
118 | OnlyPermitIff("policyset:8")(
119 | target = subject.department === "cardiology",
120 | (resource.owner_id in subject.admitted_patients_in_nurse_unit)
121 | & (!resource.owner_discharged | (env.currentDateTime lteq (resource.owner_discharged_dateTime + 3.days)))
122 | ),
123 |
124 | // For nurses of the elder care department.
125 | new PolicySet("policyset:9")(
126 | target = subject.department === "elder_care",
127 | pca = DenyOverrides,
128 | subpolicies = List(
129 | // Of the nurses of the elder care department, only nurses who have been allowed to use the PMS can access the PMS.
130 | new Policy("policy:20")(
131 | target = !subject.allowed_to_access_pms,
132 | effect = Deny),
133 |
134 | // Nurses of the elder care department can only view the patient status of a patient
135 | // who is currently admitted to their nurse unit and for whome they are assigned responsible.
136 | OnlyPermitIff("policySet:10")(
137 | target = AlwaysTrue,
138 | (resource.owner_id in subject.admitted_patients_in_nurse_unit)
139 | & (resource.owner_id in subject.responsible_patients)
140 | )
141 | )
142 | )
143 | )
144 | ),
145 | // For patients
146 | new PolicySet("policyset:11")(
147 | target = "patient" in subject.roles,
148 | pca = FirstApplicable,
149 | subpolicies = List(
150 | // A patient can only access the PMS if (still) allowed by the hospital (e.g., has
151 | // subscribed to the PMS, but is not paying any more).
152 | new Policy("policy:23")(
153 | target = !subject.allowed_to_access_pms,
154 | effect = Deny),
155 |
156 | // A patient can only view his own status.
157 | new Policy("policy:24")(
158 | target = !(resource.owner_id === subject.id),
159 | effect = Deny),
160 |
161 | defaultPermit("policy:25")
162 | )
163 | )
164 | )
165 | )
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # STAPL
2 |
3 | STAPL or the Simple Tree-structured Attribute-based Policy Language is a Scala DSL designed to express and evaluate XACML-like policies easily. STAPL provides a human-friendly policy syntax and an efficient evaluation engine that is easy to test and use in Scala and Java applications.
4 |
5 | For quickly getting started with STAPL, check out the [Quick Start][1] below.
6 |
7 | ### Related projects
8 |
9 | This repository hosts `stapl-core`. Some other projects extend the core:
10 |
11 | * [stapl-getting-started][2] provides a simple Maven project to quickly get you started with STAPL. For more information about this, see [below][3].
12 | * [stapl-templates](https://github.com/maartendecat/stapl-templates) provides a number of policy templates that make it easier to write policies.
13 | * [stapl-java-api][4] provides a simple Java wrapper for the STAPL policy engine. More information [below][5].
14 | * [stapl-examples](https://github.com/maartendecat/stapl-examples) provides examples of using STAPL, the templates and the Java API.
15 | * [stapl-performance](https://github.com/maartendecat/stapl-performance) provides some performance tests.
16 |
17 | # Example
18 |
19 | Here is a simple example of a STAPL policy based on an e-health scenario:
20 |
21 | ```scala
22 | package mypackage
23 |
24 | import stapl.core._
25 |
26 | object PolicyFromTheReadMe extends BasicPolicy {
27 | import stapl.core.dsl._
28 |
29 | subject.roles = ListAttribute(String)
30 | subject.treated = ListAttribute(String)
31 | resource.type_ = SimpleAttribute(String)
32 | resource.owner_id = SimpleAttribute(String)
33 | // action.id is defined by STAPL itself
34 |
35 | // Permit only if the physician treated the owner of the patient data.
36 | val policy = Policy("e-health example") := when ((action.id === "view") & (resource.type_ === "patient-data")
37 | & ("physician" in subject.roles)) apply PermitOverrides to (
38 | Rule("requirement-for-permit") := permit iff (resource.owner_id in subject.treated),
39 | Rule("default deny") := deny
40 | )
41 | }
42 | ```
43 |
44 | Quite consice and readable, no? As comparison, here is the XACML representation of this same policy:
45 |
46 | ```xml
47 |
49 | Permit only if the physician treated the owner of the patient data.
50 |
51 |
52 |
53 |
54 | view
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | patient-data
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | physician
71 |
72 |
73 |
74 |
75 |
76 |
77 | Permit if the physician treated the owner of the patient data.
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Deny otherwise
89 |
90 |
91 | ```
92 |
93 | For more examples, see the [stapl-examples](https://github.com/maartendecat/stapl-examples) project.
94 |
95 | # Features
96 |
97 | #### Simple and concise syntax
98 | No more need for XML copy-pasting.
99 |
100 | #### Policy references
101 | These allow you to build large policies modularly. If you want, you can even use the Scala package system to structure large policy sets.
102 |
103 | #### Policy templates
104 | These allow you increase modularity even more by encapsulating common policy patterns.
105 |
106 | #### Run-time policy loading
107 | STAPL policies can be compiled just as any other Scala and Java code. However, your application should not be recompiled for every policy change. Therefore, STAPL allows you to load policies at run-time. If you would like to know, this is done internally using the Scala interpreter.
108 |
109 | #### Efficient policy evaluation
110 | Compared to [our optimized version of the SunXACML engine][6], STAPL policies can be evaluated multiple times faster than their XACML equivalents.
111 |
112 | #### Extensive logging
113 | Wondering why a certain decision is Permit or Deny? Turn on logging and get an overview of the whole policy evaluation process.
114 |
115 | ### Future-plans
116 |
117 | Towards the future, we plan to add the following features.
118 |
119 | #### Stand-alone deployment
120 | The STAPL evaluation engine can be compiled directly into your Scala or Java application. However, for other languages, we plan to allow the engine to be deployed stand-alone and reached using [Apache Thrift][7].
121 |
122 |
123 |
124 | # Employing the engine in Scala
125 |
126 | To get an access control decision, simply load the policy in the policy decision point (PDP) and ask for a decision:
127 |
128 | ```scala
129 | import mypackage.PolicyFromTheReadMe.policy
130 | val pdp = new PDP(policy)
131 | pdp.evaluate("john", "view", "doc123",
132 | subject.roles -> List("physician"),
133 | subject.treated -> List("patient1", "patient2"),
134 | resource.type_ -> "patient-data",
135 | resource.owner_id -> "patient3") // so this will result in a Deny
136 | }
137 | ```
138 |
139 | If you cannot provide all attributes with the decision request or want to dynamically fetch attributes from a database, write your own AttributeFinderModule and add it to the PDP:
140 |
141 | ```scala
142 | class MyAttributeFinderModule extends AttributeFinderModule {
143 |
144 | override def find(ctx: EvaluationCtx, cType: AttributeContainerType, name: String, aType: AttributeType):
145 | // fetch the attribute from the database and return it
146 | ...
147 | }
148 |
149 | val finder = new AttributeFinder
150 | finder += new MyAttributeFinderModule
151 | val pdp = new PDP(policy, finder)
152 | ```
153 |
154 | # Employing the engine in Java
155 |
156 | Because STAPL is implemented in Scala, you're best off with writing your policies in Scala. However, the policy evaluation engine can easily be used from Java as well. In theory, Scala classes can directly be used in Java. In practice however, this leads to some annoying boiler-plate code. Therefore we also provide a simple API that wraps the STAPL evaluation engine in a clean Java API, see [https://github.com/maartendecat/stapl-java-api][8].
157 |
158 | Here is an example of using the Java API (taken from the [examples](https://github.com/maartendecat/stapl-examples/tree/master/src/main/scala/stapl/examples/javaapi)):
159 |
160 | ```java
161 | package stapl.examples.javaapi;
162 |
163 | import static stapl.javaapi.pdp.attributevalues.AttributeValueFactory.list;
164 | import static stapl.javaapi.pdp.attributevalues.AttributeValueFactory.simple;
165 | import stapl.core.AttributeContainer;
166 | import stapl.core.Policy;
167 | import stapl.core.Result;
168 | import stapl.examples.policies.EdocsPolicy;
169 | import stapl.javaapi.pdp.PDP;
170 |
171 | public class SimpleExample {
172 |
173 | public static void main(String[] args) {
174 | /* Java imitation of the following Scala code:
175 |
176 | println(pdp.evaluate("subject1", "view", "resource1",
177 | subject.role -> List("helpdesk"),
178 | subject.tenant_name -> List("provider"),
179 | subject.tenant_type -> List("provider"),
180 | subject.assigned_tenants -> List("tenant1","tenant3"),
181 | resource.type_ -> "document",
182 | resource.owning_tenant -> "tenant4",
183 | resource.confidential -> false))
184 | */
185 |
186 | Policy policy = EdocsPolicy.policy();
187 | AttributeContainer subject = EdocsPolicy.subject();
188 | AttributeContainer resource = EdocsPolicy.resource();
189 | AttributeContainer action = EdocsPolicy.action();
190 | AttributeContainer env = EdocsPolicy.environment();
191 |
192 | PDP pdp = new PDP(policy);
193 | Result result = pdp.evaluate("subject1", "view", "resource1",
194 | list(subject.get("role"), "helpdesk"),
195 | list(subject.get("tenant_name"), "provider"),
196 | list(subject.get("tenant_type"), "provider"),
197 | list(subject.get("assigned_tenants"), "tenant1", "tenant2"),
198 | simple(resource.get("type_"), "document"),
199 | simple(resource.get("owning_tenant"), "tenant4"),
200 | simple(resource.get("confidential"), false));
201 | System.out.println(result);
202 | }
203 |
204 | }
205 | ```
206 |
207 | For more examples, check the package `stapl.javaapi.examples`.
208 |
209 | # Unit testing the policy
210 |
211 | Using the interface to the policy engine shown above, STAPL policies can easily be tested for correctness in JUnit unit tests:
212 |
213 | ```scala
214 | assert(pdp.evaluate("subject1", "view", "resource1",
215 | ... // any attributes for the test
216 | ) === Result(Permit, List(
217 | ... // any obligations
218 | )
219 | ))
220 | ```
221 |
222 | For elaborate examples of policy testing, see the tests of [stapl-examples](https://github.com/maartendecat/stapl-examples).
223 |
224 | # Logging
225 |
226 | Wondering why a certain decision is Permit or Deny? STAPL can provide you with a detailed overview of the whole policy evaluation process. For example, for the policy above, this could be the logging output:
227 |
228 | ```
229 | 09:36:46.191 [main] DEBUG stapl.core.Policy - FLOW: starting evaluation of PolicySet #e-health example
230 | 09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(ACTION,id,String) in cache: view
231 | 09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(RESOURCE,type_,String) in cache: patient-data
232 | 09:36:46.194 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of ListAttribute(SUBJECT,roles,String) in cache: List(physician)
233 | 09:36:46.196 [main] DEBUG stapl.core.Rule - FLOW: starting evaluation of Policy #e-health example>requirement-for-permit
234 | 09:36:46.196 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of SimpleAttribute(RESOURCE,owner_id,String) in cache: patientX
235 | 09:36:46.196 [main] DEBUG stapl.core.pdp.BasicEvaluationCtx - FLOW: found value of ListAttribute(SUBJECT,treated,String) in cache: List(patientX)
236 | 09:36:46.196 [main] DEBUG stapl.core.Rule - FLOW: Policy #e-health example>requirement-for-permit returned Permit with obligations List()
237 | 09:36:46.198 [main] DEBUG stapl.core.Policy - FLOW: PolicySet #e-health example returned Result(Permit,List())
238 | ```
239 |
240 | By default, logging is turned off. To turn it on, edit the file `stapl-core/src/main/resources/logback.xml` by changing the line
241 |
242 | ```XML
243 |
244 | ```
245 |
246 | to
247 |
248 | ```XML
249 |
250 | ```
251 |
252 | # Getting started
253 |
254 | Here are a few simple steps you can follow to get started with STAPL. If you want to skip even this explanation and want to dive right into STAPL, the [stapl-getting-started][9] project contains the resulting Maven project, just `git clone` it.
255 |
256 | #### 1. Install Scala
257 |
258 | STAPL was built using Scala 2.10. How to install Scala depends on your operating system. For Fedora:
259 |
260 | ```
261 | > sudo yum install scala
262 | ```
263 |
264 | #### 2. Get a good Scala editor
265 |
266 | If you don't know where to look, try [http://scala-ide.org/][10].
267 |
268 | #### 2. Get STAPL and install it locally in Maven
269 |
270 | ```
271 | > git clone https://github.com/stapl-dsl/stapl-core
272 | > cd stapl-core
273 | > mvn install
274 | ```
275 |
276 | #### 3. Create a new Scala Maven project
277 |
278 | In the Scala IDE: Create a new Maven project with archetype `scala-archetype-simple` of group `net.alchim31.maven`. For more information: http://scala-ide.org/docs/tutorials/m2eclipse/
279 |
280 | #### 4. Add the STAPL dependency
281 |
282 | Add the following lines to the `` section in your `pom.xml`:
283 |
284 | ```XML
285 |
286 | stapl
287 | stapl-core
288 | 0.0.1-SNAPSHOT
289 |
290 | ```
291 |
292 | #### 5. Create a new policy
293 |
294 | The simplest way to create a new policy is to declare a Scala `object` and assign the policy to a `val`. This way, you can easily access the policy from other parts of your code, such as the policy decision point.
295 |
296 | For example, add this simple policy to the end of the `App.scala` file generated by the Scala IDE:
297 |
298 | ```scala
299 | import stapl.core._
300 |
301 | object ExamplePolicy extends BasicPolicy {
302 | import stapl.core.dsl._
303 |
304 | subject.roles = ListAttribute(String)
305 |
306 | val policy = Policy("example policy") := when (action.id === "view") apply PermitOverrides to (
307 | Rule("permit physicians") := permit iff ("physician" in subject.roles),
308 | Rule("default deny") := deny
309 | )
310 | }
311 | ```
312 |
313 | Explanation:
314 |
315 | * The import `stapl.core._` imports the main part of STAPL, such as the `Policy` and `Rule` classes and the implicit conversions of strings and ints to STAPL attributes.
316 | * The `BasicPolicy` class is a convenience class that avoids some boiler-plate code in your own policies. For example, by extending it, you automatically have `subject`, `resource`, `action` and `environment` declared in your scope.
317 | * `subject.roles = ListAttribute(String)` declares that subjects have an attribute called `Roles`, that is is multi-valued and that is is of the type `String`. After this declaration, you can use `subject.roles` in your policies.
318 | * The policy itself is as explained above. As you can see, it's a simple policy that only applies to the action `view`, permits physicians using the `role` attribute and denies all other subjects.
319 | * The assignment of the policy to a `val` is required to be able to access the policy from another part of your code, as shown in the example below.
320 |
321 | #### 6. Use your new policy
322 |
323 | Now that we have declared a new policy, you can use it by importing it, putting it in a policy decision point (PDP) and request an access decision.
324 |
325 | For example, let's test the policy in the `main` method generated by the Scala IDE. First, add the following import to the top of `App.scala` (but below the `package` declaration):
326 |
327 | ```scala
328 | import stapl.core.pdp._
329 | ```
330 |
331 | Then change the main method of `object App` to the following:
332 |
333 | ```scala
334 | def main(args : Array[String]) {
335 | import ExamplePolicy._
336 | val pdp = new PDP(policy)
337 | println(pdp.evaluate("subject1", "view", "resource1",
338 | subject.roles -> List("physician"))) // will return Permit
339 | println(pdp.evaluate("subject1", "view", "resource1",
340 | subject.roles -> List("another role"))) // will return Deny
341 | println(pdp.evaluate("subject1", "another action", "resource1",
342 | subject.roles -> List("physician"))) // will return NotApplicable
343 | }
344 | ```
345 |
346 | Explanation:
347 |
348 | * The import imports everything we declared in `ExamplePolicy` into the current scope. Most importantly, this includes `subject,` `resource`, `action`, `env` and `policy`.
349 | * The next line constructs a simple policy decision point with our example policy.
350 | * The next lines request access decisions from the policy decision point, which will evaluate our example policy with the given attributes. The first evaluation will return Permit because of the `view` action and the role `physician`. The second evaluation will return Deny because of the wrong role. The third evaluation will return NotApplicable because of the incorrect action id.
351 |
352 | Some important notes here:
353 |
354 | * Notice that you always need to provide the id of the subject, the id of the action and the id of the resource. This is needed because these ids are required for fetching other attributes of them should we incorporate and attribute database or something similar. After these ids, you can pass any number of attributes you want.
355 | * Notice that we did not include any `AttributeFinder`s. These can be used to allow the PDP to search for attributes you did not pass with the request at run-time, for example in a database. For more information, take a look [above][11].
356 | * Notice that the PDP does not just return the decision, but also an empty list. This list contains the obligations which should be fulfilled with enforcing the access decision. It is empty because our example policy did not incorporate any obligations. For examples of obligations, check out the E-health policy in `stapl.core.examples`.
357 |
358 | #### 7. Next
359 |
360 | Try out some stuff yourself. If you want inspiration, take a look at the more elaborate examples in [stapl-examples](https://github.com/maartendecat/stapl-examples). Also take a look at the policy templates in [stapl-templates](https://github.com/maartendecat/stapl-templates), they make it even easier for you to specify policies.
361 |
362 |
363 |
364 | [1]: #getting-started
365 | [2]: https://github.com/maartendecat/stapl-getting-started/
366 | [3]: #getting-started
367 | [4]: https://github.com/maartendecat/stapl-java-api
368 | [5]: #employing-the-engine-in-java
369 | [6]: https://github.com/PUMA-IAM/puma-sunxacml
370 | [7]: https://thrift.apache.org/
371 | [8]: https://github.com/maartendecat/stapl-java-api
372 | [9]: https://github.com/maartendecat/stapl-getting-started/
373 | [10]: http://scala-ide.org/
374 | [11]: #employing-the-engine-in-scala
375 |
--------------------------------------------------------------------------------
/src/main/scala/stapl/core/ConcreteValues.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 KU Leuven Research and Developement - iMinds - Distrinet
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * Administrative Contact: dnet-project-office@cs.kuleuven.be
17 | * Technical Contact: maarten.decat@cs.kuleuven.be
18 | * Author: maarten.decat@cs.kuleuven.be
19 | */
20 | package stapl.core
21 |
22 | import org.joda.time.LocalTime
23 | import org.joda.time.LocalDate
24 | import org.joda.time.LocalDateTime
25 | import org.joda.time.Period
26 | import stapl.core.pdp.EvaluationCtx
27 | import scala.concurrent.Future
28 | import scala.concurrent.ExecutionContext.Implicits.global
29 | import scala.util.{Try, Success, Failure}
30 |
31 | //object ConcreteValue {
32 | //
33 | // def unapply(value: Value)(implicit ctx: EvaluationCtx): Option[ConcreteValue] =
34 | // Option(value.getConcreteValue(ctx))
35 | //}
36 | //
37 | //object Representation {
38 | //
39 | // def unapply(value: Value)(implicit ctx: EvaluationCtx): Option[Any] =
40 | // Option(value.getConcreteValue(ctx).representation)
41 | //}
42 |
43 | trait ConcreteValue extends Value with Serializable {
44 |
45 | override def getConcreteValue(ctx: EvaluationCtx): ConcreteValue = this
46 |
47 | val representation: Any
48 |
49 | def equalRepr(value: ConcreteValue): Boolean = this.representation == value.representation
50 |
51 | def reprGreaterThan(value: ConcreteValue): Boolean = throw new UnsupportedOperationException
52 |
53 | def reprContains(value: ConcreteValue): Boolean = throw new UnsupportedOperationException
54 |
55 | def add(value: ConcreteValue): ConcreteValue = throw new UnsupportedOperationException
56 |
57 | def subtract(value: ConcreteValue): ConcreteValue = throw new UnsupportedOperationException
58 |
59 | def multiply(value: ConcreteValue): ConcreteValue = throw new UnsupportedOperationException
60 |
61 | def divide(value: ConcreteValue): ConcreteValue = throw new UnsupportedOperationException
62 |
63 | def abs(): ConcreteValue = throw new UnsupportedOperationException
64 | }
65 |
66 | /*sealed abstract class AbstractDateTime(year:Int, month:Int, day:Int, hours:Int, minutes:Int, seconds:Int, val nanoseconds:Int)
67 | extends ConcreteValue with Ordered[AbstractDateTime] with Equals {
68 |
69 | require(nanoseconds >= 0 && nanoseconds < 1000000000)
70 |
71 | val date : Date = Utilities.constructDate(year, month, day, hours, minutes, seconds)
72 |
73 | override val isList = false
74 |
75 | override val representation = this
76 |
77 | override def compare(that: AbstractDateTime) : Int = {
78 | val dateComp = this.date compareTo that.date
79 | if (dateComp != 0)
80 | dateComp
81 | else
82 | this.nanoseconds - that.nanoseconds
83 | }
84 |
85 | def canEqual(other: Any): Boolean
86 |
87 | override def equals(other: Any) = {
88 | other match {
89 | case that: be.kuleuven.cs.distrinet.policylang.AbstractDateTime => that.canEqual(AbstractDateTime.this) && nanoseconds == that.nanoseconds
90 | case _ => false
91 | }
92 | }
93 |
94 | override def hashCode() = {
95 | val prime = 41
96 | prime * (prime + date.hashCode) + nanoseconds.hashCode
97 | }
98 |
99 |
100 | override def reprGreaterThan(value: ConcreteValue): Boolean =
101 | this.representation > value.representation.asInstanceOf[AbstractDateTime]
102 | }
103 | */
104 | class DateTimeImpl(val dt: LocalDateTime)
105 | extends ConcreteValue with Equals {
106 |
107 | def this(year:Int, month:Int, day:Int, hours:Int, minutes:Int, seconds:Int, millis:Int) =
108 | this(new LocalDateTime(year, month, day, hours, minutes, seconds, millis))
109 |
110 | override val representation = this
111 |
112 | override val isList = false
113 |
114 | override val aType = DateTime
115 |
116 | override def toString(): String = dt.toString()
117 |
118 | def canEqual(other: Any) = {
119 | other.isInstanceOf[DateTimeImpl]
120 | }
121 |
122 | override def equals(other: Any) = {
123 | other match {
124 | case that: DateTimeImpl => that.canEqual(this) && this.dt == that.dt
125 | case _ => false
126 | }
127 | }
128 |
129 | override def hashCode() = {
130 | val prime = 41
131 | prime + dt.hashCode()
132 | }
133 |
134 | override def reprGreaterThan(value: ConcreteValue): Boolean =
135 | (this.dt compareTo value.asInstanceOf[DateTimeImpl].dt) > 0
136 |
137 | override def add(value: ConcreteValue): ConcreteValue = value match {
138 | case d: DateTimeDurationImpl => new DateTimeImpl(this.dt.plus(d.period))
139 | case d: DayDurationImpl => new DateTimeImpl(this.dt.plus(d.period))
140 | case d: TimeDurationImpl => new DateTimeImpl(this.dt.plus(d.period))
141 | case _ => throw new UnsupportedOperationException
142 | }
143 |
144 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
145 | case d: DateTimeDurationImpl => new DateTimeImpl(this.dt.minus(d.period))
146 | case d: DayDurationImpl => new DateTimeImpl(this.dt.minus(d.period))
147 | case d: TimeDurationImpl => new DateTimeImpl(this.dt.minus(d.period))
148 | case d: DateTimeImpl => new DateTimeDurationImpl(new Period(this.dt, d.dt))
149 | case _ => throw new UnsupportedOperationException
150 | }
151 | }
152 |
153 | class TimeImpl(val time: LocalTime) extends ConcreteValue with Equals {
154 |
155 | def this(hours:Int, minutes:Int, seconds:Int, millis:Int) =
156 | this(new LocalTime(hours, minutes, seconds, millis))
157 |
158 | override val representation = this
159 |
160 | override val isList = false
161 |
162 | override val aType = Time
163 |
164 | override def toString(): String = time.toString()
165 |
166 | def canEqual(other: Any) = {
167 | other.isInstanceOf[TimeImpl]
168 | }
169 |
170 | override def equals(other: Any) = {
171 | other match {
172 | case that: TimeImpl => that.canEqual(this) && this.time == that.time
173 | case _ => false
174 | }
175 | }
176 |
177 | override def hashCode() = {
178 | val prime = 41
179 | prime + time.hashCode()
180 | }
181 |
182 | override def reprGreaterThan(value: ConcreteValue): Boolean =
183 | (this.time compareTo value.asInstanceOf[TimeImpl].time) > 0
184 |
185 | override def add(value: ConcreteValue): ConcreteValue = value match {
186 | case d: TimeDurationImpl => new TimeImpl(this.time.plus(d.period))
187 | case _ => throw new UnsupportedOperationException
188 | }
189 |
190 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
191 | case d: TimeDurationImpl => new TimeImpl(this.time.minus(d.period))
192 | case d: TimeImpl => new TimeDurationImpl(new Period(this.time, d.time))
193 | case _ => throw new UnsupportedOperationException
194 | }
195 | }
196 |
197 | class DayImpl(val day: LocalDate) extends ConcreteValue with Equals {
198 |
199 | def this(year:Int, month:Int, day:Int) =
200 | this(new LocalDate(year, month, day))
201 |
202 | override val representation = this
203 |
204 | override val isList = false
205 |
206 | override val aType = Day
207 |
208 | override def toString(): String = day.toString()
209 |
210 | def canEqual(other: Any) = {
211 | other.isInstanceOf[DayImpl]
212 | }
213 |
214 | override def equals(other: Any) = {
215 | other match {
216 | case that: DayImpl => that.canEqual(this) && this.day == that.day
217 | case _ => false
218 | }
219 | }
220 |
221 | override def hashCode() = {
222 | val prime = 41
223 | prime + day.hashCode()
224 | }
225 |
226 | override def reprGreaterThan(value: ConcreteValue): Boolean =
227 | (this.day compareTo value.asInstanceOf[DayImpl].day) > 0
228 |
229 | override def add(value: ConcreteValue): ConcreteValue = value match {
230 | case d: DayDurationImpl => new DayImpl(this.day.plus(d.period))
231 | case _ => throw new UnsupportedOperationException
232 | }
233 |
234 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
235 | case d: DayDurationImpl => new DayImpl(this.day.minus(d.period))
236 | case d: DayImpl => new DayDurationImpl(new Period(this.day, d.day))
237 | case _ => throw new UnsupportedOperationException
238 | }
239 | }
240 |
241 |
242 | abstract class Duration(val period: Period) extends ConcreteValue {
243 |
244 | override val representation = this
245 |
246 | override val isList = false
247 |
248 | override def toString(): String = period.toString()
249 | }
250 |
251 | class DateTimeDurationImpl(period: Period) extends Duration(period) {
252 |
253 | def this(years:Int, months:Int, days:Int, hours:Int, minutes:Int, seconds:Int, millis:Int) =
254 | this(Period.years(years)
255 | .withMonths(months)
256 | .withDays(days)
257 | .withHours(hours)
258 | .withMinutes(minutes)
259 | .withSeconds(seconds)
260 | .withMillis(millis))
261 |
262 | override val aType = DateTimeDuration
263 |
264 | override def add(value: ConcreteValue): ConcreteValue = value match {
265 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
266 | case d: DayDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
267 | case d: TimeDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
268 | case _ => throw new UnsupportedOperationException
269 | }
270 |
271 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
272 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
273 | case d: DayDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
274 | case d: TimeDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
275 | case _ => throw new UnsupportedOperationException
276 | }
277 | }
278 |
279 | class TimeDurationImpl(period: Period) extends Duration(period) {
280 |
281 | def this(hours:Int, minutes:Int, seconds:Int, millis:Int) =
282 | this(Period.hours(hours)
283 | .withMinutes(minutes)
284 | .withSeconds(seconds)
285 | .withMillis(millis))
286 |
287 | override val aType = TimeDuration
288 |
289 | override def add(value: ConcreteValue): ConcreteValue = value match {
290 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
291 | case d: DayDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
292 | case d: TimeDurationImpl => new TimeDurationImpl(this.period.plus(d.period))
293 | case _ => throw new UnsupportedOperationException
294 | }
295 |
296 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
297 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
298 | case d: DayDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
299 | case d: TimeDurationImpl => new TimeDurationImpl(this.period.minus(d.period))
300 | case _ => throw new UnsupportedOperationException
301 | }
302 | }
303 |
304 | class DayDurationImpl(period: Period) extends Duration(period) {
305 |
306 | def this(years:Int, months:Int, days:Int) =
307 | this(Period.years(years)
308 | .withMonths(months)
309 | .withDays(days))
310 |
311 | override val aType = DayDuration
312 |
313 | override def add(value: ConcreteValue): ConcreteValue = value match {
314 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
315 | case d: DayDurationImpl => new DayDurationImpl(this.period.plus(d.period))
316 | case d: TimeDurationImpl => new DateTimeDurationImpl(this.period.plus(d.period))
317 | case _ => throw new UnsupportedOperationException
318 | }
319 |
320 | override def subtract(value: ConcreteValue): ConcreteValue = value match {
321 | case d: DateTimeDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
322 | case d: DayDurationImpl => new DayDurationImpl(this.period.minus(d.period))
323 | case d: TimeDurationImpl => new DateTimeDurationImpl(this.period.minus(d.period))
324 | case _ => throw new UnsupportedOperationException
325 | }
326 | }
327 |
328 |
329 | abstract class BasicValue(private val value: Any, override val aType: AttributeType) extends ConcreteValue with Equals {
330 |
331 | override val isList = false
332 |
333 | override val representation = value
334 |
335 | override def toString(): String = value.toString
336 |
337 | override def equals(other: Any) = {
338 | other match {
339 | case that: BasicValue => that.canEqual(this) && value == that.value
340 | case _ => false
341 | }
342 | }
343 |
344 | override def hashCode() = {
345 | val prime = 41
346 | prime + value.##
347 | }
348 |
349 | }
350 |
351 | class NumberImpl(value: Either[Long,Double]) extends BasicValue(value, Number) {
352 |
353 | // TODO: remove Double for ease of use?
354 | // or remove Long?
355 |
356 | override val representation: Any = value match {
357 | case Left(long) => long
358 | case Right(double) => double
359 | }
360 |
361 | def canEqual(other: Any): Boolean = other.isInstanceOf[NumberImpl]
362 |
363 | override def toString(): String = representation.toString()
364 |
365 | override def reprGreaterThan(value: ConcreteValue): Boolean = value.representation match {
366 | case long1: Long => this.representation match {
367 | case long2: Long => long1 < long2
368 | case double2: Double => long1 < double2
369 | }
370 | case double1: Double => this.representation match {
371 | case long2: Long => double1 < long2
372 | case double2: Double => double1 < double2
373 | }
374 | }
375 |
376 | override def add(value: ConcreteValue): ConcreteValue =
377 | try {
378 | val left = this.value.asInstanceOf[Either[Long,Double]]
379 | val right = value.representation
380 |
381 | left.fold(
382 | long => right match {
383 | case l: Long => long + l
384 | case r: Double => long + r
385 | },
386 | double => right match {
387 | case l: Long => double + l
388 | case r: Double => double + r
389 | })
390 | } catch {
391 | case e: ClassCastException => throw new UnsupportedOperationException
392 | }
393 |
394 | override def subtract(value: ConcreteValue): ConcreteValue =
395 | try {
396 | val left = this.value.asInstanceOf[Either[Long,Double]]
397 | val right = value.representation
398 |
399 | left.fold(
400 | long => right match {
401 | case l: Long => long - l
402 | case r: Double => long - r
403 | },
404 | double => right match {
405 | case l: Long => double - l
406 | case r: Double => double - r
407 | })
408 | } catch {
409 | case e: ClassCastException => throw new UnsupportedOperationException
410 | }
411 |
412 | override def multiply(value: ConcreteValue): ConcreteValue =
413 | try {
414 | val left = this.value.asInstanceOf[Either[Long,Double]]
415 | val right = value.representation
416 |
417 | left.fold(
418 | long => right match {
419 | case l: Long => long * l
420 | case r: Double => long * r
421 | },
422 | double => right match {
423 | case l: Long => double * l
424 | case r: Double => double * r
425 | })
426 | } catch {
427 | case e: ClassCastException => throw new UnsupportedOperationException
428 | }
429 |
430 | override def divide(value: ConcreteValue): ConcreteValue =
431 | try {
432 | val left = this.value.asInstanceOf[Either[Long,Double]]
433 | val right = value.representation
434 |
435 | left.fold(
436 | long => right match {
437 | case l: Long => long / l.asInstanceOf[Double]
438 | case r: Double => long / r
439 | },
440 | double => right match {
441 | case l: Long => double / l
442 | case r: Double => double / r
443 | })
444 | } catch {
445 | case e: ClassCastException => throw new UnsupportedOperationException
446 | }
447 |
448 | override def abs(): ConcreteValue = value match {
449 | case Left(long) => Math.abs(long)
450 | case Right(double) => Math.abs(double)
451 | }
452 | }
453 |
454 | class BoolImpl(val value: Boolean) extends BasicValue(value, Bool) {
455 |
456 | def canEqual(other: Any): Boolean = other.isInstanceOf[BoolImpl]
457 |
458 | override def reprGreaterThan(value: ConcreteValue): Boolean = this.value > value.representation.asInstanceOf[Boolean]
459 | }
460 |
461 | class StringImpl(value: String) extends BasicValue (value, String) {
462 |
463 | def canEqual(other: Any): Boolean = other.isInstanceOf[StringImpl]
464 |
465 | override def reprGreaterThan(value: ConcreteValue): Boolean = this.value > value.representation.asInstanceOf[String]
466 |
467 | override def add(value: ConcreteValue): ConcreteValue =
468 | try {
469 | this.value concat value.representation.asInstanceOf[String]
470 | } catch {
471 | case e: ClassCastException => throw new UnsupportedOperationException
472 | }
473 | }
474 |
475 |
476 |
477 |
478 | abstract class SeqValue(private val seq: Seq[Any], override val aType: AttributeType) extends ConcreteValue with Equals {
479 |
480 | override val isList = true
481 |
482 | override val representation = seq
483 |
484 | override def toString(): String = seq.toString
485 |
486 | def length = seq.length
487 |
488 | override def equals(other: Any) = {
489 | other match {
490 | case that: SeqValue => canEqual(other) && seq == that.seq
491 | case _ => false
492 | }
493 | }
494 |
495 | override def hashCode() = {
496 | val prime = 41
497 | prime + seq.##
498 | }
499 |
500 | override def reprContains(value: ConcreteValue): Boolean = seq contains value.representation
501 | }
502 |
503 | class DoubleSeqImpl(seq: Seq[Double]) extends SeqValue(seq, Number) {
504 |
505 | def canEqual(other: Any) = other.isInstanceOf[DoubleSeqImpl]
506 | }
507 |
508 | class IntSeqImpl(seq: Seq[Int]) extends SeqValue(seq, Number) {
509 |
510 | def canEqual(other: Any) = other.isInstanceOf[IntSeqImpl]
511 | }
512 |
513 | class LongSeqImpl(seq: Seq[Long]) extends SeqValue(seq, Number) {
514 |
515 | def canEqual(other: Any) = other.isInstanceOf[LongSeqImpl]
516 | }
517 |
518 | class BoolSeqImpl(seq: Seq[Boolean]) extends SeqValue(seq, Bool) {
519 |
520 | def canEqual(other: Any) = other.isInstanceOf[BoolSeqImpl]
521 | }
522 |
523 | class StringSeqImpl(seq: Seq[String]) extends SeqValue(seq, String) {
524 |
525 | def canEqual(other: Any) = other.isInstanceOf[StringSeqImpl]
526 | }
527 |
528 | class TimeSeqImpl(seq: Seq[TimeImpl]) extends SeqValue(seq, Time) {
529 |
530 | def canEqual(other: Any) = other.isInstanceOf[TimeSeqImpl]
531 | }
532 |
533 | class DaySeqImpl(seq: Seq[DayImpl]) extends SeqValue(seq, Day) {
534 |
535 | def canEqual(other: Any) = other.isInstanceOf[DaySeqImpl]
536 | }
537 |
538 | // FIXME why do the date sequences wrap other Impl classes instead of sequences
539 | // of joda classes? The other SeqImpl classes do wrap raw types.
540 |
541 | class DateTimeSeqImpl(seq: Seq[DateTimeImpl]) extends SeqValue(seq, DateTime) {
542 |
543 | def canEqual(other: Any) = other.isInstanceOf[DateTimeSeqImpl]
544 | }
545 |
546 | class DateTimeDurSeqImpl(seq: Seq[DateTimeDurationImpl]) extends SeqValue(seq, DateTimeDuration) {
547 |
548 | def canEqual(other: Any) = other.isInstanceOf[DateTimeDurSeqImpl]
549 | }
550 |
551 | class DayDurSeqImpl(seq: Seq[DayDurationImpl]) extends SeqValue(seq, DayDuration) {
552 |
553 | def canEqual(other: Any) = other.isInstanceOf[DayDurSeqImpl]
554 | }
555 |
556 | class TimeDurSeqImpl(seq: Seq[TimeDurationImpl]) extends SeqValue(seq, TimeDuration) {
557 |
558 | def canEqual(other: Any) = other.isInstanceOf[TimeDurSeqImpl]
559 | }
--------------------------------------------------------------------------------