made the code use more of ScalaZ. Should be more functional. Is this better? webid
authorHenry Story <henry.story@bblfish.net>
Tue, 29 Nov 2011 20:00:41 +0100
branchwebid
changeset 143 704344e7f4d8
parent 142 d1d551188b0f
child 144 ff77c1ecc2b4
made the code use more of ScalaZ. Should be more functional. Is this better?
src/main/resources/template/WebIDService.main.xhtml
src/main/scala/auth/AuthenticationFilter.scala
src/main/scala/auth/Authz.scala
src/main/scala/auth/Principals.scala
src/main/scala/auth/WebIDSrvc.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
src/main/scala/auth/earl.scala
src/main/scala/util/package.scala
src/test/scala/auth/CreateWebIDSpec.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/template/WebIDService.main.xhtml	Tue Nov 29 20:00:41 2011 +0100
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head id="head">
+	<title>WebId Identity Provider Info Page</title>
+	<script src="/public/logout.js" type="text/javascript" />
+</head>
+<body>
+
+<div id="tx-content">
+    <p>This is a simple Identity Provider for <a href="http://webid.info/spec">WebID</a>. It is meant to help
+        sites that would like to provide WebID authentication to their users quickly.</p>
+    <p>If you are hosting such a site then you can rely on this service to help authenticate your users with WebID,
+        without your needing to set up https on your server. When you are satisfied of its usefulness you can deploy it
+        to your site.</p>
+    <p>There are two stages to get going. First you need to create the login button linking to this service. Then you need to
+        understand how to interpret what will be returned, so that you can write a script to authenticate
+        your users with the given WebID - ie, set a cookie for them.</p>
+
+    <h2>Create your login link</h2>
+    <p>Create a login button or link that points to this service. This needs to contain an attribute as a URL to a
+        script on your site so that we can send you the response. This will be done by redirecting the user's browser
+        with a signed response containing his WebID. To create such a link enter the URL of your login service here:</p>
+    <form action="" method="get">Requesting auth service URL:
+        <input name="rs" size="80" type="text" />
+        <input type="submit" value="Log into this service provider" />
+    </form>
+    <p>By clicking on the form you will land on a page whose URL is the one you should enter into your
+        login button/link. You will also see what identity you were logged in as, and given some options to change
+        it.
+    </p>
+
+    <h2>Understanding the response</h2>
+    <p>The redirected to URL is constructed on the following pattern: </p>
+    <pre><b>$relyingService?webid=$webid&amp;ts=$timeStamp</b>&amp;sig=$URLSignature</pre>
+    <p>Where the above variables have the following meanings: </p>
+    <ul>
+        <li><code>$relyingService</code> is the URL passed by the server in
+            the initial request as the <code>rs</code> parameter, and is the service to which the response is sent.</li>
+        <li><code>$webid</code> is the WebID of the user connecting.</li>
+        <li><code>$timeStamp</code> is a time stamp in XML Schema format
+            (same as used by Atom). This is needed to reduce the ease of developing
+            replay attacks.</li>
+        <li><code>$URLSignature</code> is the signature of the whole URL
+            in bold above using the public key shown below, and encoded in a
+            <a href="http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString%28byte[]%29">URL friendly base64</a> encoding.</li>
+    </ul>
+
+    <h3>Error responses</h3>
+    <p>In case of error the service gets redirected to <code>$relyingService?error=$code</code>Where
+        $code can be either one of</p>
+    <ul>
+        <li><code>nocert</code>: when the client has no cert. </li>
+        <li><code>noVerifiedWebId</code>: no verified WebId was found in the certificate</li>
+        <li><code>noWebId</code>: todo: show this error when there are no webids at all</li>
+        <li><code>IdPError</code>: for some error in the IdP setup. Warn
+            the IdP administrator!</li>
+        <li>other messages, not standardised yet</li>
+    </ul>
+
+    <h2>Verifiying the WebId</h2>
+
+    <p>In order for the Relying Party to to be comfortable that the returned WebId
+        was not altered in transit, the whole URL is signed by this server as
+        shown above. Here are the public keys and algorithms this us using:</p>
+
+
+    <p>The signature uses the RSA with SHA-1 algorithm.</p>
+
+    <p>The public key used by this service that verifies the signature is: </p>
+
+    <ul>
+        <li>Key Type: <pre>http://www.w3.org/ns/auth/rsa#RSAPublicKey</pre></li>
+        <li>public exponent (decimal): <pre>65537</pre> </li>
+        <li>modulus (decimal):<br />
+           <pre>84:56:e8:8b:04:b9:1f:3b:10:00:07:ab:18:e8:fc:66:4e:aa:bc:47:f6:
+41:56:ab:96:6f:9c:d5:fc:5d:e9:fd:ce:a1:0f:5e:ce:26:f5:2e:35:e2:
+b7:0f:b3:db:17:0b:1b:c9:73:69:39:8a:39:4d:23:c3:b2:99:a7:a5:8b:
+5b:a8:2a:84:05:a3:d8:14:35:2e:49:7d:47:b6:80:52:90:37:ca:99:39:
+da:08:a4:f2:ef:f9:26:25:a9:4e:dd:44:57:df:43:3f:95:cd:cf:34:3f:
+41:58:e4:bc:19:63:ad:8f:b5:65:e3:3e:5e:d2:b3:19:f6:ca:ed:e5:a1:
+e7:cd:f1:9f:70:04:ea:66:a9:ad:77:cb:02:8d:c1:8d:45:89:39:07:b4:
+54:71:98:82:b0:55:39:c4:50:ad:24:3a:df:8f:df:fa:39:36:da:d9:98:
+65:1c:dd:4d:3f:d9:09:a7:5e:2d:de:cd:af:22:1e:25:b1:2e:d1:6d:74:
+e4:96:2f:2a:87:5a:c1:23:37:ff:38:ed:e1:f5:c5:20:fc:81:cf:cb:c7:
+1e:61:d1:77:6b:32:0d:6a:94:cb:8e:98:55:07:ea:09:f5:01:75:79:07:
+6e:f5:50:06:d0:1f:bd:11:94:85:86:c5:42:6f:76:e9:a9:fa:cf:db:91:
+13:92:c2:69:
+</pre>
+        </li>
+    </ul>
+
+    <p>For ease of use, depending on which tool you use, here is the public key in a PEM format:</p>
+<pre>
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhFboiwS5HzsQAAerGOj8
+Zk6qvEf2QVarlm+c1fxd6f3OoQ9ezib1LjXitw+z2xcLG8lzaTmKOU0jw7KZp6WL
+W6gqhAWj2BQ1Lkl9R7aAUpA3ypk52gik8u/5JiWpTt1EV99DP5XNzzQ/QVjkvBlj
+rY+1ZeM+XtKzGfbK7eWh583xn3AE6maprXfLAo3BjUWJOQe0VHGYgrBVOcRQrSQ6
+34/f+jk22tmYZRzdTT/ZCadeLd7NryIeJbEu0W105JYvKodawSM3/zjt4fXFIPyB
+z8vHHmHRd2syDWqUy46YVQfqCfUBdXkHbvVQBtAfvRGUhYbFQm926an6z9uRE5LC
+aQIDAQAB
+-----END PUBLIC KEY-----
+</pre>
+</div>
+
+</body>
+</html>
\ No newline at end of file
--- a/src/main/scala/auth/AuthenticationFilter.scala	Sun Nov 27 22:23:44 2011 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2011 Henry Story (bblfish.net)
- * under the MIT licence defined
- *    http://www.opensource.org/licenses/mit-license.html
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in the
- * Software without restriction, including without limitation the rights to use, copy,
- * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so, subject to the
- * following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-package org.w3.readwriteweb.auth
-
-import java.security.cert.X509Certificate
-import javax.servlet._
-import org.w3.readwriteweb._
-
-import collection.JavaConversions._
-import javax.security.auth.Subject
-import java.security.PrivilegedExceptionAction
-import java.util.concurrent.TimeUnit
-import com.google.common.cache.{CacheBuilder, Cache, CacheLoader}
-
-/**
- * This filter places the all the principals into a Subject,
- * which can then be accessed later on in by the code.
- *
- * note: It would be better if this were only called at the point when authentication
- * is needed. That is in fact possible with TLS renegotiation, but requires a server that allows
- * access to the TLS layer. This is an intermediary solution.
- */
-class AuthenticationFilter(implicit webCache: WebCache) extends Filter {
-  def init(filterConfig: FilterConfig) {}
-
-  val idCache: Cache[X509Certificate, X509Claim] =
-    CacheBuilder.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).
-      build(new CacheLoader[X509Certificate, X509Claim]() {
-        def load(x509: X509Certificate) = new X509Claim(x509)
-    })
-
-
-  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 = idCache.get(certChain.get(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)
-    }
-    try {
-      Subject.doAs(subject,new PrivilegedExceptionAction[Unit]() { def run(): Unit = chain.doFilter(request, response) } )
-    } catch {
-      case e: Exception => System.err.println("cought "+e)
-    }
-//    chain.doFilter(request, response)
-  }
-
-  def destroy() {}
-}
-
--- a/src/main/scala/auth/Authz.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/Authz.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -44,7 +44,7 @@
     val subject = new Subject()
     subject.getPublicCredentials.add(x509c)
     if (x509c.isCurrent()) {
-      val verified = x509c.verifiedClaims.map(claim => claim.principal)
+      val verified = x509c.verifiedClaims
       subject.getPrincipals.addAll(verified.asJava)
     }
     subject
