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 1409993d3594e1a
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
     1.1 --- a/project/build.scala	Wed Nov 23 15:22:01 2011 +0100
     1.2 +++ b/project/build.scala	Thu Nov 24 13:53:25 2011 +0100
     1.3 @@ -4,7 +4,9 @@
     1.4  // some usefull libraries
     1.5  // they are pulled only if used
     1.6  object Dependencies {
     1.7 -  val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
     1.8 +//  val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
     1.9 +  val specs2 = "org.specs2" %% "specs2" % "1.6.1"
    1.10 +  val specs2_scalaz =  "org.specs2" %% "specs2-scalaz-core" % "6.0.1" % "test"
    1.11    val dispatch_http = "net.databinder" %% "dispatch-http" % "0.8.5" 
    1.12    val unfiltered_version = "0.5.1"
    1.13    val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % unfiltered_version 
    1.14 @@ -82,7 +84,8 @@
    1.15        resolvers += mavenLocal,
    1.16        resolvers += ScalaToolsReleases,
    1.17        resolvers += ScalaToolsSnapshots,
    1.18 -      libraryDependencies += specs,
    1.19 +      libraryDependencies += specs2,
    1.20 +      libraryDependencies += specs2_scalaz,
    1.21  //      libraryDependencies += unfiltered_spec,
    1.22        ivyXML := ivyUnfilteredSpec,
    1.23        libraryDependencies += dispatch_http,
     2.1 --- a/src/main/scala/auth/WebIdClaim.scala	Wed Nov 23 15:22:01 2011 +0100
     2.2 +++ b/src/main/scala/auth/WebIdClaim.scala	Thu Nov 24 13:53:25 2011 +0100
     2.3 @@ -23,13 +23,12 @@
     2.4  
     2.5  package org.w3.readwriteweb.auth
     2.6  
     2.7 -import java.math.BigInteger
     2.8  import java.security.PublicKey
     2.9  import org.w3.readwriteweb.WebCache
    2.10  import java.security.interfaces.RSAPublicKey
    2.11  import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
    2.12 -import com.hp.hpl.jena.rdf.model.RDFNode
    2.13  import java.net.URL
    2.14 +import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
    2.15  
    2.16  /**
    2.17   * @author hjs
    2.18 @@ -37,101 +36,16 @@
    2.19   */
    2.20  
    2.21  object WebIDClaim {
    2.22 -     final val cert: String = "http://www.w3.org/ns/auth/cert#"
    2.23 -     final val xsd: String = "http://www.w3.org/2001/XMLSchema#"
    2.24 +    final val cert: String = "http://www.w3.org/ns/auth/cert#"
    2.25  
    2.26 -    val selectQuery = QueryFactory.create("""
    2.27 -      PREFIX cert: <http://www.w3.org/ns/auth/cert#>
    2.28 -      PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
    2.29 -      SELECT ?m ?e ?mod ?exp
    2.30 -      WHERE {
    2.31 -         {
    2.32 -           ?key  cert:identity ?webid .
    2.33 -         } UNION {
    2.34 -           ?webid cert:key ?key .
    2.35 -         }
    2.36 -          ?key rsa:modulus ?m ;
    2.37 -               rsa:public_exponent ?e .
    2.38 +    val askQuery = QueryFactory.create("""
    2.39 +      PREFIX : <http://www.w3.org/ns/auth/cert#>
    2.40 +      ASK {
    2.41 +          ?webid :key [ :modulus ?m ;
    2.42 +                        :exponent ?e ].
    2.43 +      }""")
    2.44  
    2.45 -       OPTIONAL { ?m cert:hex ?mod . }
    2.46 -       OPTIONAL { ?e cert:decimal ?exp . }
    2.47 -      }""") //Including OPTIONAL notation, for backward compatibility - should remove that after a while
    2.48 -
    2.49 -  /**
    2.50 -    * Transform an RDF representation of a number into a BigInteger
    2.51 -    * <p/>
    2.52 -    * Passes a statement as two bindings and the relation between them. The
    2.53 -    * subject is the number. If num is already a literal number, that is
    2.54 -    * returned, otherwise if enough information from the relation to optstr
    2.55 -    * exists, that is used.
    2.56 -    *
    2.57 -    * @param num the number node
    2.58 -    * @param optRel name of the relation to the literal
    2.59 -    * @param optstr the literal representation if it exists
    2.60 -    * @return the big integer that num represents, or null if undetermined
    2.61 -    */
    2.62 -   def toInteger(num: RDFNode, optRel: String, optstr: RDFNode): Option[BigInteger] =
    2.63 -       if (null == num) None
    2.64 -       else if (num.isLiteral) {
    2.65 -         val lit = num.asLiteral()
    2.66 -         toInteger_helper(lit.getLexicalForm,lit.getDatatypeURI)
    2.67 -       } else if (null != optstr && optstr.isLiteral) {
    2.68 -           toInteger_helper(optstr.asLiteral().getLexicalForm,optRel)
    2.69 -       } else None
    2.70 -
    2.71 -
    2.72 -    private def intValueOfHexString(s: String): BigInteger = {
    2.73 -      val strval = cleanHex(s);
    2.74 -      new BigInteger(strval, 16);
    2.75 -    }
    2.76 -
    2.77 -
    2.78 -    /**
    2.79 -     * This takes any string and returns in order only those characters that are
    2.80 -     * part of a hex string
    2.81 -     *
    2.82 -     * @param strval
    2.83 -     *            any string
    2.84 -     * @return a pure hex string
    2.85 -     */
    2.86 -
    2.87 -    private def cleanHex(strval: String) = {
    2.88 -      def legal(c: Char) = {
    2.89 -        //in order of appearance probability
    2.90 -        ((c >= '0') && (c <= '9')) ||
    2.91 -          ((c >= 'A') && (c <= 'F')) ||
    2.92 -          ((c >= 'a') && (c <= 'f'))
    2.93 -      }
    2.94 -      (for (c <- strval; if legal(c)) yield c)
    2.95 -    }
    2.96 -
    2.97 -
    2.98 -   /**
    2.99 -    * This transforms a literal into a number if possible ie, it returns the
   2.100 -    * BigInteger of "ddd"^^type
   2.101 -    *
   2.102 -    * @param num the string representation of the number
   2.103 -    * @param tpe the type of the string representation
   2.104 -    * @return the number
   2.105 -    */
   2.106 -   protected def toInteger_helper(num: String, tpe: String): Option[BigInteger] =
   2.107 -     try {
   2.108 -       if (tpe.equals(cert + "decimal") || tpe.equals(cert + "int")
   2.109 -         || tpe.equals(xsd + "integer") || tpe.equals(xsd + "int")
   2.110 -         || tpe.equals(xsd + "nonNegativeInteger")) {
   2.111 -         // cert:decimal is deprecated
   2.112 -         Some(new BigInteger(num.trim(), 10));
   2.113 -       } else if (tpe.equals(cert + "hex")) {
   2.114 -         Some(intValueOfHexString(num));
   2.115 -       } else {
   2.116 -         // it could be some other encoding - one should really write a
   2.117 -         // special literal transformation class
   2.118 -         None;
   2.119 -       }
   2.120 -     } catch {
   2.121 -       case e: NumberFormatException => None
   2.122 -     }
   2.123 -
   2.124 +     def hex(bytes: Array[Byte]): String = bytes.map("%02X" format _).mkString.stripPrefix("00")
   2.125  
   2.126  }
   2.127  
   2.128 @@ -147,6 +61,8 @@
   2.129  
   2.130  	var tests: List[Verification] = List()
   2.131  
   2.132 +  /** the tests have been done and are still valid - the idea is perhaps after a time tests would
   2.133 +   * have to be done again? Eg: the claim is cached and re-used after a while */
   2.134    private var valid = false
   2.135  
   2.136    def verified(implicit cache: WebCache): Boolean = {
   2.137 @@ -157,7 +73,6 @@
   2.138    private def verify(implicit cache: WebCache): List[Verification] = {
   2.139      import org.w3.readwriteweb.util.wrapValidation
   2.140  
   2.141 -    import collection.JavaConversions._
   2.142      import WebIDClaim._
   2.143      try {
   2.144        return if (!webId.startsWith("http:") && !webId.startsWith("https:")) {
   2.145 @@ -171,32 +86,21 @@
   2.146              t => new ProfileError("error fetching profile", t)
   2.147            }
   2.148          } yield {
   2.149 +          val rsakey = key.asInstanceOf[RSAPublicKey]
   2.150            val initialBinding = new QuerySolutionMap();
   2.151            initialBinding.add("webid",model.createResource(webId))
   2.152 -          val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.selectQuery, model,initialBinding)
   2.153 +          initialBinding.add("m",model.createTypedLiteral( hex(rsakey.getModulus.toByteArray), XSDDatatype.XSDhexBinary))
   2.154 +          initialBinding.add("e",model.createTypedLiteral( rsakey.getPublicExponent.toString, XSDDatatype.XSDinteger ))
   2.155 +          val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model,initialBinding)
   2.156            try {
   2.157 -            qe.execSelect().map( qs => {
   2.158 -              val modulus = toInteger(qs.get("m"), cert + "hex", qs.get("mod"))
   2.159 -              val exponent = toInteger(qs.get("e"), cert + "decimal", qs.get("exp"))
   2.160 -
   2.161 -              (modulus, exponent) match {
   2.162 -                case (Some(mod), Some(exp)) => {
   2.163 -                  val rsakey = key.asInstanceOf[RSAPublicKey]
   2.164 -                  if (rsakey.getPublicExponent == exp && rsakey.getModulus == mod) verifiedWebID
   2.165 -                  else keyDoesNotMatch
   2.166 -                }
   2.167 -                case _ => new KeyProblem("profile contains key that cannot be analysed:" +
   2.168 -                  qs.varNames().map(nm => nm + "=" + qs.get(nm).toString) + "; ")
   2.169 -              }
   2.170 -            }).toList
   2.171 -            //it would be nice if we could keep a lot more state of what was verified and how
   2.172 -            //will do that when implementing tests, so that these tests can then be used directly as much as possible
   2.173 +            if (qe.execAsk()) verifiedWebID
   2.174 +            else noMatchingKey
   2.175            } finally {
   2.176              qe.close()
   2.177            }
   2.178          }
   2.179          res.either match {
   2.180 -          case Right(tests) => tests
   2.181 +          case Right(tests) => tests::Nil
   2.182            case Left(profileErr) => profileErr::Nil
   2.183          }
   2.184        }
   2.185 @@ -206,8 +110,6 @@
   2.186  
   2.187  
   2.188    }
   2.189 -  
   2.190 -
   2.191  
   2.192  	def canEqual(other: Any) = other.isInstanceOf[WebIDClaim]
   2.193  
     3.1 --- a/src/test/scala/auth/CreateWebIDSpec.scala	Wed Nov 23 15:22:01 2011 +0100
     3.2 +++ b/src/test/scala/auth/CreateWebIDSpec.scala	Thu Nov 24 13:53:25 2011 +0100
     3.3 @@ -34,6 +34,7 @@
     3.4  import javax.net.ssl._
     3.5  import java.io.File
     3.6  import org.w3.readwriteweb.{Post, RDFXML, TURTLE}
     3.7 +import org.apache.commons.codec.binary.Hex
     3.8  
     3.9  
    3.10  /**
    3.11 @@ -101,11 +102,11 @@
    3.12  
    3.13    val updatePk = """
    3.14         PREFIX cert: <http://www.w3.org/ns/auth/cert#>
    3.15 -       PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
    3.16 +       PREFIX xsd:    <http://www.w3.org/2001/XMLSchema#>
    3.17         PREFIX : <#>
    3.18         INSERT DATA {
    3.19 -         :jL cert:key [ rsa:modulus "%s"^^cert:hex;
    3.20 -                        rsa:public_exponent "%s"^^cert:int ] .
    3.21 +         :jL cert:key [ cert:modulus "%s"^^xsd:hexBinary;
    3.22 +                        cert:exponent "%s"^^xsd:integer ] .
    3.23         }
    3.24    """
    3.25  
    3.26 @@ -181,8 +182,9 @@
    3.27  
    3.28         val joeKey = joeCert(0).getPublicKey.asInstanceOf[RSAPublicKey]
    3.29  
    3.30 +       val hex = new String(Hex.encodeHex(joeKey.getModulus.toByteArray))
    3.31         val updateQStr = updatePk.format(
    3.32 -                     joeKey.getModulus.toString(16),
    3.33 +                     hex.stripPrefix("00"),
    3.34                       joeKey.getPublicExponent()
    3.35         )
    3.36  
     4.1 --- a/src/test/scala/auth/secure_specs.scala	Wed Nov 23 15:22:01 2011 +0100
     4.2 +++ b/src/test/scala/auth/secure_specs.scala	Thu Nov 24 13:53:25 2011 +0100
     4.3 @@ -35,6 +35,8 @@
     4.4  import dispatch.Http
     4.5  import org.apache.http.client.HttpClient
     4.6  import javax.net.ssl.{SSLContext, X509TrustManager, KeyManager}
     4.7 +import util.trySome
     4.8 +import java.nio.file.Files
     4.9  
    4.10  /**
    4.11   * @author hjs
    4.12 @@ -122,7 +124,18 @@
    4.13  
    4.14    lazy val baseURL = "/wiki"
    4.15  
    4.16 -  lazy val root = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
    4.17 +  /**
    4.18 +   * finding where the specs2 output directory is, so that we can create temporary directories there,
    4.19 +   * which can then be viewed if tests are unsuccessful, but that will also be removed on "sbt clean"
    4.20 +   */
    4.21 +  lazy val outDirBase = new File(trySome { System.getProperty("spec2.outDir") } getOrElse  "target/specs2-reports/")
    4.22 +
    4.23 +  lazy val root = {
    4.24 +    outDirBase.mkdirs()
    4.25 +    val dir = Files.createTempDirectory(outDirBase.toPath, "test_rww_")
    4.26 +    System.out.println("Temp directory: "+dir.toString)
    4.27 +    dir.toFile
    4.28 +  }
    4.29  
    4.30    lazy val resourceManager = new Filesystem(root, baseURL, lang)(mode)
    4.31