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 (2011-10-14)
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