work done for shiro branch. But I am not convinced shiro is well structured. So I want to try somehting simpler first shiro
authorHenry Story <henry.story@bblfish.net>
Thu, 13 Oct 2011 13:34:16 +0200
branchshiro
changeset 58 6f860f662ed4
parent 57 f647f0335a35
child 59 34ec240b4fd2
child 218 b275b9c182c9
work done for shiro branch. But I am not convinced shiro is well structured. So I want to try somehting simpler first
project/build.scala
src/main/scala/AuthFilter.scala
src/main/scala/ReadWriteWebMain.scala
src/main/scala/auth/AuthFilter.scala
src/main/scala/auth/WebIdRealm.scala
--- a/project/build.scala	Wed Oct 12 15:26:31 2011 +0200
+++ b/project/build.scala	Thu Oct 13 13:34:16 2011 +0200
@@ -4,6 +4,8 @@
 // some usefull libraries
 // they are pulled only if used
 object Dependencies {
+  val shiro_core = "org.apache.shiro" % "shiro-core" % "1.1.0"
+  val shiro_web = "org.apache.shiro" % "shiro-web" % "1.1.0"
   val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
   val dispatch_http = "net.databinder" %% "dispatch-http" % "0.8.5" 
   val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % "0.5.0"
@@ -88,6 +90,8 @@
       libraryDependencies += scalaz,
       libraryDependencies += jsslutils,
       libraryDependencies += argot,
+      libraryDependencies += shiro_core,
+      libraryDependencies += shiro_web,
 
       jarName in assembly := "read-write-web.jar"
     )
