updated to latest spec ( http://webid.info/spec ) which simplifies dramatically the code - it is not accidental. Also upgraded to spec2 which seems to work. webid
authorHenry Story <henry.story@bblfish.net>
Thu, 24 Nov 2011 13:53:25 +0100
branchwebid
changeset 140 9993d3594e1a
parent 139 cc0fb90aa327
child 141 6d3921ea581a
updated to latest spec ( http://webid.info/spec ) which simplifies dramatically the code - it is not accidental. Also upgraded to spec2 which seems to work.
project/build.scala
src/main/scala/auth/WebIdClaim.scala
src/test/scala/auth/CreateWebIDSpec.scala
src/test/scala/auth/secure_specs.scala
--- a/project/build.scala	Wed Nov 23 15:22:01 2011 +0100
+++ b/project/build.scala	Thu Nov 24 13:53:25 2011 +0100
@@ -4,7 +4,9 @@
 // some usefull libraries
 // they are pulled only if used
 object Dependencies {
-  val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
+//  val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
+  val specs2 = "org.specs2" %% "specs2" % "1.6.1"
+  val specs2_scalaz =  "org.specs2" %% "specs2-scalaz-core" % "6.0.1" % "test"
   val dispatch_http = "net.databinder" %% "dispatch-http" % "0.8.5" 
   val unfiltered_version = "0.5.1"
   val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % unfiltered_version 
@@ -82,7 +84,8 @@
       resolvers += mavenLocal,
       resolvers += ScalaToolsReleases,
       resolvers += ScalaToolsSnapshots,
-      libraryDependencies += specs,
+      libraryDependencies += specs2,
+      libraryDependencies += specs2_scalaz,
 //      libraryDependencies += unfiltered_spec,
       ivyXML := ivyUnfilteredSpec,
       libraryDependencies += dispatch_http,
--- a/src/main/scala/auth/WebIdClaim.scala	Wed Nov 23 15:22:01 2011 +0100
+++ b/src/main/scala/auth/WebIdClaim.scala	Thu Nov 24 13:53:25 2011 +0100
@@ -23,13 +23,12 @@
 
 package org.w3.readwriteweb.auth
 
-import java.math.BigInteger
 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 com.hp.hpl.jena.rdf.model.RDFNode
 import java.net.URL
+import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
 
 /**
  * @author hjs
@@ -37,101 +36,16 @@
  */
 
 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 ?m ?e ?mod ?exp
-      WHERE {
-         {
-           ?key  cert:identity ?webid .
-         } UNION {
-           ?webid cert:key ?key .
-         }
-          ?key rsa:modulus ?m ;
-               rsa:public_exponent ?e .
-
-       OPTIONAL { ?m cert:hex ?mod . }
-       OPTIONAL { ?e cert:decimal ?exp . }
-      }""") //Including OPTIONAL notation, for backward compatibility - should remove that after a while
-
-  /**
-    * 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
-
+    final val cert: String = "http://www.w3.org/ns/auth/cert#"
 
-    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)
-    }
-
+    val askQuery = QueryFactory.create("""
+      PREFIX : <http://www.w3.org/ns/auth/cert#>
+      ASK {
+          ?webid :key [ :modulus ?m ;
+                        :exponent ?e ].
+      }""")
 
-   /**
-    * 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
-     }
-
+     def hex(bytes: Array[Byte]): String = bytes.map("%02X" format _).mkString.stripPrefix("00")
 
 }
 
@@ -147,6 +61,8 @@
 
 	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 = {
@@ -157,7 +73,6 @@
   private def verify(implicit cache: WebCache): List[Verification] = {
     import org.w3.readwriteweb.util.wrapValidation
 
-    import collection.JavaConversions._
     import WebIDClaim._
     try {
       return if (!webId.startsWith("http:") && !webId.startsWith("https:")) {
@@ -171,32 +86,21 @@
             t => new ProfileError("error fetching profile", t)
           }
         } yield {
+          val rsakey = key.asInstanceOf[RSAPublicKey]
           val initialBinding = new QuerySolutionMap();
           initialBinding.add("webid",model.createResource(webId))
-          val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.selectQuery, model,initialBinding)
+          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 {
-            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
+            if (qe.execAsk()) verifiedWebID
+            else noMatchingKey
           } finally {
             qe.close()
           }
         }
         res.either match {
-          case Right(tests) => tests
+          case Right(tests) => tests::Nil
           case Left(profileErr) => profileErr::Nil
         }
       }
@@ -206,8 +110,6 @@
 
 
   }
-  
-
 
 	def canEqual(other: Any) = other.isInstanceOf[WebIDClaim]
 
--- a/src/test/scala/auth/CreateWebIDSpec.scala	Wed Nov 23 15:22:01 2011 +0100
+++ b/src/test/scala/auth/CreateWebIDSpec.scala	Thu Nov 24 13:53:25 2011 +0100
@@ -34,6 +34,7 @@
 import javax.net.ssl._
 import java.io.File
 import org.w3.readwriteweb.{Post, RDFXML, TURTLE}
+import org.apache.commons.codec.binary.Hex
 
 
 /**
@@ -101,11 +102,11 @@
 
   val updatePk = """
        PREFIX cert: <http://www.w3.org/ns/auth/cert#>
-       PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
+       PREFIX xsd:    <http://www.w3.org/2001/XMLSchema#>
        PREFIX : <#>
        INSERT DATA {
-         :jL cert:key [ rsa:modulus "%s"^^cert:hex;
-                        rsa:public_exponent "%s"^^cert:int ] .
+         :jL cert:key [ cert:modulus "%s"^^xsd:hexBinary;
+                        cert:exponent "%s"^^xsd:integer ] .
        }
   """
 
@@ -181,8 +182,9 @@
 
        val joeKey = joeCert(0).getPublicKey.asInstanceOf[RSAPublicKey]
 
+       val hex = new String(Hex.encodeHex(joeKey.getModulus.toByteArray))
        val updateQStr = updatePk.format(
-                     joeKey.getModulus.toString(16),
+                     hex.stripPrefix("00"),
                      joeKey.getPublicExponent()
        )
 
--- a/src/test/scala/auth/secure_specs.scala	Wed Nov 23 15:22:01 2011 +0100
+++ b/src/test/scala/auth/secure_specs.scala	Thu Nov 24 13:53:25 2011 +0100
@@ -35,6 +35,8 @@
 import dispatch.Http
 import org.apache.http.client.HttpClient
 import javax.net.ssl.{SSLContext, X509TrustManager, KeyManager}
+import util.trySome
+import java.nio.file.Files
 
 /**
  * @author hjs
@@ -122,7 +124,18 @@
 
   lazy val baseURL = "/wiki"
 
-  lazy val root = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
+  /**
+   * finding where the specs2 output directory is, so that we can create temporary directories there,
+   * which can then be viewed if tests are unsuccessful, but that will also be removed on "sbt clean"
+   */
+  lazy val outDirBase = new File(trySome { System.getProperty("spec2.outDir") } getOrElse  "target/specs2-reports/")
+
+  lazy val root = {
+    outDirBase.mkdirs()
+    val dir = Files.createTempDirectory(outDirBase.toPath, "test_rww_")
+    System.out.println("Temp directory: "+dir.toString)
+    dir.toFile
+  }
 
   lazy val resourceManager = new Filesystem(root, baseURL, lang)(mode)