Cleaning up test code and tying it to the testing "intent". The WebIDClaim is now very much a claim. the verify method returns the WebID if there are no errros (SAN not parsed, document not found, etc...)
--- a/src/main/resources/template/WebId.xhtml Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/resources/template/WebId.xhtml Sat Dec 03 16:12:05 2011 +0100
@@ -5,35 +5,29 @@
</head>
<body>
<h1>WebID Authentication Report</h1>
-
- <p>This page describes in detail the state of your <a href="http://webid.info/spec">WebID authentication</a> session on <span class="date">Thu Nov 17 08:31:53 PST 2011.</span> </p>
- <p><a href="" onclick="return logout()">logout (only works on IE and Firefox)</a></p>
+ <p>This page describes in detail the state of your <a href="http://webid.info/spec">WebID authentication</a> session
+ on <span class="date">Thu Nov 17 08:31:53 PST 2011.</span> </p>
- <h2>WebIDs</h2>
+ <p><a href="" onclick="return logout()">logout</a> -- (only works on IE and Firefox)</p>
-
+ <h2>WebID</h2>
<div class="webid_tests">
- <h3 class="webid">WebID found: http://example.com/#me</h3>
- <table class="webid_test" width="100%" rules="groups">
- <caption class="tst_question">Could at least one WebID claim be verified?</caption>
- <thead><tr><td>description</td><td class="tst_txt">At least one WebID claimed in the certificate has public key that verifies.</td></tr></thead>
- <tbody>
- <tr><td>subject</td><td><a href="#cert" class="tst_sub">the certificate sent by your browser</a></td></tr>
- <tr><td>test result</td><td><font color="green" class="tst_res">passed</font></td></tr>
- <tr><td>result description</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+ <p class="no_webid">No Subject Alternative Names were found in your profile</p>
+ <table width="100%" rules="groups">
+ <tbody class="webid_test">
+ <tr><td colspan="2">Verification of Subject Alternative Name <span class="webid">http://bblfish.net/#me</span>?</td> </tr>
+ <tr><td class="tst_res">result</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+ <tr class="webid_cause"><td>cause</td><td class="cause_txt"></td></tr>
</tbody>
</table>
</div>
<h2>Certificate</h2>
- <table class="cert_test" width="100%" rules="groups">
- <caption class="tst_question">Could at least one WebID claim be verified?</caption>
- <thead><tr><td>description</td><td class="tst_txt">At least one WebID claimed in the certificate has public key that verifies.</td></tr></thead>
- <tbody>
- <tr><td>subject</td><td><a href="#cert" class="tst_sub">the certificate sent by your browser</a></td></tr>
- <tr><td>test result</td><td><font color="green" class="tst_res">passed</font></td></tr>
- <tr><td>result description</td><td class="tst_res_txt">found 1 valid principals</td></tr>
+ <table width="100%" rules="groups">
+ <tbody class="cert_test">
+ <tr><td colspan="2" class="tst_question">Could at least one WebID claim be verified?</td> </tr>
+ <tr><td class="tst_res">passed</td><td class="tst_res_txt">found 1 valid principals</td></tr>
</tbody>
</table>
--- a/src/main/scala/WebCache.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/WebCache.scala Sat Dec 03 16:12:05 2011 +0100
@@ -24,11 +24,11 @@
package org.w3.readwriteweb
import com.hp.hpl.jena.rdf.model.Model
-import java.net.URL
import org.apache.http.MethodNotSupportedException
import org.w3.readwriteweb.util._
import scalaz._
import Scalaz._
+import java.net.{ConnectException, URL}
/**
* @author Henry Story
@@ -69,7 +69,11 @@
}
})
- http(handler)
+ try {
+ http(handler)
+ } catch {
+ case e: ConnectException => e.fail
+ }
}
--- a/src/main/scala/auth/Principals.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/Principals.scala Sat Dec 03 16:12:05 2011 +0100
@@ -24,12 +24,12 @@
package org.w3.readwriteweb.auth
import java.security.Principal
-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._
+import java.net.{ConnectException, URL}
/**
* @author Henry Story from http://bblfish.net/
@@ -47,7 +47,7 @@
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
+ } else UnsupportedProtocol("only http and https url supported at present",subjectAlternativeName).fail
}
@@ -55,7 +55,7 @@
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
+ case e => URISyntaxError("unparseable Subject Alternative Name",urlStr).fail
}
}
@@ -77,8 +77,9 @@
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 ioe: WrappedIOException => new ProfileGetError("error fetching profile", Some(ioe),url)
+ case connE : ConnectException => new ProfileGetError("error fetching profile", Some(connE),url)
+ case other => new ProfileParseError("error parsing profile", Some(other),url)
}
}
--- a/src/main/scala/auth/WebIdClaim.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/WebIdClaim.scala Sat Dec 03 16:12:05 2011 +0100
@@ -29,7 +29,9 @@
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
import scalaz.{Scalaz, Validation}
import Scalaz._
-import org.w3.readwriteweb.util.wrapValidation
+import java.net.URL
+import java.security.PublicKey
+import com.hp.hpl.jena.rdf.model.Model
/**
@@ -52,24 +54,18 @@
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)
}
/**
* One has to construct a WebID using the object, that can do basic verifications
*/
-class WebIDClaim private (val webid: WebID, val rsakey: RSAPublicKey)(implicit cache: WebCache) {
+class WebIDClaim(val san: String, val key: PublicKey)(implicit cache: WebCache) {
+
import WebIDClaim.hex
import XSDDatatype._
- lazy val verify: Validation[WebIDClaimErr,WebID] =
- webid.getDefiningModel.failMap {
- case e => new WebIDClaimErr("could not fetch model", Some(e))
- }.flatMap { model =>
+ private def rsaTest(webid: WebID, rsakey: RSAPublicKey): (Model) => Validation[WebIDVerificationFailure, WebID] = {
+ model =>
val initialBinding = new QuerySolutionMap();
initialBinding.add("webid", model.createResource(webid.url.toString))
initialBinding.add("m", model.createTypedLiteral(hex(rsakey.getModulus.toByteArray), XSDhexBinary))
@@ -77,27 +73,43 @@
val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model, initialBinding)
try {
if (qe.execAsk()) webid.success
- else new WebIDClaimErr("could not verify public key").fail
+ else new WebIDVerificationFailure("could not verify public key", None, this).fail
} finally {
qe.close()
}
+ }
+
+ lazy val verify: Validation[WebIDClaimFailure, WebID] = key match {
+ case rsakey: RSAPublicKey =>
+ WebID(san).flatMap(webid=> webid.getDefiningModel.flatMap(rsaTest(webid, rsakey)) )
+ case _ => new UnsupportedKeyType("We only support RSA keys at present", key).fail
}
-}
+ }
+
trait Err {
+ type T <: AnyRef
val msg: String
val cause: Option[Throwable]=None
+ val subject: T
}
-abstract class Failure extends Throwable with Err
+abstract class Fail extends Throwable with Err
-abstract class SANFailure extends Failure
-case class UnsupportedProtocol(val msg: String) extends SANFailure
-case class URISyntaxError(val msg: String) extends SANFailure
+abstract class WebIDClaimFailure extends Fail
-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
+class UnsupportedKeyType(val msg: String, val subject: PublicKey) extends WebIDClaimFailure { type T = PublicKey }
+
+
+abstract class SANFailure extends WebIDClaimFailure { type T = String }
+case class UnsupportedProtocol(val msg: String, subject: String) extends SANFailure
+case class URISyntaxError(val msg: String, subject: String) extends SANFailure
+
+//The subject could be more refined than the URL, especially in the paring error
+abstract class ProfileError extends WebIDClaimFailure { type T = URL }
+case class ProfileGetError(val msg: String, override val cause: Option[Throwable], subject: URL) extends ProfileError
+case class ProfileParseError(val msg: String, override val cause: Option[Throwable], subject: URL) extends ProfileError
//it would be useful to pass the graph in
-class WebIDClaimErr(val msg: String, override val cause: Option[Throwable]=None) extends Failure
+class WebIDVerificationFailure(val msg: String, val caused: Option[Throwable], val subject: WebIDClaim)
+ extends WebIDClaimFailure { type T = WebIDClaim }
\ No newline at end of file
--- a/src/main/scala/auth/X509Claim.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/X509Claim.scala Sat Dec 03 16:12:05 2011 +0100
@@ -31,14 +31,13 @@
import javax.security.auth.Refreshable
import java.util.Date
import collection.JavaConversions._
-import java.util.concurrent.TimeUnit
-import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
-import javax.servlet.http.HttpServletRequest
import unfiltered.request.HttpRequest
import java.security.interfaces.RSAPublicKey
import collection.immutable.List
-import scalaz.{Success, Validation}
import collection.mutable.HashMap
+import scalaz.{Scalaz, Success, Validation}
+import Scalaz._
+import java.security.PublicKey
/**
* @author hjs
@@ -110,12 +109,8 @@
lazy val tooLate = claimReceivedDate.after(cert.getNotAfter())
lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
- /* a list of unverified principals */
- //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]) }
- lazy val webidclaims: List[WebIDClaim] = webidValidations.collect{ case Success(webIdClaim)=> webIdClaim }
+ lazy val webidclaims: List[WebIDClaim] = getClaimedWebIds(cert) map { webid => new WebIDClaim(webid, cert.getPublicKey.asInstanceOf[RSAPublicKey]) }
val verifiedClaims: List[WebID] = webidclaims.flatMap(_.verify.toOption)
--- a/src/main/scala/auth/X509view.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/X509view.scala Sat Dec 03 16:12:05 2011 +0100
@@ -77,37 +77,39 @@
class X509Filler(x509: X509Claim)(implicit cache: WebCache) extends Transformer {
$(".date").contents = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format( x509.claimReceivedDate)
$(".cert_test") { node =>
- val x509Tests = certOk.test(x509);
- val ff = for (tst <- x509Tests) yield {
+ val x509Assertion = new Assertion(certOk,x509);
+ val x509Assertions = x509Assertion::x509Assertion.depends
+ val ff = for (ast <- x509Assertions) yield {
new Transform(node) {
- $(".tst_question").contents = tst.of.title
- $(".tst_txt").contents = tst.of.description
- $(".tst_res").contents = tst.result.name
- $(".tst_res_txt").contents = tst.msg
+ $(".tst_question").contents = ast.test.title
+ $(".tst_txt").contents = ast.test.description
+ $(".tst_res").contents = ast.result.outcome.name
+ $(".tst_res_txt").contents = ast.result.description
}.toNodes()
}
ff.flatten
}
-// $(".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
+ $(".no_webid") { node => if (x509.verifiedClaims.size==0) node else <span/> }
+ $(".webid_test") { node =>
+ val ff = for (idclaim <- x509.webidclaims) yield {
+ val idAsrt = new Assertion(webidClaimTst, idclaim)
+ new Transform(node) {
+ $(".webid").contents = idclaim.san
+ $(".tst_res_txt").contents = idAsrt.result.description
+ $(".webid_cause") { n2 =>
+ val nn = for (a <- idAsrt.depends) yield {
+ new Transform(n2) {
+ $(".cause_question").contents = a.test.title
+ $(".cause_txt").contents = a.test.description
+ $(".cause_res").contents = a.result.outcome.name
+ }.toNodes()
+ }
+ nn.flatten
+ }
+ }.toNodes()
+ }
+ ff.flatten
+ }
+ $(".certificate").contents = x509.cert.toString
}
--- a/src/main/scala/auth/earl.scala Tue Nov 29 20:00:41 2011 +0100
+++ b/src/main/scala/auth/earl.scala Sat Dec 03 16:12:05 2011 +0100
@@ -24,11 +24,11 @@
package org.w3.readwriteweb.auth
import com.hp.hpl.jena.vocabulary.DCTerms
-import java.security.cert.X509Certificate
import java.security.interfaces.RSAPublicKey
import org.w3.readwriteweb.util.trySome
import java.lang.ref.WeakReference
import com.hp.hpl.jena.rdf.model.{Model, Property, ModelFactory}
+import scalaz.{Failure, Success}
/**
* Classes for the tests in WebID Authentication.
@@ -36,10 +36,10 @@
* The idea is to try to use the earl test cases we are defining at the WebID XG as
* a way of collecting tests done to prove the X509Claim and the WebIDClaim.
*
- * ( bblfish: theses classes feel very ad-hoc, and it is clearly a first pass:
- * - we are at the point currently of verifying which tests we need
- * - one gets the feeling these classes could even contain behavior
- * - we have instances of one class failing but not necessarily succeeding )
+ * These tests are now very close to the way the code is working. So much so that one can get
+ * feeling that perhaps by adding the URIs of these tests directly to the error codes one
+ * could merge a few classes. So perhaps with a bit of tuning a few classes here could just
+ * disappear.
*
*/
@@ -72,9 +72,9 @@
/**
* Test with extra information taken from the ontologies
*/
-class PubTest(val name: String) extends Test {
+abstract class OntTest(val name: String) extends Test {
import Tests._
- implicit def boolToResult(bool: Boolean): Result = if (bool) passed else failed
+ implicit def boolToResult(bool: Boolean): Outcome = if (bool) passed else failed
private def value(p: Property) = trySome(resource.getProperty(p).getLiteral.getLexicalForm) getOrElse "-missing-"
@@ -85,6 +85,16 @@
private def resource = model.getResource(ns+"#"+name)
}
+abstract class TestObj[T](name: String) extends OntTest(name) {
+ def apply(subj: T): Result
+ def depends(subj: T): List[Assert] =Nil
+}
+
+/**
+ * tests that we apply only to Exceptions
+ */
+class TestErr(name: String) extends OntTest(name)
+
//todo: (bblfish:) I get the feeling that one could put the logic into the tests directly.
// would that make things easier or better?
@@ -93,67 +103,122 @@
//
//for X509Claim
-object certProvided extends PubTest("certificateProvided") {
- 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 certProvided[X509Certificate] extends OntTest("certificateProvided") {
+// def apply(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 TestObj[X509Claim]("certificateOk") {
+ val parts = List[TestObj[X509Claim]](certDateOk, certProvidedSan,certPubKeyRecognized)
+
+ def apply(x509: X509Claim): Result = {
+ val deps = depends(x509)
+ val res = deps.forall(_.result.outcome == passed)
+ new Result(if (res) "X509 Certificate Good" else "X509 Certificate had problems",
+ res, deps)
+ }
+ override def depends(x509: X509Claim): List[Assert] = parts.map(test=>new Assertion[X509Claim](test,x509))
}
-object certOk extends PubTest("certificateOk") {
- 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 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 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 Assertion(this, x509.isCurrent,
- "the x509certificate " + (
- if (x509.tooEarly) "is not yet valid "
- else if (x509.tooLate) " passed its validity date "
- else " is valid")
- )
-
+object certProvidedSan extends TestObj[X509Claim]("certificateProvidedSAN") {
+ def apply(x509: X509Claim) = new Result(" There are "+x509.webidclaims.size+" SANs in the certificate",
+ x509.webidclaims.size >0)
+
}
-object certPubKeyRecognized extends PubTest("certificatePubkeyRecognised") {
- def test(claim: X509Claim) = {
+object certDateOk extends TestObj[X509Claim]("certificateDateOk") {
+ def apply(x509: X509Claim) =
+ new Result("the x509certificate " + (
+ if (x509.tooEarly) "is not yet valid "
+ else if (x509.tooLate) " passed its validity date "
+ else " is valid"),
+ x509.isCurrent)
+
+}
+
+object certPubKeyRecognized extends TestObj[X509Claim]("certificatePubkeyRecognised") {
+ def apply(claim: X509Claim) = {
val pk = claim.cert.getPublicKey;
- new Assertion(this, pk.isInstanceOf[RSAPublicKey], "We only support RSA Keys at present. " )
+ new Result("We only support RSA Keys at present. ",pk.isInstanceOf[RSAPublicKey] )
}
}
//for WebIDClaims
-object webidClaimTst extends PubTest("webidClaim")
-object pubkeyTypeTst extends PubTest("certificatePubkeyRecognised")
-object profileGetTst extends PubTest("profileGet")
-object profileParseTst extends PubTest("profileWellFormed")
-object profileOkTst extends PubTest("profileOk")
-object profileWellFormedKeyTst extends PubTest("profileWellFormedPubkey")
+object webidClaimTst extends TestObj[WebIDClaim]("webidClaim") {
+ def apply(webIdclaim: WebIDClaim) : Result =
+ webIdclaim.verify match {
+ case Success(webId) => new Result("WebId successfully verified",passed)
+ case Failure(webIdClaimErr: WebIDVerificationFailure) =>
+ new Result("keys in certificate don't match key in profile for "+webIdClaimErr.subject,failed)
+ case Failure(e) => new Result("WebID verification failed",failed,cause=List(new AssertionErr(e)))
+ }
+
+ override def depends(webIdclaim: WebIDClaim): List[Assert] =
+ webIdclaim.verify match {
+ case Failure(e) if !e.isInstanceOf[WebIDVerificationFailure] => new AssertionErr(e)::Nil
+ case _ => Nil
+ }
+}
-/** 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 )
+
+object profileGetTst extends TestErr("profileGet")
+object profileParseTst extends TestErr("profileWellFormed")
+object sanOk extends TestErr("sanOK")
+
+//object profileOkTst extends OntTest("profileOk") {
+// def test(err: ProfileError): Assertion = new Assertion(this,err)::err match {
+// case getError : ProfileGetError => profileGetTst.test(getError)
+// case ProfileParseError(parseError: Fail) => new Assertion(profileParseTst,parseError)
+// }
+//}
+//object profileWellFormedKeyTst extends OntTest("profileWellFormedPubkey")
+
+object Assertion {
+ def name(fail: WebIDClaimFailure): Test = {
+ fail match {
+ case e: UnsupportedProtocol => sanOk
+ case e: URISyntaxError => sanOk
+ case e: ProfileGetError => profileGetTst
+ case e: ProfileParseError => profileParseTst
+ case e: UnsupportedKeyType => certPubKeyRecognized
+ }
+ }
+}
+
+trait Assert {
+ val subject: AnyRef
+ val test: Test
+ val result: Result
+}
+
+class Assertion[T<:AnyRef]( val test: TestObj[T], val subject: T ) extends Assert {
+ override val result: Result = test(subject)
+ val depends: List[Assert] = test.depends(subject)
+}
+
+class AssertionErr(val fail: WebIDClaimFailure) extends Assert {
+ import Assertion.name
+ val subject = fail.subject
+ val test = name(fail)
+ val result: Result = new Result(fail.msg,failed)
+}
+
+class Result (val description: String,
+ val outcome: Outcome,
+ val cause: List[Assert] = Nil)
-sealed class Result(val name: String) {
+
+sealed class Outcome(val name: String) {
val earl = "http://www.w3.org/ns/earl#"
val id = earl+name
}
-object untested extends Result("untested")
-object passed extends Result("passed")
-object failed extends Result("failed")
+object untested extends Outcome("untested")
+object passed extends Outcome("passed")
+object failed extends Outcome("failed")