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..db328cc6ad 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") +@DenyAll +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 storage = TransactionalState.newMap[String, Integer] + /** + * allow access for any user to the resource "/secureticker/public" + */ @GET - @Produces(Array("text/html")) + @Produces(Array("text/xml")) + @Path("/public") + @PermitAll + def publicTick = tick + + /** + * restrict access to resource "/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 because of the class level annotation for any user + */ + @GET + @Produces(Array("text/xml")) + 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 + /* + + + + +