diff --git a/akka-samples-lift/src/main/scala/akka/SimpleService.scala b/akka-samples-lift/src/main/scala/akka/SimpleService.scala index af8e313e31..8f7537f5bf 100644 --- a/akka-samples-lift/src/main/scala/akka/SimpleService.scala +++ b/akka-samples-lift/src/main/scala/akka/SimpleService.scala @@ -21,8 +21,8 @@ class SimpleService extends Actor { makeTransactionRequired case object Tick - private val KEY = "COUNTER"; - private var hasStartedTicking = false; + private val KEY = "COUNTER" + private var hasStartedTicking = false private val storage = TransactionalState.newMap[String, Integer] @GET @@ -54,8 +54,8 @@ class PersistentSimpleService extends Actor { makeTransactionRequired case object Tick - private val KEY = "COUNTER"; - private var hasStartedTicking = false; + private val KEY = "COUNTER" + private var hasStartedTicking = false private val storage = PersistentState.newMap(CassandraStorageConfig()) @GET diff --git a/akka-samples-scala/src/main/scala/SimpleService.scala b/akka-samples-scala/src/main/scala/SimpleService.scala index 0011e4e041..9ca6ac03ec 100644 --- a/akka-samples-scala/src/main/scala/SimpleService.scala +++ b/akka-samples-scala/src/main/scala/SimpleService.scala @@ -50,8 +50,8 @@ class SimpleService extends Actor { makeTransactionRequired case object Tick - private val KEY = "COUNTER"; - private var hasStartedTicking = false; + private val KEY = "COUNTER" + private var hasStartedTicking = false private val storage = TransactionalState.newMap[String, Integer] @GET @@ -83,8 +83,8 @@ class PersistentSimpleService extends Actor { makeTransactionRequired case object Tick - private val KEY = "COUNTER"; - private var hasStartedTicking = false; + private val KEY = "COUNTER" + private var hasStartedTicking = false private val storage = PersistentState.newMap(CassandraStorageConfig()) @GET diff --git a/akka-samples-security/src/main/resources/akka.conf b/akka-samples-security/src/main/resources/akka.conf new file mode 100644 index 0000000000..e2a93561f0 --- /dev/null +++ b/akka-samples-security/src/main/resources/akka.conf @@ -0,0 +1,35 @@ +#################### +# Akka Config File # +#################### + +# This file has all the default settings, so all these could be remove with no visible effect. +# Modify as needed. + + + version = "0.6" + + boot = ["se.scalablesolutions.akka.security.samples.Boot"] # FQN to the class doing initial active object/actor + # supervisor bootstrap, should be defined in default constructor + + + filters = "se.scalablesolutions.akka.security.AkkaSecurityFilterFactory" + + # only one authenticator can be enabled for the security filter factory + authenticator = "se.scalablesolutions.akka.security.samples.BasicAuthenticationService" +# authenticator = "se.scalablesolutions.akka.security.samples.DigestAuthenticationService" +# authenticator = "se.scalablesolutions.akka.security.samples.SpnegoAuthenticationService" + +# +# +# servicePrincipal = "HTTP/localhost@EXAMPLE.COM" +# keyTabLocation = "URL to keytab" +# kerberosDebug = "true" +# realm = "EXAMPLE.COM" +# + + # service = on + # hostname = "localhost" + # port = 9998 + + + diff --git a/akka-samples-security/src/main/scala/SimpleService.scala b/akka-samples-security/src/main/scala/SimpleService.scala index 2f38c23ef9..827f3ad8c1 100644 --- a/akka-samples-security/src/main/scala/SimpleService.scala +++ b/akka-samples-security/src/main/scala/SimpleService.scala @@ -2,7 +2,7 @@ * Copyright (C) 2009 Scalable Solutions. */ -package sample.secure +package se.scalablesolutions.akka.security.samples import se.scalablesolutions.akka.actor.{SupervisorFactory, Actor} import se.scalablesolutions.akka.config.ScalaConfig._ @@ -10,21 +10,31 @@ import se.scalablesolutions.akka.util.Logging import se.scalablesolutions.akka.security.{DigestAuthenticationActor, UserInfo} import se.scalablesolutions.akka.state.TransactionalState -import javax.annotation.security.RolesAllowed -import javax.ws.rs.{GET, Path, Produces} - class Boot { + object factory extends SupervisorFactory { + override def getSupervisorConfig: SupervisorConfig = { SupervisorConfig( RestartStrategy(OneForOne, 3, 100), + // Dummy implementations of all authentication actors + // see akka.conf to enable one of these for the AkkaSecurityFilterFactory Supervise( - new SimpleAuthenticationService, + new BasicAuthenticationService, + LifeCycle(Permanent, 100)) :: + /** + Supervise( + new DigestAuthenticationService, LifeCycle(Permanent, 100)) :: Supervise( - new SecureService, + new SpnegoAuthenticationService, + LifeCycle(Permanent, 100)) :: + **/ + Supervise( + new SecureTickActor, LifeCycle(Permanent, 100)):: Nil) } + } val supervisor = factory.newSupervisor @@ -34,7 +44,7 @@ class Boot { /* * In akka.conf you can set the FQN of any AuthenticationActor of your wish, under the property name: akka.rest.authenticator */ -class SimpleAuthenticationService extends DigestAuthenticationActor { +class DigestAuthenticationService extends DigestAuthenticationActor { //If you want to have a distributed nonce-map, you can use something like below, //don't forget to configure your standalone Cassandra instance // @@ -43,6 +53,7 @@ class SimpleAuthenticationService extends DigestAuthenticationActor { //Use an in-memory nonce-map as default override def mkNonceMap = new scala.collection.mutable.HashMap[String,Long] + //Change this to whatever you want override def realm = "test" @@ -50,38 +61,89 @@ class SimpleAuthenticationService extends DigestAuthenticationActor { override def userInfo(username : String) : Option[UserInfo] = Some(UserInfo(username,"bar","ninja" :: "chef" :: Nil)) } +class BasicAuthenticationService extends BasicAuthenticationActor { + + //Change this to whatever you want + override def realm = "test" + + //Dummy method that allows you to log on with whatever username + def verify(odc : Option[BasicCredentials]) : Option[UserInfo] = odc match { + case Some(dc) => userInfo(dc.username) + case _ => None + } + + //Dummy method that allows you to log on with whatever username with the password "bar" + def userInfo(username : String) : Option[UserInfo] = Some(UserInfo(username,"bar","ninja" :: "chef" :: Nil)) + +} + +class SpnegoAuthenticationService extends SpnegoAuthenticationActor { + + def rolesFor(user: String) = "ninja" :: "chef" :: Nil + +} + /** - * This is merely a secured version of the scala-sample + * a REST Actor with class level paranoia settings to deny all access * * The interesting part is * @RolesAllowed * @PermitAll * @DenyAll */ +import java.lang.Integer +import javax.annotation.security.{RolesAllowed, DenyAll, PermitAll} +import javax.ws.rs.{GET, Path, Produces} +@Path("/secureticker") +class SecureTickActor extends Actor with Logging { -@Path("/securecount") -class SecureService extends Actor with Logging { makeTransactionRequired case object Tick - private val KEY = "COUNTER"; - private var hasStartedTicking = false; - private val storage = TransactionalState.newMap[String, Integer] + private val KEY = "COUNTER" + private var hasStartedTicking = false + private val storage = TransactionalState.newMap[String, Integer] + /** + * allow access for any user to "/secureticker/public" + */ @GET - @Produces(Array("text/html")) + @Produces(Array("text/xml")) + @Path("/public") + @PermitAll + def publicTick = tick + + /** + * restrict access to "/secureticker/chef" users with "chef" role + */ + @GET + @Path("/chef") + @Produces(Array("text/xml")) @RolesAllowed(Array("chef")) - def count = (this !! Tick).getOrElse(Error in counter) + def chefTick = tick + + /** + * access denied for any user to default Path "/secureticker/" + */ + @GET + @Produces(Array("text/xml")) + @DenyAll + def paranoiaTick = tick + + def tick = (this !! Tick) match { + case(Some(counter)) => (Tick: {counter}) + case _ => (Error in counter) + } override def receive: PartialFunction[Any, Unit] = { case Tick => if (hasStartedTicking) { val counter = storage.get(KEY).get.intValue - storage.put(KEY, new Integer(counter + 1)) - reply(Tick:{counter + 1}) + storage.put(KEY, counter + 1) + reply(new Integer(counter + 1)) } else { - storage.put(KEY, new Integer(0)) + storage.put(KEY, 0) hasStartedTicking = true - reply(Tick: 0) + reply(new Integer(0)) } } } \ No newline at end of file diff --git a/akka-samples-security/src/main/webapp/WEB-INF/web.xml b/akka-samples-security/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000000..a04d912c1d --- /dev/null +++ b/akka-samples-security/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + akka-security-samples + + + AkkaServlet + se.scalablesolutions.akka.rest.AkkaServlet + + + + AkkaServlet + /* + + + + + diff --git a/config/akka-reference.conf b/config/akka-reference.conf index e2451d8687..8aa9dfd6d7 100644 --- a/config/akka-reference.conf +++ b/config/akka-reference.conf @@ -19,7 +19,7 @@ # FQN to the class doing initial active object/actor # supervisor bootstrap, should be defined in default constructor - boot = ["sample.java.Boot", "sample.scala.Boot", "sample.secure.Boot"] + boot = ["sample.java.Boot", "sample.scala.Boot", "se.scalablesolutions.akka.security.samples.Boot"] timeout = 5000 # default timeout for future based invocations @@ -49,6 +49,14 @@ + + service = on + hostname = "localhost" + port = 9998 + filters = "se.scalablesolutions.akka.security.AkkaSecurityFilterFactory" + authenticator = "se.scalablesolutions.akka.security.samples.BasicAuthenticationService" + + hostname = "127.0.0.1" # IP address or hostname of one of the Cassandra cluster's seeds