merged multiverse STM rewrite with master

This commit is contained in:
jboner 2009-09-17 09:51:32 +02:00
commit 1968dc1f45
17 changed files with 758 additions and 130 deletions

View file

@ -1,68 +1,70 @@
h1. Akka: RESTful Distributed Persistent Transactional Actors
h3. "http://akkasource.org":http://akkasource.org
Akka implements a unique hybrid of:
* The Actor model (Actors and Active Objects), which gives you:
** Concurrency (high-level and simple)
** Asynchronous, non-blocking and highly performant components.
** Supervision with "let-it-crash" semantics. Components are loosely coupled and restarted upon failure.
* Software Transactional Memory (STM).
* BASE and ACID persistence - Pluggable Eventually Consistent or ACID distributed scalable persistent storage.
* Remoting - Distributed services with supervision and error management
* REST (JAX-RS) and Comet bindings.
* Monitoring and Management
Akka can be used in two different ways:
* As a library: used by a web app, to be put into WEB-INF/lib
* As a kernel: stand-alone kernel, embedding the servlet container
See the "Use-case and Deployment Scenarios":http://wiki.github.com/jboner/akka/use-case-and-deployment-scenarios for details.
h1. What's Akka all about? Why should I care?
If you are new to Akka then I suggest you start with either the:
* "High Level View":http://wiki.github.com/jboner/akka/modules-the-high-level-view; which is outlining the different modules in Akka.
* "Use-case and Deployment Scenarios":http://wiki.github.com/jboner/akka/use-case-and-deployment-scenarios; outlining how and in which use-case and deployment scenarios can I use Akka?
* "Examples":http://wiki.github.com/jboner/akka/examples; showing how to build a RESTful, transactional, persistent Active Object and Actor.
After that you can dive into the "Reference Manual":http://wiki.github.com/jboner/akka/akka-reference-manual.
h1. Documentation
Akka has pretty thorough "reference documentation":https://github.com/jboner/akka/wikis. Covering examples, APIs and configuration.
h1. Distribution
The latest distribution can be found in the "downloads section":https://github.com/jboner/akka/downloads
h1. Mailing List
If you have questions and/or feedback: please sign up to the Akka User mailing list:
"http://groups.google.com/group/akka-user":http://groups.google.com/group/akka-user
h1. Professional Support
Scalable Solutions AB is providing a variety of professional support packages for Akka, please visit their website for details:
"http://scalablesolutions.se":http://scalablesolutions.se
h1. License
<pre>
This software is licensed under the Apache 2 license, quoted below.
Copyright 2009 Scalable Solutions AB <http://scalablesolutions.se>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
</pre>
h1. Akka Transactors
h2. RESTful Distributed Persistent Transactional Actors
h3. "http://akkasource.org":http://akkasource.org
Akka implements a unique hybrid of:
* The Actor model (Actors and Active Objects), which gives you:
** Concurrency (high-level and simple)
** Asynchronous, non-blocking and highly performant components.
** Supervision with "let-it-crash" semantics. Components are loosely coupled and restarted upon failure.
* Software Transactional Memory (STM).
* BASE and ACID persistence - Pluggable Eventually Consistent or ACID distributed scalable persistent storage.
* Remoting - Distributed services with supervision and error management
* REST (JAX-RS) and Comet bindings.
* Monitoring and Management
Akka can be used in two different ways:
* As a library: used by a web app, to be put into WEB-INF/lib
* As a kernel: stand-alone kernel, embedding the servlet container
See the "Use-case and Deployment Scenarios":http://wiki.github.com/jboner/akka/use-case-and-deployment-scenarios for details.
h1. What's Akka all about? Why should I care?
If you are new to Akka then I suggest you start with either the:
* "High Level View":http://wiki.github.com/jboner/akka/modules-the-high-level-view; which is outlining the different modules in Akka.
* "Use-case and Deployment Scenarios":http://wiki.github.com/jboner/akka/use-case-and-deployment-scenarios; outlining how and in which use-case and deployment scenarios can I use Akka?
* "Examples":http://wiki.github.com/jboner/akka/examples; showing how to build a RESTful, transactional, persistent Active Object and Actor.
After that you can dive into the "Reference Manual":http://wiki.github.com/jboner/akka/akka-reference-manual.
h1. Documentation
Akka has pretty thorough "reference documentation":https://github.com/jboner/akka/wikis. Covering examples, APIs and configuration.
h1. Distribution
The latest distribution can be found in the "downloads section":https://github.com/jboner/akka/downloads
h1. Mailing List
If you have questions and/or feedback: please sign up to the Akka User mailing list:
"http://groups.google.com/group/akka-user":http://groups.google.com/group/akka-user
h1. Professional Support
Scalable Solutions AB is providing a variety of professional support packages for Akka, please visit their website for details:
"http://scalablesolutions.se":http://scalablesolutions.se
h1. License
<pre>
This software is licensed under the Apache 2 license, quoted below.
Copyright 2009 Scalable Solutions AB <http://scalablesolutions.se>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
</pre>

