adapted code to unfiltered way. Now WebID checks are only done when needed. No need for JAAS anymore either.
--- a/src/main/scala/ReadWriteWebMain.scala Fri Oct 14 12:57:48 2011 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala Fri Oct 14 16:15:52 2011 +0200
@@ -92,11 +92,10 @@
// configures and launches a Jetty server
service.filter(new FilterLogger(logger)).
- filter(new auth.AuthenticationFilter).
context("/public"){ ctx:ContextBuilder =>
ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
}.
- filter(X509view.plan).
+ filter(new X509view().plan).
filter(app.plan).run()
}
--- a/src/main/scala/auth/WebIdClaim.scala Fri Oct 14 12:57:48 2011 +0200
+++ b/src/main/scala/auth/WebIdClaim.scala Fri Oct 14 16:15:52 2011 +0200
@@ -142,65 +142,69 @@
* @author bblfish
* @created 30/03/2011
*/
-class WebIDClaim(val webId: String, val key: PublicKey)(implicit cache: WebCache) {
+class WebIDClaim(val webId: String, val key: PublicKey) {
- val errors = new LinkedList[java.lang.Throwable]()
+ var errors = new LinkedList[java.lang.Throwable]()
lazy val principal = new WebIdPrincipal(webId)
- lazy val tests: List[Verification] = verify() //I need to try to keep more verification state
-
- /**
- * verify this claim
- * @param authSrvc: the authentication service contains information about where to get graphs
- */
- //todo: make this asynchronous
- lazy val verified: Boolean = tests.exists(v => v.isInstanceOf[Verified])
+ var tests: List[Verification] = List()
- private def verify(): List[Verification] = {
+ private var valid = false
+
+ def verified(implicit cache: WebCache): Boolean = {
+ if (!valid) verify(cache)
+ tests.exists(v => v.isInstanceOf[Verified])
+ }
+
+ private def verify(implicit cache: WebCache): List[Verification] = {
import org.w3.readwriteweb.util.wrapValidation
import collection.JavaConversions._
import WebIDClaim._
- if (!webId.startsWith("http:") && !webId.startsWith("https:")) {
- //todo: ftp, and ftps should also be doable, though content negotiations is then lacking
- unsupportedProtocol::Nil
- } else if (!key.isInstanceOf[RSAPublicKey]) {
- certificateKeyTypeNotSupported::Nil
- } else {
- val res = for {
- model <- cache.resource(new URL(webId)).get() failMap {
- t => new ProfileError("error fetching profile", t)
+ try {
+ if (!webId.startsWith("http:") && !webId.startsWith("https:")) {
+ //todo: ftp, and ftps should also be doable, though content negotiations is then lacking
+ unsupportedProtocol::Nil
+ } else if (!key.isInstanceOf[RSAPublicKey]) {
+ certificateKeyTypeNotSupported::Nil
+ } else {
+ val res = for {
+ model <- cache.resource(new URL(webId)).get() failMap {
+ t => new ProfileError("error fetching profile", t)
+ }
+ } yield {
+ val initialBinding = new QuerySolutionMap();
+ initialBinding.add("webid",model.createResource(webId))
+ val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.selectQuery, model,initialBinding)
+ try {
+ qe.execSelect().map( qs => {
+ val modulus = toInteger(qs.get("m"), cert + "hex", qs.get("mod"))
+ val exponent = toInteger(qs.get("e"), cert + "decimal", qs.get("exp"))
+
+ (modulus, exponent) match {
+ case (Some(mod), Some(exp)) => {
+ val rsakey = key.asInstanceOf[RSAPublicKey]
+ if (rsakey.getPublicExponent == exp && rsakey.getModulus == mod) verifiedWebID
+ else keyDoesNotMatch
+ }
+ case _ => new KeyProblem("profile contains key that cannot be analysed:" +
+ qs.varNames().map(nm => nm + "=" + qs.get(nm).toString) + "; ")
+ }
+ }).toList
+ //it would be nice if we could keep a lot more state of what was verified and how
+ //will do that when implementing tests, so that these tests can then be used directly as much as possible
+ } finally {
+ qe.close()
+ }
}
- } yield {
- val initialBinding = new QuerySolutionMap();
- initialBinding.add("webid",model.createResource(webId))
- val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.selectQuery, model,initialBinding)
- try {
- qe.execSelect().map( qs => {
- val modulus = toInteger(qs.get("m"), cert + "hex", qs.get("mod"))
- val exponent = toInteger(qs.get("e"), cert + "decimal", qs.get("exp"))
-
- (modulus, exponent) match {
- case (Some(mod), Some(exp)) => {
- val rsakey = key.asInstanceOf[RSAPublicKey]
- if (rsakey.getPublicExponent == exp && rsakey.getModulus == mod) verifiedWebID
- else keyDoesNotMatch
- }
- case _ => new KeyProblem("profile contains key that cannot be analysed:" +
- qs.varNames().map(nm => nm + "=" + qs.get(nm).toString) + "; ")
- }
- }).toList
- //it would be nice if we could keep a lot more state of what was verified and how
- //will do that when implementing tests, so that these tests can then be used directly as much as possible
- } finally {
- qe.close()
+ res.either match {
+ case Right(tests) => tests
+ case Left(profileErr) => profileErr::Nil
}
}
- res.either match {
- case Right(tests) => tests
- case Left(profileErr) => profileErr::Nil
- }
+ } finally {
+ valid = true
}
--- a/src/main/scala/auth/X509Cert.scala Fri Oct 14 12:57:48 2011 +0200
+++ b/src/main/scala/auth/X509Cert.scala Fri Oct 14 16:15:52 2011 +0200
@@ -24,8 +24,10 @@
package org.w3.readwriteweb.auth
import javax.servlet.http.HttpServletRequest
-import javax.security.cert.X509Certificate
+import java.security.cert.X509Certificate
import unfiltered.request.HttpRequest
+import java.util.concurrent.TimeUnit
+import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
/**
* @author Henry Story, with help from Doug Tangren on unfiltered mailing list
@@ -33,12 +35,10 @@
*/
object X509Cert {
- def unapply[T <: HttpServletRequest](r: HttpRequest[T]): Option[(X509Certificate)] =
+ def unapply[T <: HttpServletRequest](r: HttpRequest[T]): Option[Array[X509Certificate]] =
r.underlying.getAttribute("javax.servlet.request.X509Certificate") match {
- case certs: Array[X509Certificate] =>
- Some(certs(0))
+ case certs: Array[X509Certificate] => Some(certs)
case _ => None
}
- def apply[T <: HttpServletRequest](r: HttpRequest[T]) = unapply(r)
}
\ No newline at end of file
--- a/src/main/scala/auth/X509Claim.scala Fri Oct 14 12:57:48 2011 +0200
+++ b/src/main/scala/auth/X509Claim.scala Fri Oct 14 16:15:52 2011 +0200
@@ -31,7 +31,10 @@
import javax.security.auth.Refreshable
import java.util.Date
import collection.JavaConversions._
-
+import java.util.concurrent.TimeUnit
+import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
+import javax.servlet.http.HttpServletRequest
+import unfiltered.request.HttpRequest
/**
* @author hjs
@@ -41,6 +44,20 @@
object X509Claim {
final val logger = LoggerFactory.getLogger(classOf[X509Claim])
+ val idCache: Cache[X509Certificate, X509Claim] =
+ CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).
+ build(new CacheLoader[X509Certificate, X509Claim]() {
+ def load(x509: X509Certificate) = new X509Claim(x509)
+ })
+
+ def unapply[T <: HttpServletRequest](r: HttpRequest[T])(implicit webCache: WebCache): Option[X509Claim] =
+ r match {
+ case X509Cert(certs) => Some(idCache.get(certs(0)))
+ case _ => None
+ }
+
+
+
/**
* Extracts the URIs in the subject alternative name extension of an X.509
* certificate
@@ -72,7 +89,7 @@
* @author bblfish
* @created: 30/03/2011
*/
-class X509Claim(val cert: X509Certificate)(implicit webCache: WebCache) extends Refreshable {
+class X509Claim(val cert: X509Certificate) extends Refreshable {
import X509Claim._
val claimReceivedDate = new Date();
@@ -85,6 +102,7 @@
}.toSet
+
//note could also implement Destroyable
//
//http://download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html#Credentials
--- a/src/main/scala/auth/X509view.scala Fri Oct 14 12:57:48 2011 +0200
+++ b/src/main/scala/auth/X509view.scala Fri Oct 14 16:15:52 2011 +0200
@@ -23,11 +23,9 @@
package org.w3.readwriteweb.auth
-import javax.security.auth.Subject
-import java.security.{PrivilegedExceptionAction, PrivilegedActionException, AccessController}
+import unfiltered.request.Path
import unfiltered.response.{Html, ContentType, Ok}
-import unfiltered.request.Path
-import collection.JavaConversions._
+import org.w3.readwriteweb.WebCache
/**
* This plan just described the authentication information.
@@ -37,43 +35,24 @@
* @created: 13/10/2011
*/
-object X509view {
+class X509view(implicit val webCache: WebCache) {
+
val plan = unfiltered.filter.Planify {
- case req @ Path(path) if path startsWith "/test/authinfo"=> {
- val context = AccessController.getContext
- val subj = try {
- AccessController.doPrivileged(new PrivilegedExceptionAction[Option[Subject]] {
- def run = Option(Subject.getSubject(context))
- })
- } catch {
- case ex: PrivilegedActionException => {
- ex.getCause match {
- case runE: RuntimeException => throw runE
- case e => {
- System.out.println("error " + e)
- None
- }
- }
- }
- case _ => None
+ case req @ Path(path) if path startsWith "/test/auth/x509" =>
+ Ok ~> ContentType("text/html") ~> Html(
+ <html><head><title>Authentication Page</title></head>
+ { req match {
+ case X509Claim(xclaim: X509Claim) => <body>
+ <h1>Authentication Info received</h1>
+ <p>You were identified with the following WebIDs</p>
+ <ul>{xclaim.webidclaims.filter(cl=>cl.verified).map(p=> <li>{p}</li>)}</ul>
+ <p>You sent the following certificate</p>
+ <pre>{xclaim.cert.toString}</pre>
+ </body>
+ case _ => <body><p>We received no Authentication information</p></body>
}
- Ok ~> ContentType("text/html") ~> Html(<html><head><title>Authentication Page</title></head>
- <body><h1>Authentication Info received</h1>
- {subj match {
- case Some(x) => <span><p>You were identified with the following WebIDs</p>
- <ul>{x.getPrincipals.map(p=> <li>{p}</li>)}</ul>
- {val certs = x.getPublicCredentials(classOf[X509Claim])
- if (certs.size() >0) <span><p>You sent the following certificate</p>
- <pre>{certs.head.cert.toString}</pre>
- </span>
- }
- </span>
- case None => <p>We received no Authentication information</p>
- }
- }
- </body></html>)
+ }</html>)
+
}
- }
-
}
\ No newline at end of file