adapted code to unfiltered way. Now WebID checks are only done when needed. No need for JAAS anymore either. webid
authorHenry Story <henry.story@bblfish.net>
Fri, 14 Oct 2011 16:15:52 +0200
branchwebid
changeset 63 ca2de8338abf
parent 62 419ae72e4ecf
child 64 215441a92c78
adapted code to unfiltered way. Now WebID checks are only done when needed. No need for JAAS anymore either.
src/main/scala/ReadWriteWebMain.scala
src/main/scala/auth/WebIdClaim.scala
src/main/scala/auth/X509Cert.scala
src/main/scala/auth/X509Claim.scala
src/main/scala/auth/X509view.scala
--- 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