--- a/src/main/scala/AuthFilter.scala	Wed Oct 12 15:26:31 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,378 +0,0 @@
-/*
- * Copyright (c) 2011 Henry Story (bblfish.net)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by Henry Story.  The name of bblfish.net may not be used to endorse
- * or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
- */
-
-package org.w3.readwriteweb.webid
-
-
-
-import java.security.cert.X509Certificate
-import javax.servlet._
-import org.slf4j.LoggerFactory
-import org.w3.readwriteweb._
-
-import java.util.{LinkedList, Date}
-import java.security.interfaces.RSAPublicKey
-import java.security.{Principal, PublicKey}
-import java.net.URL
-import java.math.BigInteger
-import com.hp.hpl.jena.rdf.model.RDFNode
-import collection.JavaConversions._
-import javax.security.auth.{Subject, Refreshable}
-import com.hp.hpl.jena.query._
-
-/**
- * @author Henry Story from http://bblfish.net/
- * @created: 09/10/2011
- */
-
-case class WebIdPrincipal(webid: String) extends Principal {
-  def getName = webid
-  override def equals(that: Any) = that match {
-    case other: WebIdPrincipal => other.webid == webid
-    case _ => false
-  }
-}
-
-case class Anonymous() extends Principal {
-  def getName = "anonymous"
-  override def equals(that: Any) =  that match {
-      case other: WebIdPrincipal => other eq this 
-      case _ => false
-    } //anonymous principals are equal only when they are identical. is this wise?
-      //well we don't know when two anonymous people are the same or different.
-}
-
-class AuthFilter(implicit webCache: WebCache) extends Filter {
-  def init(filterConfig: FilterConfig) {}
-
-  def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
-    val certChain = request.getAttribute("javax.servlet.request.X509Certificate") match {
-      case certs: Array[X509Certificate] => certs.toList
-      case _ => Nil
-    }
-    val subject = new Subject()
-    if (certChain.size == 0) {
-      System.err.println("No certificate found!")
-      subject.getPrincipals.add(Anonymous())
-    } else {
-      val x509c = new X509Claim(certChain(0))
-      subject.getPublicCredentials.add(x509c)
-
-
-      val verified = for (
-        claim <- x509c.webidclaims;
-        if (claim.verified)
-      ) yield claim.principal
-
-      subject.getPrincipals.addAll(verified)
-      System.err.println("Found "+verified.size+" principals: "+verified)
-    }
-
-    chain.doFilter(request, response)
-  }
-
-  def destroy() {}
-}
-
-object X509Claim {
-  final val logger = LoggerFactory.getLogger(classOf[X509Claim])
-
-  /**
-   * Extracts the URIs in the subject alternative name extension of an X.509
-   * certificate
-   *
-   * @param cert X.509 certificate from which to extract the URIs.
-   * @return Iterator of URIs as strings found in the subjectAltName extension.
-   */
-	def getClaimedWebIds(cert: X509Certificate): Iterator[String] =
-    if (cert == null)  Iterator.empty;
-    else cert.getSubjectAlternativeNames() match {
-      case coll if (coll != null) => {
-        for (sanPair <- coll
-             if (sanPair.get(0) == 6)
-        ) yield sanPair(1).asInstanceOf[String]
-      }.iterator
-      case _ => Iterator.empty
-    }
-
-
-
-}
-
-
-/**
- * An X509 Claim maintains information about the proofs associated with claims
- * found in an X509 Certificate. It is the type of object that can be passed
- * into the public credentials part of a Subject node
- *
- * todo: think of what this would look like for a chain of certificates
- *
- * @author bblfish
- * @created: 30/03/2011
- */
-class X509Claim(val cert: X509Certificate)(implicit webCache: WebCache) extends Refreshable {
-
-  import X509Claim._
-  val claimReceivedDate = new Date();
-  lazy val tooLate = claimReceivedDate.after(cert.getNotAfter())
-  lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
-
-  /* a list of unverified principals */
-  lazy val webidclaims = getClaimedWebIds(cert).map {
-    webid =>new WebIDClaim(webid, cert.getPublicKey)
-  }.toSet
-
-
-  //note could also implement Destroyable
-  //
-  //http://download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html#Credentials
-  //
-  //if updating validity periods can also take into account the WebID reference, then it is possible
-  //that a refresh could have as consequence to do a fetch on the WebID profile
-  //note: one could also take the validity period to be dependent on the validity of the profile representation
-  //in which case updating the validity period would make more sense.
-
-  override
-  def refresh() {
-  }
-
-  /* The certificate is currently within the valid time zone */
-  override
-  def isCurrent(): Boolean = !(tooLate||tooEarly)
-
-  lazy val error = {}
-
-  def canEqual(other: Any) = other.isInstanceOf[X509Claim]
-
-  override
-  def equals(other: Any): Boolean =
-    other match {
-      case that: X509Claim => (that eq this) || (that.canEqual(this) && cert == that.cert)
-      case _ => false
-    }
-
-  override
-  lazy val hashCode: Int = 41 * (41 +
-    (if (cert != null) cert.hashCode else 0))
-
-
-}
-
-object WebIDClaim {
-     final val cert: String = "http://www.w3.org/ns/auth/cert#"
-     final val xsd: String = "http://www.w3.org/2001/XMLSchema#"
-
-    val selectQuery = QueryFactory.create("""
- 		  PREFIX cert: <http://www.w3.org/ns/auth/cert#>
- 		  PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
- 		  SELECT ?key ?m ?e ?mod ?exp
- 		  WHERE {
- 		   ?key cert:identity ?webid ;
- 		      rsa:modulus ?m ;
- 		      rsa:public_exponent ?e .
- 
- 		    OPTIONAL { ?m cert:hex ?mod . }
- 		    OPTIONAL { ?e cert:decimal ?exp . }
- 		  }""")
-
-  /**
-    * Transform an RDF representation of a number into a BigInteger
-    * <p/>
-    * Passes a statement as two bindings and the relation between them. The
-    * subject is the number. If num is already a literal number, that is
-    * returned, otherwise if enough information from the relation to optstr
-    * exists, that is used.
-    *
-    * @param num the number node
-    * @param optRel name of the relation to the literal
-    * @param optstr the literal representation if it exists
-    * @return the big integer that num represents, or null if undetermined
-    */
-   def toInteger(num: RDFNode, optRel: String, optstr: RDFNode): Option[BigInteger] =
-       if (null == num) None
-       else if (num.isLiteral) {
-         val lit = num.asLiteral()
-         toInteger_helper(lit.getLexicalForm,lit.getDatatypeURI)
-       } else if (null != optstr && optstr.isLiteral) {
-           toInteger_helper(optstr.asLiteral().getLexicalForm,optRel)
-       } else None
-
-
-    private def intValueOfHexString(s: String): BigInteger = {
-      val strval = cleanHex(s);
-      new BigInteger(strval, 16);
-    }
-
-
-    /**
-     * This takes any string and returns in order only those characters that are
-     * part of a hex string
-     *
-     * @param strval
-     *            any string
-     * @return a pure hex string
-     */
-
-    private def cleanHex(strval: String) = {
-      def legal(c: Char) = {
-        //in order of appearance probability
-        ((c >= '0') && (c <= '9')) ||
-          ((c >= 'A') && (c <= 'F')) ||
-          ((c >= 'a') && (c <= 'f'))
-      }
-      (for (c <- strval; if legal(c)) yield c)
-    }
-
-
-   /**
-    * This transforms a literal into a number if possible ie, it returns the
-    * BigInteger of "ddd"^^type
-    *
-    * @param num the string representation of the number
-    * @param tpe the type of the string representation
-    * @return the number
-    */
-   protected def toInteger_helper(num: String, tpe: String): Option[BigInteger] =
-     try {
-       if (tpe.equals(cert + "decimal") || tpe.equals(cert + "int")
-         || tpe.equals(xsd + "integer") || tpe.equals(xsd + "int")
-         || tpe.equals(xsd + "nonNegativeInteger")) {
-         // cert:decimal is deprecated
-         Some(new BigInteger(num.trim(), 10));
-       } else if (tpe.equals(cert + "hex")) {
-         Some(intValueOfHexString(num));
-       } else {
-         // it could be some other encoding - one should really write a
-         // special literal transformation class
-         None;
-       }
-     } catch {
-       case e: NumberFormatException => None
-     }
-
-
-}
-
-/**
- * An X509 Claim maintains information about the proofs associated with claims
- * found in an X509 Certificate. It is the type of object that can be passed
- * into the public credentials part of a Subject node
- *
- * todo: think of what this would look like for a chain of certificates
- *
- * @author bblfish
- * @created 30/03/2011
- */
-class WebIDClaim(val webId: String, val key: PublicKey)(implicit cache: WebCache) {
-
-	val 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])
-
-  private def verify(): List[Verification] = {
-    import 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)
-        }
-      } 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
-      }
-    }
-
-
-  }
-  
-
-
-	def canEqual(other: Any) = other.isInstanceOf[WebIDClaim]
-
-	override
-	def equals(other: Any): Boolean =
-		other match {
-			case that: WebIDClaim => (that eq this) || (that.canEqual(this) && webId == that.webId && key == that.key)
-			case _ => false
-		}
-
-	override
-	lazy val hashCode: Int = 41 * (
-		41 * (
-			41 + (if (webId != null) webId.hashCode else 0)
-			) + (if (key != null) key.hashCode else 0)
-		)
-}
-
-
-class Verification(msg: String)
-class Verified(msg: String) extends Verification(msg)
-class Unverified(msg: String) extends Verification(msg)
-
-class TestFailure(msg: String) extends Verification(msg)
-class ProfileError(msg: String,  t: Throwable ) extends TestFailure(msg)
-class KeyProblem(msg: String) extends TestFailure(msg)
-
-object unsupportedProtocol extends TestFailure("WebID protocol not supported")
-object noMatchingKey extends TestFailure("No keys in profile matches key in cert")
-object keyDoesNotMatch extends TestFailure("Key does not match")
-
-object verifiedWebID extends Verified("WebId verified")
-object notstarted extends Unverified("No verification attempt started")
-object failed extends Unverified("Tests failed")
-object certificateKeyTypeNotSupported extends TestFailure("The certificate key type is not supported. We only support RSA")
\ No newline at end of file
--- a/src/main/scala/ReadWriteWebMain.scala	Wed Oct 12 15:26:31 2011 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala	Thu Oct 13 13:34:16 2011 +0200
@@ -94,7 +94,7 @@
 
     // configures and launches a Jetty server
     service.filter(new FilterLogger(logger)).
