├── .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 |
header.txt
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 | } --------------------------------------------------------------------------------