View file

@ -16,18 +16,20 @@ import scala.collection.jcl.HashMap
object ActorRegistry extends Logging {
private val actors = new HashMap[String, List[Actor]]
def actorsFor(clazz: Class[_]): List[Actor] = synchronized {
actors.get(clazz.getName) match {
def actorsFor(fqn : String): List[Actor] = synchronized {
actors.get(fqn) match {
case None => Nil
case Some(instances) => instances
}
}
def actorsFor(clazz: Class[_]) : List[Actor] = actorsFor(clazz.getName)
def register(actor: Actor) = synchronized {
val name = actor.getClass.getName
actors.get(name) match {
case Some(instances) => actors + (name -> (actor :: instances))
case None => actors + (name -> (actor :: Nil))
case None => actors + (name -> (actor :: Nil))
}
}
}

View file

@ -11,11 +11,12 @@
* limitations under the License.
*/
package se.scalablesolutions.akka.util
package se.scalablesolutions.akka.actor
import java.util.concurrent._
import actor.{OneForOneStrategy, Actor}
import config.ScalaConfig._
import _root_.se.scalablesolutions.akka.util.{Logging}
import org.scala_tools.javautils.Imports._

View file

@ -4,7 +4,6 @@
package se.scalablesolutions.akka.state
//import org.multiverse.datastructures.refs.manual.Ref
import stm.{TransactionManagement, Ref}
import org.multiverse.templates.AtomicTemplate
import org.multiverse.api.Transaction;
@ -12,8 +11,6 @@ import akka.collection._
import org.codehaus.aspectwerkz.proxy.Uuid
import scala.collection.mutable.{ArrayBuffer, HashMap}
/**
* Example Scala usage:
* <pre>
@ -68,7 +65,7 @@ object TransactionalRef {
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class TransactionalRef[+T] extends Transactional {
class TransactionalRef[T] extends Transactional {
private[this] val ref = new Ref[T]
def swap(elem: T) = ref.set(elem)
@ -93,7 +90,7 @@ class TransactionalRef[+T] extends Transactional {
def flatMap[B](f: T => Option[B]): Option[B] = if (isEmpty) None else f(ref.get)
def filter(p: T => Boolean): Option[T] = if (isEmpty || p(ref.get)) this else None
def filter(p: T => Boolean): Option[T] = if (isEmpty || p(ref.get)) Some(ref.get) else None
def foreach(f: T => Unit) { if (!isEmpty) f(ref.get) }
@ -104,8 +101,6 @@ class TransactionalRef[+T] extends Transactional {
def toRight[X](left: => X) = if (isEmpty) Left(left) else Right(ref.get)
def toLeft[X](right: => X) = if (isEmpty) Right(right) else Left(ref.get)
def orElse[B >: T](alternative: => TransactionalRef[B]): TransactionalRef[B] = if (isEmpty) alternative else this
}
object TransactionalMap {
@ -160,7 +155,7 @@ class TransactionalMap[K, V] extends Transactional with scala.collection.mutable
}
object TransactionalVector {
def apply[T]() = new TransactionalVector
def apply[T]() = new TransactionalVector[T]
}
/**

View file

@ -4,9 +4,8 @@ import junit.framework.Test
import junit.framework.TestCase
import junit.framework.TestSuite
import actor.{ActorSpec, RemoteActorSpec, InMemoryActorSpec, SupervisorSpec, RemoteSupervisorSpec}
import actor.{ActorSpec, RemoteActorSpec, InMemoryActorSpec, SupervisorSpec, RemoteSupervisorSpec,SchedulerSpec}
import reactor.{EventBasedSingleThreadDispatcherTest, EventBasedThreadPoolDispatcherTest}
import util.SchedulerSpec
object AllTest extends TestCase {
def suite(): Test = {

View file

@ -1,6 +1,4 @@
package se.scalablesolutions.akka.util
import se.scalablesolutions.akka.actor.Actor
package se.scalablesolutions.akka.actor
import java.util.concurrent.TimeUnit

View file

@ -42,6 +42,11 @@
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-security</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<!-- Core deps -->
<dependency>

View file

@ -35,10 +35,7 @@ class AkkaServlet extends ServletContainer with AtmosphereServletProcessor with
val configurators = ConfiguratorRepository.getConfigurators
rc.getClasses.addAll(configurators.flatMap(_.getComponentInterfaces))
log.info("Starting AkkaServlet with ResourceFilters: " + rc.getProperty("com.sun.jersey.spi.container.ResourceFilters"));
rc.getProperties.put("com.sun.jersey.spi.container.ResourceFilters", "org.atmosphere.core.AtmosphereFilter")
//rc.getFeatures.put("com.sun.jersey.config.feature.Redirect", true)
//rc.getFeatures.put("com.sun.jersey.config.feature.ImplicitViewables",true)
rc.getProperties.put("com.sun.jersey.spi.container.ResourceFilters", akka.Config.config.getString("akka.rest.filters").getOrElse(""))
wa.initiate(rc, new ActorComponentProviderFactory(configurators))
}

View file

@ -108,8 +108,8 @@ trait PersistentMap extends scala.collection.mutable.Map[AnyRef, AnyRef] with Tr
override def get(key: AnyRef): Option[AnyRef] = {
if (newAndUpdatedEntries.contains(key)) newAndUpdatedEntries.get(key)
else try {
storage.getMapStorageEntryFor(uuid, key)
} catch { case e: Exception => None }
storage.getMapStorageEntryFor(uuid, key)
} catch { case e: Exception => None }
}
override def elements: Iterator[Tuple2[AnyRef, AnyRef]] = {

View file

@ -0,0 +1,83 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>akka-samples-security</artifactId>
<name>Akka Sample Security Module</name>
<packaging>jar</packaging>
<parent>
<artifactId>akka</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<artifactId>akka-kernel</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-util-java</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-util</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-actors</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-security</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-persistence</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>install</phase>
<configuration>
<tasks>
<copy file="target/akka-samples-security-${akka.version}.jar"
tofile="../deploy/akka-samples-security-${akka.version}.jar"/>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,87 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package sample.secure
import _root_.se.scalablesolutions.akka.state.{TransactionalState,PersistentState, CassandraStorageConfig}
import _root_.se.scalablesolutions.akka.actor.{SupervisorFactory, Actor}
import _root_.se.scalablesolutions.akka.config.ScalaConfig._
import _root_.se.scalablesolutions.akka.util.Logging
import _root_.se.scalablesolutions.akka.security.{DigestAuthenticationActor, UserInfo}
import _root_.javax.annotation.security.{DenyAll,PermitAll,RolesAllowed}
import javax.ws.rs.{GET, POST, Path, Produces, Consumes}
class Boot {
object factory extends SupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(OneForOne, 3, 100),
Supervise(
new SimpleAuthenticationService,
LifeCycle(Permanent, 100)) ::
Supervise(
new SecureService,
LifeCycle(Permanent, 100)):: Nil)
}
}
val supervisor = factory.newSupervisor
supervisor.startSupervisor
}
/*
* 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
{
//If you want to have a distributed nonce-map, you can use something like below,
//don't forget to configure your standalone Cassandra instance
//
//makeTransactionRequired
//override def mkNonceMap = PersistentState.newMap(CassandraStorageConfig()).asInstanceOf[scala.collection.mutable.Map[String,Long]]
//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"
//Dummy method that allows you to log on with whatever username with the password "bar"
override def userInfo(username : String) : Option[UserInfo] = Some(UserInfo(username,"bar","ninja" :: "chef" :: Nil))
}
/**
* This is merely a secured version of the scala-sample
*
* The interesting part is
* @RolesAllowed
* @PermitAll
* @DenyAll
*/
@Path("/securecount")
class SecureService extends Actor with Logging {
makeTransactionRequired
case object Tick
private val KEY = "COUNTER";
private var hasStartedTicking = false;
private val storage = PersistentState.newMap(CassandraStorageConfig())
@GET
@Produces(Array("text/html"))
@RolesAllowed(Array("chef"))
def count = (this !! Tick).getOrElse(<error>Error in counter</error>)
override def receive: PartialFunction[Any, Unit] = {
case Tick => if (hasStartedTicking) {
val counter = storage.get(KEY).get.asInstanceOf[Integer].intValue
storage.put(KEY, new Integer(counter + 1))
reply(<success>Tick:{counter + 1}</success>)
} else {
storage.put(KEY, new Integer(0))
hasStartedTicking = true
reply(<success>Tick: 0</success>)
}
}
}

65
akka-security/pom.xml Normal file
View file

@ -0,0 +1,65 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>akka-security</artifactId>
<name>Akka Security Module</name>
<packaging>jar</packaging>
<parent>
<artifactId>akka</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.7.5</version>
</dependency>
<!--<dependency>
<artifactId>akka-kernel</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>-->
<dependency>
<artifactId>akka-actors</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-persistence</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<artifactId>akka-util</artifactId>
<groupId>se.scalablesolutions.akka</groupId>
<version>0.6</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.1.1-ea</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>net.liftweb</groupId>
<artifactId>lift-util</artifactId>
<version>1.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,334 @@
/*
* Copyright 2007-2008 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions
* and limitations under the License.
*/
/*
* AKKA AAS (Authentication and Authorization Service)
* Rework of lift's (www.liftweb.com) HTTP Authentication module
* All cred to the Lift team (www.liftweb.com), especially David Pollak and Tim Perrett
*/
package se.scalablesolutions.akka.security
import _root_.se.scalablesolutions.akka.actor.{Scheduler,Actor,ActorRegistry}
import _root_.se.scalablesolutions.akka.state.{TransactionalState,PersistentStorageConfig}
import _root_.se.scalablesolutions.akka.util.{Logging}
import _root_.com.sun.jersey.api.model.AbstractMethod
import _root_.com.sun.jersey.spi.container.{ResourceFilterFactory,ContainerRequest,ContainerRequestFilter,ContainerResponse,ContainerResponseFilter,ResourceFilter}
import _root_.com.sun.jersey.core.util.Base64
import _root_.javax.ws.rs.core.{SecurityContext,Context,Response}
import _root_.javax.ws.rs.WebApplicationException
import _root_.javax.annotation.security.{DenyAll,PermitAll,RolesAllowed}
import _root_.java.security.Principal
import _root_.java.util.concurrent.TimeUnit
import _root_.net.liftweb.util.{SecurityHelpers, StringHelpers,IoHelpers}
object Enc extends SecurityHelpers with StringHelpers with IoHelpers
case object OK
/**
* Authenticate represents a message to authenticate a request
*/
case class Authenticate(val req : ContainerRequest, val rolesAllowed : List[String])
/**
* User info represents a sign-on with associated credentials/roles
*/
case class UserInfo(val username : String,val password : String,val roles : List[String])
trait Credentials
case class BasicCredentials(username : String, password : String) extends Credentials
case class DigestCredentials(method: String,
userName: String,
realm: String,
nonce: String,
uri: String,
qop: String,
nc: String,
cnonce: String,
response: String,
opaque: String) extends Credentials
/**
* Jersey Filter for invocation intercept and authorization/authentication
*/
class AkkaSecurityFilterFactory extends ResourceFilterFactory with Logging {
class Filter(actor : Actor,rolesAllowed : Option[List[String]]) extends ResourceFilter with ContainerRequestFilter with Logging {
override def getRequestFilter : ContainerRequestFilter = this
override def getResponseFilter : ContainerResponseFilter = null
/**
* Here's where the magic happens. The request is authenticated by
* sending a request for authentication to the configured authenticator actor
*/
override def filter(request : ContainerRequest) : ContainerRequest =
rolesAllowed match {
case Some(roles) => {
val result : AnyRef = (authenticator !? Authenticate(request,roles))
result match {
case OK => request
case r if r.isInstanceOf[Response] =>
throw new WebApplicationException(r.asInstanceOf[Response])
case x => {
log.error("Authenticator replied with unexpected result: ",x);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR)
}
}
}
case None => throw new WebApplicationException(Response.Status.FORBIDDEN)
}
}
lazy val authenticatorFQN = akka.Config.config.getString("akka.rest.authenticator").getOrElse(throw new IllegalStateException("akka.rest.authenticator"))
/**
* Currently we always take the first, since there usually should be at most one authentication actor, but a round-robin
* strategy could be implemented in the future
*/
def authenticator : Actor = ActorRegistry.actorsFor(authenticatorFQN).head
def mkFilter(roles : Option[List[String]]) : java.util.List[ResourceFilter] = java.util.Collections.singletonList(new Filter(authenticator,roles))
/**
* The create method is invoked for each resource, and we look for javax.annotation.security annotations
* and create the appropriate Filter configurations for each.
*/
override def create(am : AbstractMethod) : java.util.List[ResourceFilter] = {
//DenyAll takes precedence
if (am.isAnnotationPresent(classOf[DenyAll]))
return mkFilter(None)
//Method-level RolesAllowed takes precedence
val ra = am.getAnnotation(classOf[RolesAllowed])
if (ra ne null)
return mkFilter(Some(ra.value.toList))
//PermitAll takes precedence over resource-level RolesAllowed annotation
if (am.isAnnotationPresent(classOf[PermitAll]))
return null;
//Last but not least, the resource-level RolesAllowed
val cra = am.getResource.getAnnotation(classOf[RolesAllowed])
if (cra ne null)
return mkFilter(Some(ra.value.toList))
return null;
}
}
/**
* AuthenticationActor is the super-trait for actors doing Http authentication
* It defines the common ground and the flow of execution
*/
trait AuthenticationActor[C <: Credentials] extends Actor with Logging
{
type Req = ContainerRequest
//What realm does the authentication use?
def realm : String
//Creates a response to signal unauthorized
def unauthorized : Response
//Used to extract information from the request, returns None if no credentials found
def extractCredentials(r : Req) : Option[C]
//returns None is unverified
def verify(c : Option[C]) : Option[UserInfo]
//Contruct a new SecurityContext from the supplied parameters
def mkSecurityContext(r : Req, user : UserInfo) : SecurityContext
//This is the default security context factory
def mkDefaultSecurityContext(r : Req,u : UserInfo, scheme : String) : SecurityContext = {
val n = u.username
val p = new Principal { def getName = n }
new SecurityContext {
def getAuthenticationScheme = scheme
def getUserPrincipal = p
def isSecure = r.isSecure
def isUserInRole(role : String) = u.roles.exists(_ == role)
}
}
/**
* Responsible for the execution flow of authentication
*
* Credentials are extracted and verified from the request,
* and a se3curity context is created for the ContainerRequest
* this should ensure good integration with current Jersey security
*/
protected val authenticate: PartialFunction[Any,Unit] = {
case Authenticate(req,roles) => {
verify(extractCredentials(req)) match {
case Some(u : UserInfo) => {
req.setSecurityContext(mkSecurityContext(req,u))
if(roles.exists(req.isUserInRole(_)))
reply(OK)
else
reply(Response.status(Response.Status.FORBIDDEN).build)
}
case _ => reply(unauthorized)
}
}
}
override def receive: PartialFunction[Any, Unit] = authenticate
//returns the string value of the "Authorization"-header of the request
def auth(r : Req) = r.getHeaderValue("Authorization")
//Turns the aforementioned header value into an option
def authOption(r : Req) : Option[String] = {
val a = auth(r)
if(a != null && a.length > 0) Some(a) else None
}
}
/**
* This trait implements the logic for Http Basic authentication
* mix this trait into a class to create an authenticator
* Don't forget to set the authenticator FQN in the rest-part of the akka config
*/
trait BasicAuthenticationActor extends AuthenticationActor[BasicCredentials]
{
override def unauthorized =
Response.status(401).header("WWW-Authenticate","Basic realm=\"" + realm + "\"").build
override def extractCredentials(r : Req) : Option[BasicCredentials] = {
val a = r.getHeaderValue("Authorization")
new String(Base64.decode(a.substring(6,a.length).getBytes)).split(":").toList match {
case userName :: password :: _ => Some(BasicCredentials(userName, password))
case userName :: Nil => Some(BasicCredentials(userName, ""))
case _ => None
}
}
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
mkDefaultSecurityContext(r,u,SecurityContext.BASIC_AUTH)
}
/**
* This trait implements the logic for Http Digest authentication
* mix this trait into a class to create an authenticator
* Don't forget to set the authenticator FQN in the rest-part of the akka config
*/
trait DigestAuthenticationActor extends AuthenticationActor[DigestCredentials]
{
import Enc._
private object InvalidateNonces
//Holds the generated nonces for the specified validity period
val nonceMap = mkNonceMap
//Discards old nonces
protected val invalidateNonces: PartialFunction[Any,Unit] = {
case InvalidateNonces =>
{
val ts = System.currentTimeMillis
nonceMap.retain((k,v) => (ts - v) < nonceValidityPeriod)
}
case e => log.info("Don't know what to do with: " + e)
}
//Schedule the invalidation of nonces
Scheduler.schedule(this, InvalidateNonces, noncePurgeInterval, noncePurgeInterval, TimeUnit.MILLISECONDS )
//authenticate or invalidate nonces
override def receive: PartialFunction[Any, Unit] = authenticate orElse invalidateNonces
override def unauthorized : Response =
{
val nonce = randomString(64);
nonceMap.put(nonce,System.currentTimeMillis)
unauthorized(nonce,"auth",randomString(64))
}
def unauthorized(nonce : String, qop : String, opaque : String) : Response =
{
Response.status(401).header("WWW-Authenticate",
"Digest realm=\"" + realm + "\", " +
"qop=\"" + qop + "\", " +
"nonce=\"" + nonce + "\", " +
"opaque=\"" + opaque + "\"").build
}
//Tests wether the specified credentials are valid
def validate(auth: DigestCredentials,user : UserInfo) : Boolean = {
def h(s : String) = hexEncode(md5(s.getBytes("UTF-8")))
val ha1 = h(auth.userName + ":" + auth.realm + ":" + user.password)
val ha2 = h(auth.method + ":" + auth.uri)
val response = h(ha1 + ":" + auth.nonce + ":" +
auth.nc + ":" + auth.cnonce + ":" +
auth.qop + ":" + ha2)
(response == auth.response) && (nonceMap.getOrElse(auth.nonce, -1) != -1)
}
override def verify(odc : Option[DigestCredentials]) : Option[UserInfo] = odc match {
case Some(dc) => {
userInfo(dc.userName) match {
case Some(u) if validate(dc,u) =>
nonceMap.get(dc.nonce).map( t => (System.currentTimeMillis - t) < nonceValidityPeriod ).map(_ => u)
case _ => None
}
}
case _ => None
}
override def extractCredentials(r : Req) : Option[DigestCredentials] =
{
authOption(r).map( s => {
val ? = splitNameValuePairs(s.substring(7,s.length ))
DigestCredentials(r.getMethod.toUpperCase, ?("username"), ?("realm"), ?("nonce"),
?("uri"), ?("qop"), ?("nc"),
?("cnonce"), ?("response"), ?("opaque"))
})
}
override def mkSecurityContext(r : Req,u : UserInfo) : SecurityContext =
mkDefaultSecurityContext(r,u,SecurityContext.DIGEST_AUTH)
//Mandatory overrides
def userInfo(username : String) : Option[UserInfo]
def mkNonceMap : scala.collection.mutable.Map[String,Long]
//Optional overrides
def nonceValidityPeriod = 60*1000//ms
def noncePurgeInterval = 2*60*1000 //ms
}

View file

@ -261,6 +261,8 @@
<module fileurl="file://$PROJECT_DIR$/akka-samples-java/akka-samples-java.iml" filepath="$PROJECT_DIR$/akka-samples-java/akka-samples-java.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-samples-lift/akka-samples-lift.iml" filepath="$PROJECT_DIR$/akka-samples-lift/akka-samples-lift.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-samples-scala/akka-samples-scala.iml" filepath="$PROJECT_DIR$/akka-samples-scala/akka-samples-scala.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-samples-security/akka-samples-security.iml" filepath="$PROJECT_DIR$/akka-samples-security/akka-samples-security.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-security/akka-security.iml" filepath="$PROJECT_DIR$/akka-security/akka-security.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-util/akka-util.iml" filepath="$PROJECT_DIR$/akka-util/akka-util.iml" />
<module fileurl="file://$PROJECT_DIR$/akka-util-java/akka-util-java.iml" filepath="$PROJECT_DIR$/akka-util-java/akka-util-java.iml" />
</modules>
@ -1384,6 +1386,50 @@
<root url="jar://$MAVEN_REPOSITORY$/org/scala-tools/testing/scalatest/0.9.5/scalatest-0.9.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.annotation:jsr250-api:1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/jsr250-api/1.0/jsr250-api-1.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mortbay.jetty:jetty:7.0.0.pre5">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty/7.0.0.pre5/jetty-7.0.0.pre5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty/7.0.0.pre5/jetty-7.0.0.pre5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty/7.0.0.pre5/jetty-7.0.0.pre5-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mortbay.jetty:servlet-api:3.0.pre4">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/servlet-api/3.0.pre4/servlet-api-3.0.pre4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/servlet-api/3.0.pre4/servlet-api-3.0.pre4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/servlet-api/3.0.pre4/servlet-api-3.0.pre4-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mortbay.jetty:jetty-util:7.0.0.pre5">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty-util/7.0.0.pre5/jetty-util-7.0.0.pre5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty-util/7.0.0.pre5/jetty-util-7.0.0.pre5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mortbay/jetty/jetty-util/7.0.0.pre5/jetty-util-7.0.0.pre5-sources.jar!/" />
</SOURCES>
</library>
</component>
<UsedPathMacros>
<macro name="MAVEN_REPOSITORY" description="Maven Local Repostiry" />

View file

@ -2,17 +2,9 @@
<project relativePaths="false" version="4">
<component name="ChangeListManager">
<list default="true" readonly="true" id="188c966f-a83c-4d3a-9128-54d5a2947a12" name="Default" comment="">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-actors/src/main/scala/actor/Actor.scala" afterPath="$PROJECT_DIR$/akka-actors/src/main/scala/actor/Actor.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-persistence/src/test/scala/MongoPersistentActorSpec.scala" afterPath="$PROJECT_DIR$/akka-persistence/src/test/scala/MongoPersistentActorSpec.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-actors/src/test/scala/InMemoryActorSpec.scala" afterPath="$PROJECT_DIR$/akka-actors/src/test/scala/InMemoryActorSpec.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-persistence/src/test/scala/AllTest.scala" afterPath="$PROJECT_DIR$/akka-persistence/src/test/scala/AllTest.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-persistence/src/main/scala/CassandraStorage.scala" afterPath="$PROJECT_DIR$/akka-persistence/src/main/scala/CassandraStorage.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-persistence/src/test/scala/CassandraPersistentActorSpec.scala" afterPath="$PROJECT_DIR$/akka-persistence/src/test/scala/CassandraPersistentActorSpec.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka.ipr" afterPath="$PROJECT_DIR$/akka.ipr" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka.iws" afterPath="$PROJECT_DIR$/akka.iws" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala" afterPath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionManagement.scala" afterPath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionManagement.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala" afterPath="$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/Transaction.scala" afterPath="$PROJECT_DIR$/akka-actors/src/main/scala/stm/Transaction.scala" />
</list>
<ignored path=".idea/workspace.xml" />
<ignored path="akka.iws" />
@ -120,16 +112,34 @@
<file leaf-file-name="InMemoryActorSpec.scala" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/akka-actors/src/test/scala/InMemoryActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="38" column="0" selection-start="1046" selection-end="1433" vertical-scroll-proportion="0.0">
<state line="30" column="59" selection-start="1165" selection-end="1165" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="MongoPersistentActorSpec.scala" pinned="false" current="true" current-in-tab="true">
<file leaf-file-name="MongoPersistentActorSpec.scala" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/akka-persistence/src/test/scala/MongoPersistentActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="33" column="0" selection-start="1045" selection-end="1045" vertical-scroll-proportion="0.6984127">
<state line="33" column="0" selection-start="1045" selection-end="1045" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="TransactionalState.scala" pinned="false" current="true" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="38" column="18" selection-start="919" selection-end="919" vertical-scroll-proportion="0.28222525">
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="PersistentState.scala" pinned="false" current="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="263" column="5" selection-start="9023" selection-end="9023" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
@ -155,10 +165,10 @@
<option value="$PROJECT_DIR$/akka-actors/src/main/scala/actor/Actor.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/test/scala/AllTest.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/main/scala/CassandraStorage.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala" />
<option value="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/test/scala/CassandraPersistentActorSpec.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/test/scala/MongoPersistentActorSpec.scala" />
<option value="$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala" />
<option value="$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala" />
</list>
</option>
</component>
@ -759,27 +769,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="41" column="38" selection-start="1050" selection-end="1050" vertical-scroll-proportion="0.043419268">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="160" column="49" selection-start="6053" selection-end="6053" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-actors/src/test/scala/InMemoryActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="38" column="0" selection-start="1046" selection-end="1433" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-persistence/src/test/scala/CassandraPersistentActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="42" column="0" selection-start="969" selection-end="1352" vertical-scroll-proportion="0.0">
@ -789,7 +778,28 @@
</entry>
<entry file="file://$PROJECT_DIR$/akka-persistence/src/test/scala/MongoPersistentActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="33" column="0" selection-start="1045" selection-end="1045" vertical-scroll-proportion="0.6984127">
<state line="33" column="0" selection-start="1045" selection-end="1045" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-persistence/src/main/scala/PersistentState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="263" column="5" selection-start="9023" selection-end="9023" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-actors/src/test/scala/InMemoryActorSpec.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="30" column="59" selection-start="1165" selection-end="1165" vertical-scroll-proportion="0.0">
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/akka-actors/src/main/scala/stm/TransactionalState.scala">
<provider selected="true" editor-type-id="text-editor">
<state line="38" column="18" selection-start="919" selection-end="919" vertical-scroll-proportion="0.28222525">
<folding />
</state>
</provider>

View file

@ -17,7 +17,7 @@
<akka>
version = "0.6"
boot = ["sample.java.Boot", "sample.scala.Boot"] # FQN to the class doing initial active object/actor
boot = ["sample.java.Boot", "sample.scala.Boot", "sample.secure.Boot"] # FQN to the class doing initial active object/actor
# supervisor bootstrap, should be defined in default constructor
<actor>
timeout = 5000 # default timeout for future based invocations
@ -44,6 +44,8 @@
service = on
hostname = "localhost"
port = 9998
filters = "se.scalablesolutions.akka.security.AkkaSecurityFilterFactory;org.atmosphere.core.AtmosphereFilter"
authenticator = "sample.secure.SimpleAuthenticationService"
</rest>
<storage>

View file

@ -25,11 +25,13 @@
<module>akka-rest</module>
<module>akka-camel</module>
<module>akka-amqp</module>
<module>akka-security</module>
<module>akka-kernel</module>
<module>akka-fun-test-java</module>
<module>akka-samples-scala</module>
<module>akka-samples-lift</module>
<module>akka-samples-java</module>
<module>akka-samples-security</module>
</modules>
<organization>