@@ -149,10 +149,10 @@
           else subj match {
             case Some(s) => {
               agentsAllowed.exists{
-                p =>  s.getPrincipals(classOf[WebIdPrincipal]).asScala.
+                p =>  s.getPrincipals(classOf[WebID]).asScala.
                   exists(id=> {
                   val ps = if (p._1 != null) p._1.toString else null;
-                  ps == id.webid
+                  ps == id.getName
                 })
               }
             }
--- a/src/main/scala/auth/Principals.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/Principals.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -24,29 +24,69 @@
 package org.w3.readwriteweb.auth
 
 import java.security.Principal
-
-/**
- * @author hjs
- * @created: 13/10/2011
- */
+import java.net.URL
+import org.w3.readwriteweb.WebCache
+import com.hp.hpl.jena.rdf.model.Model
+import com.hp.hpl.jena.shared.WrappedIOException
+import scalaz.{Scalaz, Validation}
+import Scalaz._
 
 /**
  * @author Henry Story from http://bblfish.net/
  * @created: 09/10/2011
  */
 
-case class WebIdPrincipal(webid: String) extends Principal {
-  def getName = webid
+/**
+ * The WebID - ie, verified identity - something like a Principal.
+ * it is arguable that it should know what it was verified against.
+ */
+protected object WebID {
+
+  def apply(subjectAlternativeName: String): Validation[SANFailure,WebID] =
+    toUrl(subjectAlternativeName) flatMap {  url =>
+        val protocol = url.getProtocol
+        if ("http".equals(protocol) || "https".equals(protocol)) {
+          new WebID(url).success
+        } else UnsupportedProtocol("only http and https url supported at present").fail
+    }
+
+
+
+  def toUrl(urlStr: String): Validation[SANFailure,URL] = {
+    try { new URL(urlStr).success } catch {
+      // oops: should be careful, not all SANs are perhaps traditionally written out as a full URL.
+      case e => URISyntaxError("unparseable Subject Alternative Name: "+urlStr).fail
+    }
+  }
+
+}
+
+/**
+ * A WebID Principal
+ * Can only be constructed by the object, which does some minimal verifications
+ **/
+case class WebID private (val url: URL) extends Principal {
+  import org.w3.readwriteweb.util.wrapValidation
+
+  def getName = url.toExternalForm
+
   override def equals(that: Any) = that match {
-    case other: WebIdPrincipal => other.webid == webid
+    case other: WebID => other.url == url
     case _ => false
   }
+
+  def getDefiningModel(implicit cache: WebCache): Validation[ProfileError, Model] =
+    cache.resource(url).get() failMap {
+      case ioe: WrappedIOException => new ProfileGetError("error fetching profile", Some(ioe))
+      case other => new ProfileParseError("error parsing profile", Some(other))
+    }
 }
 
+
 case class Anonymous() extends Principal {
   def getName = "anonymous"
   override def equals(that: Any) =  that match {
-      case other: WebIdPrincipal => other eq this 
+      case other: Principal => 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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/WebIDSrvc.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * under the MIT licence defined at
+ *    http://www.opensource.org/licenses/mit-license.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package auth
+
+import org.w3.readwriteweb.WebCache
+import java.io.File
+import unfiltered.Cycle
+import unfiltered.request.{QueryString, Path}
+import xml.{Elem, XML}
+import unfiltered.response.{Html, Ok}
+import org.w3.readwriteweb.auth.NoX509
+import org.fusesource.scalate.scuery.Transformer
+
+/**
+ * @author Henry Story
+ *
+ */
+
+trait WebIDSrvc[Req,Res] {
+  implicit def wc: WebCache
+  implicit def manif: Manifest[Req]
+
+  val fileDir: File = new File(this.getClass.getResource("/template/").toURI)
+
+  lazy val webidSrvc: Elem = XML.loadFile(new File(fileDir, "WebIdService.main.xhtml"))
+  lazy val noX509: Elem = XML.loadFile(new File(fileDir, "NoWebId.xhtml"))
+
+  def intent : Cycle.Intent[Req,Res] = {
+    case req @ Path("/srv/idp")  => req match {
+      case QueryString(query) => Ok ~> Html( new ServiceFiller().apply(webidSrvc) )
+      case _ => Ok ~> Html (new NoX509().apply(noX509))
+    }
+
+  }
+}
+
+class ServiceFiller extends Transformer {
+
+}
\ No newline at end of file
--- a/src/main/scala/auth/WebIdClaim.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/WebIdClaim.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -23,121 +23,81 @@
 
 package org.w3.readwriteweb.auth
 
-import java.security.PublicKey
 import org.w3.readwriteweb.WebCache
 import java.security.interfaces.RSAPublicKey
 import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
-import java.net.URL
 import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
+import scalaz.{Scalaz, Validation}
+import Scalaz._
+import org.w3.readwriteweb.util.wrapValidation
+
 
 /**
- * @author hjs
+ * @author Henry Story
  * @created: 13/10/2011
  */
 
+/**
+ * One can only construct a WebID via the WebIDClaim apply
+ */
 object WebIDClaim {
-    final val cert: String = "http://www.w3.org/ns/auth/cert#"
+  final val cert: String = "http://www.w3.org/ns/auth/cert#"
 
-    val askQuery = QueryFactory.create("""
+  val askQuery = QueryFactory.create("""
       PREFIX : <http://www.w3.org/ns/auth/cert#>
       ASK {
           ?webid :key [ :modulus ?m ;
                         :exponent ?e ].
       }""")
 
-     def hex(bytes: Array[Byte]): String = bytes.dropWhile(_ == 0).map("%02X" format _).mkString
+  def hex(bytes: Array[Byte]): String = bytes.dropWhile(_ == 0).map("%02X" format _).mkString
 
+  def apply(san: String, rsakey: RSAPublicKey)(implicit cache: WebCache): Validation[WebIDClaimErr, WebIDClaim] =
+    for (id <- WebID(san) failMap {
+      case e => new WebIDClaimErr("Unsupported WebID", Some(e))
+    })
+    yield new WebIDClaim(id,rsakey)
 }
 
 /**
- * A claim that the user identified by the WebId controls the public key
- *
- * @author bblfish
- * @created 30/03/2011
+ * One has to construct a WebID using the object, that can do basic verifications
  */
-class WebIDClaim(val webId: String, val key: PublicKey) {
-
-	lazy val principal = new WebIdPrincipal(webId)
-
-	var tests: List[Verification] = List()
-
-  /** the tests have been done and are still valid - the idea is perhaps after a time tests would
-   * have to be done again? Eg: the claim is cached and re-used after a while */
-  private var valid = false
-
-  def verified(implicit cache: WebCache): Boolean = {
-    if (!valid) tests = verify(cache)
-    tests.contains(verifiedWebID)
-  }
-  
-  private def verify(implicit cache: WebCache): List[Verification] = {
-    import org.w3.readwriteweb.util.wrapValidation
+class WebIDClaim private (val webid: WebID, val rsakey: RSAPublicKey)(implicit cache: WebCache) {
+  import WebIDClaim.hex
+  import XSDDatatype._
 
-    import WebIDClaim._
-    try {
-      return 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 rsakey = key.asInstanceOf[RSAPublicKey]
-          val initialBinding = new QuerySolutionMap();
-          initialBinding.add("webid",model.createResource(webId))
-          initialBinding.add("m",model.createTypedLiteral( hex(rsakey.getModulus.toByteArray), XSDDatatype.XSDhexBinary))
-          initialBinding.add("e",model.createTypedLiteral( rsakey.getPublicExponent.toString, XSDDatatype.XSDinteger ))
-          val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model,initialBinding)
-          try {
-            if (qe.execAsk()) verifiedWebID
-            else noMatchingKey
-          } finally {
-            qe.close()
-          }
-        }
-        res.either match {
-          case Right(tests) => tests::Nil
-          case Left(profileErr) => profileErr::Nil
-        }
+  lazy val verify: Validation[WebIDClaimErr,WebID] =
+    webid.getDefiningModel.failMap {
+      case e => new WebIDClaimErr("could not fetch model", Some(e))
+    }.flatMap { model =>
+      val initialBinding = new QuerySolutionMap();
+      initialBinding.add("webid", model.createResource(webid.url.toString))
+      initialBinding.add("m", model.createTypedLiteral(hex(rsakey.getModulus.toByteArray), XSDhexBinary))
+      initialBinding.add("e", model.createTypedLiteral(rsakey.getPublicExponent.toString, XSDinteger))
+      val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model, initialBinding)
+      try {
+        if (qe.execAsk()) webid.success
+        else new WebIDClaimErr("could not verify public key").fail
+      } finally {
+        qe.close()
       }
-    } finally {
-      valid = true
     }
-
-
-  }
-
-	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 ProfileError(msg: String,  t: Throwable) extends Verification(profileOkTst,failed,msg, Some(t))
-class KeyProblem(msg: String) extends Verification(profileWellFormedKeyTst,failed,msg)
-
-object keyDoesNotMatch extends Verification(null,null,null) //this test will be forgotten
+trait Err {
+  val msg: String
+  val cause: Option[Throwable]=None
+}
 
-object verifiedWebID extends Verification(webidClaimTst, passed, "WebId verified")
-object noMatchingKey extends Verification(webidClaimTst, failed, "No keys in profile matches key in cert")
-object unsupportedProtocol extends Verification(webidClaimTst,untested,"WebID protocol not supported" )
+abstract class Failure extends Throwable with Err
 
-object certificateKeyTypeNotSupported extends Verification(pubkeyTypeTst,failed,"The certificate key type is not supported. We only support RSA")
+abstract class SANFailure extends Failure
+case class UnsupportedProtocol(val msg: String) extends SANFailure
+case class URISyntaxError(val msg: String) extends SANFailure
 
+abstract class ProfileError extends Failure
+case class ProfileGetError(val msg: String,  override val cause: Option[Throwable]) extends ProfileError
+case class ProfileParseError(val msg: String, override val cause: Option[Throwable]) extends ProfileError
 
+//it would be useful to pass the graph in
+class WebIDClaimErr(val msg: String, override val cause: Option[Throwable]=None) extends Failure
--- a/src/main/scala/auth/X509Cert.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/X509Cert.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -161,6 +161,10 @@
     return cert
   }
 
+  def signString(): String = {
+     return "todo"
+  }
+
 }
 
 
--- a/src/main/scala/auth/X509Claim.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/X509Claim.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -35,6 +35,10 @@
 import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
 import javax.servlet.http.HttpServletRequest
 import unfiltered.request.HttpRequest
+import java.security.interfaces.RSAPublicKey
+import collection.immutable.List
+import scalaz.{Success, Validation}
+import collection.mutable.HashMap
 
 /**
  * @author hjs
@@ -44,15 +48,24 @@
 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)
-     })
+  val idCache: HashMap[X509Certificate,X509Claim] = new HashMap
+
+// this is cool because it is not in danger of running out of memory but it makes it impossible to create the claim
+// with an implicit  WebCache...
+//  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](r: HttpRequest[T])(implicit webCache: WebCache,m: Manifest[T]): Option[X509Claim] = r match {
-    case Certs(c1: X509Certificate, _*) => Some(idCache.get(c1))
+    case Certs(c1: X509Certificate, _*) =>
+      idCache.get(c1).orElse {
+        val claim = new X509Claim(c1)
+        idCache.put(c1,claim)
+        Some(claim)
+      }
     case _ => None
   }
 
@@ -65,16 +78,15 @@
    * @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 {
+  def getClaimedWebIds(cert: X509Certificate): List[String] =
+    if (cert == null) Nil
+    else cert.getSubjectAlternativeNames().toList match {
       case coll if (coll != null) => {
         for {
           sanPair <- coll if (sanPair.get(0) == 6)
         } yield sanPair(1).asInstanceOf[String]
-      }.iterator
-      case _ => Iterator.empty
+      }
+      case _ => Nil
     }
 
 }
@@ -91,7 +103,7 @@
  * @created: 30/03/2011
  */
 // can't be a case class as it then creates object which clashes with defined one
-class X509Claim(val cert: X509Certificate) extends Refreshable {
+class X509Claim(val cert: X509Certificate)(implicit cache: WebCache) extends Refreshable {
 
   import X509Claim._
   val claimReceivedDate = new Date()
@@ -99,15 +111,13 @@
   lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
 
   /* a list of unverified principals */
-  lazy val webidclaims = {
-    val claims = getClaimedWebIds(cert) map { webid => new WebIDClaim(webid, cert.getPublicKey) }
-    claims.toSet
-  }
+  //TODO WE ASSUME THIS IS AN RSA KEY!!
+  lazy val webidValidations: List[Validation[WebIDClaimErr, WebIDClaim]] =
+    getClaimedWebIds(cert) map { webid => WebIDClaim(webid, cert.getPublicKey.asInstanceOf[RSAPublicKey]) }
 
-  def verifiedClaims(implicit cache: WebCache) = for (
-    claim <- webidclaims if (claim.verified)
-  ) yield claim
+  lazy val webidclaims: List[WebIDClaim] = webidValidations.collect{ case Success(webIdClaim)=> webIdClaim }
 
+  val verifiedClaims: List[WebID] = webidclaims.flatMap(_.verify.toOption)
 
   //note could also implement Destroyable
   //
--- a/src/main/scala/auth/X509view.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/X509view.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -34,6 +34,7 @@
 import unfiltered.scalate.Scalate
 import java.text.DateFormat
 import java.util.Date
+import scalaz.Validation
 
 /**
  * This plan just described the X509 WebID authentication information.
@@ -87,26 +88,26 @@
       }
       ff.flatten
   }
-  $(".webid_tests") { node =>
-    val ff = for (idclaim <- x509.webidclaims.toList) yield {
-      new Transform(node) {
-        $(".webid").contents = "Testing webid " +idclaim.webId
-        $(".webid_test") { n2 =>
-          idclaim.verified
-          val nn = for (tst <-idclaim.tests) yield {
-            new Transform(n2) {
-              $(".tst_question").contents = tst.of.title
-              $(".tst_txt").contents = tst.of.description
-              $(".tst_res").contents = tst.result.name
-              $(".tst_res_txt").contents = tst.msg
-            }.toNodes()
-          }
-          nn.flatten
-        }
-      }.toNodes()
-    }
-    ff.flatten
-  }
-  $(".certificate").contents = x509.cert.toString
+//  $(".webid_tests") { node =>
+//    val ff = for (idclaim <- x509.webidclaims) yield {
+//      new Transform(node) {
+//        $(".webid").contents = "Testing webid " +idclaim.webid
+//        $(".webid_test") { n2 =>
+//          val validation: Validation[WebIDClaimErr, WebID] = idclaim.verify
+//          val nn = for (tst <-idclaim.tests) yield {
+//            new Transform(n2) {
+//              $(".tst_question").contents = tst.of.title
+//              $(".tst_txt").contents = tst.of.description
+//              $(".tst_res").contents = tst.result.name
+//              $(".tst_res_txt").contents = tst.msg
+//            }.toNodes()
+//          }
+//          nn.flatten
+//        }
+//      }.toNodes()
+//    }
+//    ff.flatten
+//  }
+//  $(".certificate").contents = x509.cert.toString
 
 }
--- a/src/main/scala/auth/earl.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/auth/earl.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -70,7 +70,7 @@
 }
 
 /**
- * Public tests
+ * Test with extra information taken from the ontologies
  */
 class PubTest(val name: String) extends Test {
   import Tests._
@@ -94,28 +94,28 @@
 
 //for X509Claim
 object certProvided extends PubTest("certificateProvided")   {
-  def test(cert: Option[X509Certificate]): Verification = cert match {
-    case Some(x509) => new Verification(this,passed,"got certificate") //the subject is the session
-    case None => new Verification(this,failed,"missing certificate")
+  def test(cert: Option[X509Certificate]): Assertion = cert match {
+    case Some(x509) => new Assertion(this,passed,"got certificate") //the subject is the session
+    case None => new Assertion(this,failed,"missing certificate")
   }
 }
 
 object certOk extends PubTest("certificateOk") {
-   def test(x509: X509Claim): List[Verification] = {
+   def test(x509: X509Claim): List[Assertion] = {
      val res = certDateOk.test(x509)::certProvidedSan.test(x509)::certPubKeyRecognized.test(x509)::Nil
      val problems = res.filter(v => v.result != passed)
-     new Verification(this,problems.length==0,
+     new Assertion(this,problems.length==0,
        if (problems.length==0) "There were no issues with the certificate"
        else "There were some issues with your certificate")::res
    }
 }
 object certProvidedSan extends PubTest("certificateProvidedSAN") {
-  def test(x509: X509Claim) = new Verification(this,x509.webidclaims.size >0,
+  def test(x509: X509Claim) = new Assertion(this,x509.webidclaims.size >0,
     " There are "+x509.webidclaims.size+" SANs in the certificate")
 }
 object certDateOk extends PubTest("certificateDateOk") {
   def test(x509: X509Claim) = 
-    new Verification(this, x509.isCurrent,
+    new Assertion(this, x509.isCurrent,
       "the x509certificate " + (
         if (x509.tooEarly) "is not yet valid "
         else if (x509.tooLate) " passed its validity date "
@@ -127,20 +127,21 @@
 object certPubKeyRecognized extends PubTest("certificatePubkeyRecognised") {
   def test(claim: X509Claim) = {
     val pk = claim.cert.getPublicKey;
-    new Verification(this, pk.isInstanceOf[RSAPublicKey], "We only support RSA Keys at present. " )
+    new Assertion(this, pk.isInstanceOf[RSAPublicKey], "We only support RSA Keys at present. " )
   }
 }
 
 //for WebIDClaims
 object webidClaimTst extends PubTest("webidClaim")
 object pubkeyTypeTst extends PubTest("certificatePubkeyRecognised")
-object profileOkTst extends PubTest("profileWellFormed")
+object profileGetTst extends PubTest("profileGet")
+object profileParseTst extends PubTest("profileWellFormed")
+object profileOkTst extends PubTest("profileOk")
 object profileWellFormedKeyTst extends PubTest("profileWellFormedPubkey")
 
 
-
-/** short verification, that sits inside X509Claim or WebIdClaim and from which full verifications can be constituted */
-class Verification( val of: Test,
+/** Assertions on the success of a Test -- sits inside X509Claim or WebIdClaim and from which full verifications can be constituted */
+class Assertion( val of: Test,
              val result: Result = untested,
              val msg: String,
              val err: Option[Throwable] = None )
@@ -148,7 +149,7 @@
 
 sealed class Result(val name: String)  {
   val earl = "http://www.w3.org/ns/earl#"
-  def id = earl+name
+  val id = earl+name
 }
 
 object untested extends Result("untested")
--- a/src/main/scala/util/package.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/main/scala/util/package.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -5,6 +5,7 @@
 import scalaz._
 import Scalaz._
 import java.net.URL
+import com.hp.hpl.jena.shared.WrappedIOException
 
 package object util {
   
@@ -47,4 +48,11 @@
       case _ => None
     }
   
+   def tryOrFail[T](body: => T): Validation[Throwable,T] =
+      try {
+        val res = body;
+        res.success
+      } catch {
+        case e => e.fail
+      }
 }
--- a/src/test/scala/auth/CreateWebIDSpec.scala	Sun Nov 27 22:23:44 2011 +0100
+++ b/src/test/scala/auth/CreateWebIDSpec.scala	Tue Nov 29 20:00:41 2011 +0100
@@ -160,7 +160,7 @@
          keystore,
          "JKS",
          "secret",
-         "localhost"
+         "selfsigned"
        )
 
        val rsagen = KeyPairGenerator.getInstance("RSA")