made the code use more of ScalaZ. Should be more functional. Is this better?
--- /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&ts=$timeStamp</b>&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")