-      filter(new webid.AuthFilter).
+      filter(new auth.AuthFilter).
       context("/public"){ ctx:ContextBuilder =>
       ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
     }.filter(app.plan).run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/AuthFilter.scala	Thu Oct 13 13:34:16 2011 +0200
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Henry Story.  The name of bblfish.net may not be used to endorse
+ * or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package org.w3.readwriteweb.auth
+
+import java.security.cert.X509Certificate
+import javax.servlet._
+import org.slf4j.LoggerFactory
+import org.w3.readwriteweb._
+
+import java.util.{LinkedList, Date}
+import java.security.interfaces.RSAPublicKey
+import java.security.{Principal, PublicKey}
+import java.net.URL
+import java.math.BigInteger
+import com.hp.hpl.jena.rdf.model.RDFNode
+import collection.JavaConversions._
+import javax.security.auth.{Subject, Refreshable}
+import com.hp.hpl.jena.query._
+import org.apache.shiro.authc.AuthenticationToken
+
+/**
+ * @author Henry Story from http://bblfish.net/
+ * @created: 09/10/2011
+ */
+
+case class WebIdPrincipal(webid: String) extends Principal {
+  def getName = webid
+  override def equals(that: Any) = that match {
+    case other: WebIdPrincipal => other.webid == webid
+    case _ => false
+  }
+}
+
+case class Anonymous() extends Principal {
+  def getName = "anonymous"
+  override def equals(that: Any) =  that match {
+      case other: WebIdPrincipal => other eq this 
+      case _ => false
+    } //anonymous principals are equal only when they are identical. is this wise?
+      //well we don't know when two anonymous people are the same or different.
+}
+
+class AuthFilter(implicit webCache: WebCache) extends Filter {
+  def init(filterConfig: FilterConfig) {}
+
+  def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
+    val certChain = request.getAttribute("javax.servlet.request.X509Certificate") match {
+      case certs: Array[X509Certificate] => certs.toList
+      case _ => Nil
+    }
+    val subject = new Subject()
+    if (certChain.size == 0) {
+      System.err.println("No certificate found!")
+      subject.getPrincipals.add(Anonymous())
+    } else {
+      val x509c = new X509Claim(certChain(0))
+      subject.getPublicCredentials.add(x509c)
+
+
+      val verified = for (
+        claim <- x509c.webidclaims;
+        if (claim.verified)
+      ) yield claim.principal
+
+      subject.getPrincipals.addAll(verified)
+      System.err.println("Found "+verified.size+" principals: "+verified)
+    }
+
+    chain.doFilter(request, response)
+  }
+
+  def destroy() {}
+}
+
+object X509Claim {
+  final val logger = LoggerFactory.getLogger(classOf[X509Claim])
+
+  /**
+   * Extracts the URIs in the subject alternative name extension of an X.509
+   * certificate
+   *
+   * @param cert X.509 certificate from which to extract the URIs.
+   * @return Iterator of URIs as strings found in the subjectAltName extension.
+   */
+	def getClaimedWebIds(cert: X509Certificate): Iterator[String] =
+    if (cert == null)  Iterator.empty;
+    else cert.getSubjectAlternativeNames() match {
+      case coll if (coll != null) => {
+        for (sanPair <- coll
+             if (sanPair.get(0) == 6)
+        ) yield sanPair(1).asInstanceOf[String]
+      }.iterator
+      case _ => Iterator.empty
+    }
+
+
+
+}
+
+
+/**
+ * An X509 Claim maintains information about the proofs associated with claims
+ * found in an X509 Certificate. It is the type of object that can be passed
+ * into the public credentials part of a Subject node
+ *
+ * todo: think of what this would look like for a chain of certificates
+ *
+ * @author bblfish
+ * @created: 30/03/2011
+ */
+class X509Claim(val cert: X509Certificate)(implicit webCache: WebCache) extends Refreshable with AuthenticationToken {
+
+  import X509Claim._
+  val claimReceivedDate = new Date();
+  lazy val tooLate = claimReceivedDate.after(cert.getNotAfter())
+  lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
+
+  /* a list of unverified principals */
+  lazy val webidclaims = getClaimedWebIds(cert).map {
+    webid =>new WebIDClaim(webid, cert.getPublicKey)
+  }.toSet
+
+
+  //note could also implement Destroyable
+  //
+  //http://download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html#Credentials
+  //
+  //if updating validity periods can also take into account the WebID reference, then it is possible
+  //that a refresh could have as consequence to do a fetch on the WebID profile
+  //note: one could also take the validity period to be dependent on the validity of the profile representation
+  //in which case updating the validity period would make more sense.
+
+  override
+  def refresh() {
+  }
+
+  /* The certificate is currently within the valid time zone */
+  override
+  def isCurrent(): Boolean = !(tooLate||tooEarly)
+
+  lazy val error = {}
+
+  def canEqual(other: Any) = other.isInstanceOf[X509Claim]
+
+  override
+  def equals(other: Any): Boolean =
+    other match {
+      case that: X509Claim => (that eq this) || (that.canEqual(this) && cert == that.cert)
+      case _ => false
+    }
+
+  override
+  lazy val hashCode: Int = 41 * (41 +
+    (if (cert != null) cert.hashCode else 0))
+
+  //for Shiro, we'll think of the cert as a principal as well as a credential
+  def getPrincipal = cert
+
+  def getCredentials = cert
+}
+
+object WebIDClaim {
+     final val cert: String = "http://www.w3.org/ns/auth/cert#"
+     final val xsd: String = "http://www.w3.org/2001/XMLSchema#"
+
+    val selectQuery = QueryFactory.create("""
+ 		  PREFIX cert: <http://www.w3.org/ns/auth/cert#>
+ 		  PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
+ 		  SELECT ?key ?m ?e ?mod ?exp
+ 		  WHERE {
+ 		   ?key cert:identity ?webid ;
+ 		      rsa:modulus ?m ;
+ 		      rsa:public_exponent ?e .
+ 
+ 		    OPTIONAL { ?m cert:hex ?mod . }
+ 		    OPTIONAL { ?e cert:decimal ?exp . }
+ 		  }""")
+
+  /**
+    * Transform an RDF representation of a number into a BigInteger
+    * <p/>
+    * Passes a statement as two bindings and the relation between them. The
+    * subject is the number. If num is already a literal number, that is
+    * returned, otherwise if enough information from the relation to optstr
+    * exists, that is used.
+    *
+    * @param num the number node
+    * @param optRel name of the relation to the literal
+    * @param optstr the literal representation if it exists
+    * @return the big integer that num represents, or null if undetermined
+    */
+   def toInteger(num: RDFNode, optRel: String, optstr: RDFNode): Option[BigInteger] =
+       if (null == num) None
+       else if (num.isLiteral) {
+         val lit = num.asLiteral()
+         toInteger_helper(lit.getLexicalForm,lit.getDatatypeURI)
+       } else if (null != optstr && optstr.isLiteral) {
+           toInteger_helper(optstr.asLiteral().getLexicalForm,optRel)
+       } else None
+
+
+    private def intValueOfHexString(s: String): BigInteger = {
+      val strval = cleanHex(s);
+      new BigInteger(strval, 16);
+    }
+
+
+    /**
+     * This takes any string and returns in order only those characters that are
+     * part of a hex string
+     *
+     * @param strval
+     *            any string
+     * @return a pure hex string
+     */
+
+    private def cleanHex(strval: String) = {
+      def legal(c: Char) = {
+        //in order of appearance probability
+        ((c >= '0') && (c <= '9')) ||
+          ((c >= 'A') && (c <= 'F')) ||
+          ((c >= 'a') && (c <= 'f'))
+      }
+      (for (c <- strval; if legal(c)) yield c)
+    }
+
+
+   /**
+    * This transforms a literal into a number if possible ie, it returns the
+    * BigInteger of "ddd"^^type
+    *
+    * @param num the string representation of the number
+    * @param tpe the type of the string representation
+    * @return the number
+    */
+   protected def toInteger_helper(num: String, tpe: String): Option[BigInteger] =
+     try {
+       if (tpe.equals(cert + "decimal") || tpe.equals(cert + "int")
+         || tpe.equals(xsd + "integer") || tpe.equals(xsd + "int")
+         || tpe.equals(xsd + "nonNegativeInteger")) {
+         // cert:decimal is deprecated
+         Some(new BigInteger(num.trim(), 10));
+       } else if (tpe.equals(cert + "hex")) {
+         Some(intValueOfHexString(num));
+       } else {
+         // it could be some other encoding - one should really write a
+         // special literal transformation class
+         None;
+       }
+     } catch {
+       case e: NumberFormatException => None
+     }
+
+
+}
+
+/**
+ * An X509 Claim maintains information about the proofs associated with claims
+ * found in an X509 Certificate. It is the type of object that can be passed
+ * into the public credentials part of a Subject node
+ *
+ * todo: think of what this would look like for a chain of certificates
+ *
+ * @author bblfish
+ * @created 30/03/2011
+ */
+class WebIDClaim(val webId: String, val key: PublicKey)(implicit cache: WebCache) extends AuthenticationToken {
+
+	val 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])
+
+  private def verify(): List[Verification] = {
+    import 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)
+        }
+      } 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
+      }
+    }
+
+
+  }
+  
+
+
+	def canEqual(other: Any) = other.isInstanceOf[WebIDClaim]
+
+	override
+	def equals(other: Any): Boolean =
+		other match {
+			case that: WebIDClaim => (that eq this) || (that.canEqual(this) && webId == that.webId && key == that.key)
+			case _ => false
+		}
+
+	override
+	lazy val hashCode: Int = 41 * (
+		41 * (
+			41 + (if (webId != null) webId.hashCode else 0)
+			) + (if (key != null) key.hashCode else 0)
+		)
+
+  def getPrincipal = webId
+
+  def getCredentials = key
+}
+
+
+class Verification(msg: String)
+class Verified(msg: String) extends Verification(msg)
+class Unverified(msg: String) extends Verification(msg)
+
+class TestFailure(msg: String) extends Verification(msg)
+class ProfileError(msg: String,  t: Throwable ) extends TestFailure(msg)
+class KeyProblem(msg: String) extends TestFailure(msg)
+
+object unsupportedProtocol extends TestFailure("WebID protocol not supported")
+object noMatchingKey extends TestFailure("No keys in profile matches key in cert")
+object keyDoesNotMatch extends TestFailure("Key does not match")
+
+object verifiedWebID extends Verified("WebId verified")
+object notstarted extends Unverified("No verification attempt started")
+object failed extends Unverified("Tests failed")
+object certificateKeyTypeNotSupported extends TestFailure("The certificate key type is not supported. We only support RSA")
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/WebIdRealm.scala	Thu Oct 13 13:34:16 2011 +0200
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by Henry Story.  The name of bblfish.net may not be used to endorse
+ * or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+package org.w3.readwriteweb.auth
+
+import org.w3.readwriteweb.WebCache
+import org.apache.shiro.authc.{AuthenticationInfo, AuthenticationToken}
+import org.apache.shiro.subject.{SimplePrincipalCollection, PrincipalCollection}
+import collection.JavaConversions
+import org.apache.shiro.realm.{AuthenticatingRealm, AuthorizingRealm}
+import org.apache.shiro.authz.AuthorizationInfo
+
+/**
+ * @author hjs
+ * @created: 12/10/2011
+ */
+
+class WebIdRealm(cache: WebCache) extends AuthorizingRealm {
+
+  def doGetAuthenticationInfo(token: AuthenticationToken): AuthenticationInfo = {
+      val x509claim = token.asInstanceOf[X509Claim]
+      val verified = for (
+       claim <- x509claim.webidclaims;
+       if (claim.verified)
+     ) yield claim.principal
+
+
+    return new AuthenticationInfo {
+      def getPrincipals =  new SimplePrincipalCollection(JavaConversions.asJavaCollection(verified),"webid")
+
+      def getCredentials = x509claim
+    }
+  }
+
+  // not really sure what to do here
+  //
+  def doGetAuthorizationInfo(principals: PrincipalCollection) = new AuthorizationInfo {
+
+    def getRoles = null
+
+    def getStringPermissions = null
+
+    def getObjectPermissions = null
+  }
+
+
+}
+