~ merge with webid branch, starting code-review
authorAlexandre Bertails <bertails@gmail.com>
Sat, 12 Nov 2011 08:33:10 -0500
changeset 113 94123d365eae
parent 112 2afbc8fdc6f8 (current diff)
parent 111 5497f1957ad0 (diff)
child 114 7781eb6764bd
~ merge with webid branch, starting code-review
.hgignore
src/main/scala/Resource.scala
--- a/.hgignore	Sat Nov 12 08:29:00 2011 -0500
+++ b/.hgignore	Sat Nov 12 08:33:10 2011 -0500
@@ -17,4 +17,6 @@
 .scala_dependencies
 *.orig
 .cache
-.settings
\ No newline at end of file
+.settings
+.idea
+.idea_modules
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/INSTALL.txt	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,84 @@
+
+0. download code using mercurial, and switch to the webid branch
+  $ hg clone https://dvcs.w3.org/hg/read-write-web
+  $ cd read-write-web
+  $ hg checkout webid
+
+1. download java 6 or above for your Operating System - don't download just the JRE but also the java tools
+
+2. download scala http://www.scala-lang.org/
+
+3. download the scala build tool ( sbt ) from https://github.com/harrah/xsbt 
+   I have an xsbt shell script that contains
+    java $SBT_PROPS -Xmx512m -Dfile.encoding=UTF-8 -jar `dirname $0`/jars/sbt-launch-0.11.0.jar "$@"
+
+4. set up some environmental variables for the https server to run on
+
+ export SBT_PROPS='-Djetty.ssl.keyStoreType=JKS -Djetty.ssl.keyStore=keys/KEYSTORE.jks -Djetty.ssl.keyStorePassword=secret'  
+
+  Notice if you want to avoid browser warnings and you want to put this up on a public site then you need to get a CA signed certificate for your domain. There are providers that give those for free. There are protocols that will appear to make this no longer necessary
+
+5. run sbt, then start the server from the command line
+
+  $ sbt
+  >  run --https 8443 test_www /2011/09
+
+  This will compile the server, start the https port on 8442 and use files in the test_www directory so that you can access them at https://localhost:8443/2011/09/
+
+6. connect to different resources
+
+6.1 GETing a public resource
+
+ $ curl -k https://localhost:8443/public/ 
+
+  If you access this via your browser and you have more than one webid certificate, your browser will ask you for  even when you access this. We are working on a solution to stop this from happening.
+
+6.2 GETing a protected resource
+
+The following is a protected resource so if you access it without authentification credentials you will get  
+
+ $  curl  -i -k https://localhost:8443/2011/09/foaf.n3
+HTTP/1.1 401 Unauthorized
+Content-Length: 0
+Server: Jetty(7.2.2.v20101205)
+   
+If you have a WebID enabled certificate ( http://webid.info/ ) with the public and private keys in a pem file, then you can access the resource with, 
+
+ $ curl -E Certificates.pem -i -k https://localhost:8443/2011/09/foaf.n3
+
+since only Henry Story can view it as seen from the file
+
+  $ cat test_www/foaf.n3.protect.n3 
+
+@prefix acl: <http://www.w3.org/ns/auth/acl#> .
+@prefix : <#> .
+
+:a1 a acl:Authorization;
+   acl:accessTo <foaf.n3>;
+   acl:mode acl:Read;
+   acl:agent <http://bblfish.net/people/henry/card#me> .
+
+If you want to give yourself access then replace Henry's WebID, "http://bblfish.net/people/henry/card#me"  with your own, or make another example for that.
+
+6.3 Uploading a resource
+
+Say you wanted to upload an RDF file to the server
+
+$ curl http://bblfish.net/people/henry/card.rdf | curl -i -k  -H "Content-Type: application/rdf+xml" -X PUT https://localhost:8443/2011/09/test2.rdf -T - 
+HTTP/1.1 100 Continue
+
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100 24386  100 24386    0     0   2223      0  0:00:10  0:00:10 --:--:-- 33405
+HTTP/1.1 201 Created
+Content-Length: 0
+Server: Jetty(7.2.2.v20101205)
+
+the file will then be available as /2011/09/test2.rdf  on the localhost server
+
+6.4 querying a resource with SPARQL 
+
+You can write a SPARQL query and then query the given model by POSTing the query to the resource
+
+curl -k -i -H "Content-Type: application/sparql-query"  -X POST https://localhost:8443/2011/09/test.rdf -T queryfriends.sparql
+
--- a/README.markdown	Sat Nov 12 08:29:00 2011 -0500
+++ b/README.markdown	Sat Nov 12 08:33:10 2011 -0500
@@ -73,3 +73,15 @@
  *   --strict  Documents must be created using PUT else they return 404
     
     
+HTTPS with WebID 
+----------------
+
+### to run on https with WebID
+    1. make a directory called tmp 
+    2. lauch
+    > java -Djetty.ssl.keyStoreType=JKS -Djetty.ssl.keyStore=/Users/hjs/tmp/cert/KEYSTORE.jks -Djetty.ssl.keyStorePassword=secret -jar target/read-write-web.jar --https 8443 tmp /2011/09
+
+### to enable debug add the following parameters after 'java'
+
+     -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/rwsbt.sh	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,27 @@
+#!/bin/bash 
+
+KS=src/test/resources/KEYSTORE.jks
+while [ $# -gt 0 ] 
+do 
+ case $1 in 
+  -d) PROPS="$PROPS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
+   ;; 
+  -n) PROPS="$PROPS -Dnetty.ssl.keyStoreType=JKS -Dnetty.ssl.keyStore=$KS -Dnetty.ssl.keyStorePassword=secret" 
+   ;;
+  -j) PROPS="$PROPS -Djetty.ssl.keyStoreType=JKS -Djetty.ssl.keyStore=$KS -Djetty.ssl.keyStorePassword=secret"
+   ;;
+  -sslUnsafe) PROPS="$PROPS -Dsun.security.ssl.allowUnsafeRenegotiation=true"
+   ;; # see: http://download.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#workarounds
+  -sslLegacy) PROPS="$PROPS -Dsun.security.ssl.allowLegacyHelloMessages=true"
+   ;;
+  -sslDebug) PROPS="$PROPS -Djavax.net.debug=all"
+   ;; # see http://download.oracle.com/javase/1,5,0/docs/guide/security/jsse/ReadDebug.html
+  *) echo the arguments to use are -d
+   ;;
+  esac
+  shift 1
+ done
+
+
+export SBT_PROPS=$PROPS
+xsbt 
--- a/project/build.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/project/build.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -5,28 +5,36 @@
 // they are pulled only if used
 object Dependencies {
   val specs = "org.scala-tools.testing" %% "specs" % "1.6.9" % "test"
-  val dispatch = "net.databinder" %% "dispatch-http" % "0.8.5" % "test"
-  val unfiltered_filter = "net.databinder" %% "unfiltered-filter" % "0.4.1"
-  val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % "0.4.1"
+  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 
+  val unfiltered_jetty = "net.databinder" %% "unfiltered-jetty" % unfiltered_version 
+  val unfiltered_netty = "net.databinder" %% "unfiltered-netty" % unfiltered_version 
   // val unfiltered_spec = "net.databinder" %% "unfiltered-spec" % "0.4.1" % "test"
   val ivyUnfilteredSpec =
     <dependencies>
-      <dependency org="net.databinder" name="unfiltered-spec_2.9.1" rev="0.4.1">
+      <dependency org="net.databinder" name="unfiltered-spec_2.9.1" rev={unfiltered_version}>
         <exclude org="net.databinder" module="dispatch-mime_2.9.0-1"/>
       </dependency>
     </dependencies>
-  val slf4jSimple = "org.slf4j" % "slf4j-simple" % "1.5.8"
+  val slf4jSimple = "org.slf4j" % "slf4j-simple" % "1.6"
   val antiXML = "com.codecommit" %% "anti-xml" % "0.4-SNAPSHOT" % "test"
   val jena = "com.hp.hpl.jena" % "jena" % "2.6.4"
   val arq = "com.hp.hpl.jena" % "arq" % "2.8.8"
   val grizzled = "org.clapper" %% "grizzled-scala" % "1.0.8" % "test"
   val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.2"
+
   val argot =  "org.clapper" %% "argot" % "0.3.5"
+  val guava =  "com.google.guava" % "guava" % "10.0.1"
+//  val restlet = "org.restlet.dev" % "org.restlet" % "2.1-SNAPSHOT"
+//  val restlet_ssl = "org.restlet.dev" % "org.restlet.ext.ssl" % "2.1-SNAPSHOT"
+  val jsslutils = "org.jsslutils" % "jsslutils" % "1.0.5"
 }
 
 // some usefull repositories
 object Resolvers {
   val novus = "repo.novus snaps" at "http://repo.novus.com/snapshots/"
+  val mavenLocal = "Local Maven Repository" at "file://" + (Path.userHome / ".m2" / "repository").absolutePath
 }
 
 // general build settings
@@ -71,24 +79,31 @@
 
   val projectSettings =
     Seq(
+      resolvers += mavenLocal,
       resolvers += ScalaToolsReleases,
       resolvers += ScalaToolsSnapshots,
       libraryDependencies += specs,
 //      libraryDependencies += unfiltered_spec,
       ivyXML := ivyUnfilteredSpec,
-      libraryDependencies += dispatch,
+      libraryDependencies += dispatch_http,
       libraryDependencies += unfiltered_filter,
       libraryDependencies += unfiltered_jetty,
+      libraryDependencies += unfiltered_netty,
 //      libraryDependencies += slf4jSimple,
       libraryDependencies += jena,
       libraryDependencies += arq,
       libraryDependencies += antiXML,
       libraryDependencies += grizzled,
       libraryDependencies += scalaz,
-      jarName in assembly := "read-write-web.jar",
-      libraryDependencies += argot
+      libraryDependencies += jsslutils,
+      libraryDependencies += argot,
+      libraryDependencies += guava,
+
+      jarName in assembly := "read-write-web.jar"
     )
 
+
+
   lazy val project = Project(
     id = "read-write-web",
     base = file("."),
--- a/src/main/scala/Authoritative.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/Authoritative.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,14 +1,49 @@
+/*
+ * 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
 
 import unfiltered.request._
-import java.net.URL
+import java.security.cert.Certificate
+import javax.servlet.http.HttpServletRequest
+import unfiltered.netty.ReceivedMessage
+import org.eclipse.jetty.util.URIUtil
+import java.net.{MalformedURLException, URL}
 
 object Authoritative {
   
   val r = """^(.*)\.(\w{0,4})$""".r
-  
-  def unapply(req: HttpRequest[javax.servlet.http.HttpServletRequest]): Option[(URL, Representation)] = {
-    val uri = req.underlying.getRequestURL.toString
+
+  // all of this would be unnecessary if req.uri would really return the full URI
+  // we should try to push for that to be done at unfiltered layer
+  def reqURL[T](m: Manifest[T], r: HttpRequest[T]): String = {
+    if (m <:< manifest[HttpServletRequest]) reqUrlServlet(r.asInstanceOf[HttpRequest[HttpServletRequest]])
+    else if (m <:< manifest[ReceivedMessage]) reqUrlNetty(r.asInstanceOf[HttpRequest[ReceivedMessage]])
+    else "" //todo: should perhaps throw an exception here.
+  }
+
+  def unapply[T](req: HttpRequest[T]) (implicit m: Manifest[T]) : Option[(URL, Representation)] =  {
+    val uri = reqURL(m, req)
     val suffixOpt = uri match {
       case r(_, suffix) => Some(suffix)
       case _ if uri endsWith "/" => Some("/")
@@ -17,4 +52,30 @@
     Some((new URL(uri), Representation(suffixOpt, Accept(req))))
   }
 
+
+  private def reqUrlServlet[T <: HttpServletRequest](req: HttpRequest[T]): String = {
+    req.underlying.getRequestURL.toString
+  }
+
+  private def reqUrlNetty[T <: ReceivedMessage](req: HttpRequest[T]): String = {
+      try {
+        val u = new URL(req.uri)
+        new URL(u.getProtocol,u.getHost,u.getPort,u.getPath).toExternalForm
+      } catch {
+        case e:  MalformedURLException => {
+          val url: StringBuffer = new StringBuffer (48)
+          val scheme = if (req.isSecure) "https" else "http"
+          val hostport = {//we assume there was some checking done earlier, and that we can rely on this
+          val host = req.headers ("Host")
+          if (host.hasNext) host.next () else "localhost"
+          }
+          url.append (scheme)
+          url.append ("://")
+          url.append (hostport)
+          url.append(req.uri)
+          url.toString
+        }
+      }
+  }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/EchoPlan.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import unfiltered.request.Path
+import unfiltered.response.{ResponseString, PlainTextContent, ContentType, Ok}
+import io.BufferedSource
+
+
+/**
+ * @author hjs
+ * @created: 19/10/2011
+ */
+
+class EchoPlan {
+  import collection.JavaConversions._
+
+  lazy val plan = unfiltered.filter.Planify({
+    case req@Path(path) if path startsWith "/test/http/echo" => {
+      Ok ~> PlainTextContent ~> {
+        val headers = req.underlying.getHeaderNames()
+        val result = for (name <- headers ;
+             val nameStr = name.asInstanceOf[String]
+        ) yield {
+          nameStr + ": " + req.underlying.getHeader(nameStr)+"\r\n"
+        }
+        ResponseString(result.mkString+ "\r\n" + new BufferedSource(req.inputStream).mkString)
+      }
+    }
+
+  })
+}
\ No newline at end of file
--- a/src/main/scala/Filesystem.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/Filesystem.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -5,10 +5,10 @@
 import java.io._
 import java.net.URL
 import org.slf4j.{Logger, LoggerFactory}
-import com.hp.hpl.jena.rdf.model._
+import com.hp.hpl.jena.rdf.model.{Resource=>JResource,_}
 import com.hp.hpl.jena.shared.JenaException
 
-import scalaz.{sys => _, _}
+import scalaz.{Resource => SzResource, sys => _,  _}
 import Scalaz._
 
 class Filesystem(
@@ -20,7 +20,7 @@
   
   def sanityCheck(): Boolean =
     baseDirectory.exists && baseDirectory.isDirectory
-  
+
   def resource(url: URL): Resource = new Resource {
     val relativePath: String = url.getPath.replaceAll("^"+basePath.toString+"/?", "")
     val fileOnDisk = new File(baseDirectory, relativePath)
@@ -46,10 +46,17 @@
     
     def get(): Validation[Throwable, Model] = {
       val model = ModelFactory.createDefaultModel()
+      val guessLang = fileOnDisk.getName match {
+        case Authoritative.r(_,suffix) => Representation.fromSuffix(suffix) match {
+          case RDFRepr(rdfLang) => rdfLang
+          case _ => lang
+        }
+        case _ => lang
+      }
       if (fileOnDisk.exists()) {
         val fis = new FileInputStream(fileOnDisk)
         try {
-          val reader = model.getReader(lang.jenaLang)
+          val reader = model.getReader(guessLang.jenaLang)
           reader.read(model, fis, url.toString)
         } catch {
           case je: JenaException => throw je
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/HttpsTrustAll.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,90 @@
+/*
+ * 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
+
+import java.io.File
+import javax.net.ssl.X509TrustManager
+import org.jsslutils.keystores.KeyStoreLoader
+import org.jsslutils.sslcontext.trustmanagers.TrustAllClientsWrappingTrustManager
+import org.jsslutils.sslcontext.{X509TrustManagerWrapper, X509SSLContextFactory}
+import sys.SystemProperties
+import unfiltered.jetty.{Ssl, Https}
+
+
+/**
+ * @author Henry Story
+ * @created: 12/10/2011
+ */
+
+case class HttpsTrustAll(override val port: Int, override val host: String) extends Https(port, host) with TrustAll
+
+
+/**
+ * Trust all ssl connections. Authentication will be done at a different layer
+ * This code is very much tied to jetty
+ * It requires the following System properties to be set
+ *
+ *  - jetty.ssl.keyStoreType
+ *  - jetty.ssl.keyStore
+ *  - jetty.ssl.keyStorePassword
+ *
+ *  Client Auth is set to Want.
+ *
+ *  Authentication could be done here, allowing the code to reject broken certificates, but then
+ *  the user experience would be very bad, since TLS does not give many options for explaining what the problem
+ *  is.
+ */
+trait TrustAll { self: Ssl =>
+   import scala.sys.SystemProperties._
+
+   lazy val sslContextFactory = new X509SSLContextFactory(
+               serverCertKeyStore,
+               tryProperty("jetty.ssl.keyStorePassword"),
+               serverCertKeyStore); //this one is not needed since our wrapper ignores all trust managers
+
+   lazy val trustWrapper = new X509TrustManagerWrapper {
+     def wrapTrustManager(trustManager: X509TrustManager) = new TrustAllClientsWrappingTrustManager(trustManager)
+   }
+
+   lazy val serverCertKeyStore = {
+      val keyStoreLoader = new KeyStoreLoader
+   		keyStoreLoader.setKeyStoreType(System.getProperty("jetty.ssl.keyStoreType","JKS"))
+   		keyStoreLoader.setKeyStorePath(trustStorePath)
+   		keyStoreLoader.setKeyStorePassword(System.getProperty("jetty.ssl.keyStorePassword","password"))
+      keyStoreLoader.loadKeyStore();
+   }
+
+   sslContextFactory.setTrustManagerWrapper(trustWrapper);
+
+
+ 	 lazy val trustStorePath =  new SystemProperties().get("jetty.ssl.keyStore") match {
+       case Some(path) => path
+       case None => new File(new File(tryProperty("user.home")), ".keystore").getAbsolutePath
+   }
+
+   sslConn.setSslContext(sslContextFactory.buildSSLContext())
+   sslConn.setWantClientAuth(true)
+
+}
+
--- a/src/main/scala/Lang.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/Lang.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,3 +1,26 @@
+/*
+ * 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
 
 import unfiltered.request._
@@ -15,7 +38,7 @@
     case TURTLE => "TURTLE"
     case N3 => "N3"
   }
-  
+
 }
 
 object Lang {
@@ -32,7 +55,7 @@
       case "text/turtle" => Some(TURTLE)
       case "application/rdf+xml" => Some(RDFXML)
       case _ => None
-  }
+  }    
   
   def apply(cts: Iterable[String]): Option[Lang] =
     cts map Lang.apply collectFirst { case Some(lang) => lang }
--- a/src/main/scala/ReadWriteWebMain.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/ReadWriteWebMain.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,9 +1,8 @@
 package org.w3.readwriteweb
 
+import auth.{RDFAuthZ, X509view}
 import org.w3.readwriteweb.util._
 
-import javax.servlet._
-import javax.servlet.http._
 import unfiltered.jetty._
 import java.io.File
 import Console.err
@@ -11,14 +10,28 @@
 
 import org.clapper.argot._
 import ArgotConverters._
+import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
 
-object ReadWriteWebMain {
-
+trait ReadWriteWebArgs {
   val logger: Logger = LoggerFactory.getLogger(this.getClass)
 
-  val parser = new ArgotParser("read-write-web")
+  val postUsageMsg= Some("""
+  |PROPERTIES
+  |
+  | * Keystore properties that need to be set if https is started
+  |  -Djetty.ssl.keyStoreType=type : the type of the keystore, JKS by default usually
+  |  -Djetty.ssl.keyStore=path : specify path to key store (for https server certificate)
+  |  -Djetty.ssl.keyStorePassword=password : specify password for keystore store (optional)
+  |
+  |NOTES
+  |
+  |  - Trust stores are not needed because we use the WebID protocol, and client certs are nearly never signed by CAs
+  |  - one of --http or --https must be selected
+     """.stripMargin);
 
-  val mode = parser.option[RWWMode](List("mode"), "m", "wiki mode") {
+  val parser = new ArgotParser("read-write-web",postUsage=postUsageMsg)
+
+  val mode = parser.option[RWWMode](List("mode"), "m", "wiki mode: wiki or strict") {
     (sValue, opt) =>
       sValue match {
         case "wiki" => AllResourcesAlreadyExist
@@ -37,7 +50,8 @@
       }
   }
 
-  val port = parser.parameter[Int]("port", "Port to use", false)
+    val httpPort = parser.option[Int]("http", "Port","start the http server on port")
+    val httpsPort = parser.option[Int]("https","port","start the https server on port")
 
   val rootDirectory = parser.parameter[File]("rootDirectory", "root directory", false) {
     (sValue, opt) => {
@@ -49,8 +63,17 @@
     }
   }
 
+  val webCache = new WebCache()
+
   val baseURL = parser.parameter[String]("baseURL", "base URL", false)
 
+}
+
+
+object ReadWriteWebMain extends ReadWriteWebArgs {
+
+   import unfiltered.filter.Planify
+
   // regular Java main
   def main(args: Array[String]) {
 
@@ -66,17 +89,36 @@
         baseURL.value.get,
         lang=rdfLanguage.value getOrElse RDFXML)(mode.value getOrElse ResourcesDontExistByDefault)
     
-    val app = new ReadWriteWeb(filesystem)
+    val rww = new ReadWriteWeb[HttpServletRequest,HttpServletResponse] {
+      val rm = filesystem
+      def manif = manifest[HttpServletRequest]
+      override implicit val authz = new RDFAuthZ[HttpServletRequest,HttpServletResponse](webCache,filesystem)
+    }
+
+    //this is incomplete: we should be able to start both ports.... not sure how to do this yet.
+    val service = httpsPort.value match {
+      case Some(port) => HttpsTrustAll(port,"0.0.0.0")
+      case None => Http(httpPort.value.get)
+    }
 
     // configures and launches a Jetty server
-    unfiltered.jetty.Http(port.value.get)
-    .filter(new FilterLogger(logger))
-    .context("/public") {
-      ctx: ContextBuilder =>
+    service.filter(new FilterLogger(logger)).
+      context("/public"){ ctx:ContextBuilder =>
         ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
-    }.filter(app.plan).run()
+    }.
+      filter(Planify(rww.intent)).
+      filter(Planify(x509v.intent)).
+      filter(new EchoPlan().plan).run()
     
   }
 
+
+
+  object x509v extends X509view[HttpServletRequest,HttpServletResponse] {
+    def wc = webCache
+    def manif = manifest[HttpServletRequest]
+  }
+
 }
 
+
--- a/src/main/scala/Representation.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/Representation.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,3 +1,4 @@
+
 package org.w3.readwriteweb
 
 import unfiltered.request._
@@ -18,6 +19,7 @@
     }
   }
   
+
   val htmlContentTypes = Set("text/html", "application/xhtml+xml")
   
   def acceptsHTML(ct: Iterable[String]) =
--- a/src/main/scala/RequestLang.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/RequestLang.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,3 +1,26 @@
+/*
+ * 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
 
 import unfiltered.request._
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/WebCache.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,82 @@
+ /*
+ * 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
+
+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._
+
+/**
+ * @author Henry Story
+ * @created: 12/10/2011
+ *
+ * The WebCache currently does not cache
+ */
+class WebCache extends ResourceManager {
+  import dispatch._
+
+  val http = new Http
+  
+  def basePath = null //should be cache dir?
+
+  def sanityCheck() = true  //cache dire exists? But is this needed for functioning?
+
+  def resource(u : URL) = new org.w3.readwriteweb.Resource {
+    def get() = {
+      // note we prefer rdf/xml and turtle over html, as html does not always contain rdfa, and we prefer those over n3,
+      // as we don't have a full n3 parser. Better would be to have a list of available parsers for whatever rdf framework is
+      // installed (some claim to do n3 when they only really do turtle)
+      // we can't currently accept */* as we don't have GRDDL implemented
+      val request = url(u.toString) <:< Map("Accept"->
+        "application/rdf+xml,text/turtle,application/xhtml+xml;q=0.8,text/html;q=0.7,text/n3;q=0.6")
+
+      //we need to tell the model about the content type
+      val handler: Handler[Validation[Throwable, Model]] = request.>+>[Validation[Throwable, Model]](res =>  {
+        res >:> { headers =>
+          val encoding = headers("Content-Type").headOption match {
+            case Some(mime) => Lang(mime) getOrElse Lang.default
+            case None => RDFXML  // it would be better to try to do a bit of guessing in this case by looking at content
+          }
+          val loc = headers("Content-Location").headOption match {
+            case Some(loc) =>  new URL(u,loc)
+            case None => new URL(u.getProtocol,u.getAuthority,u.getPort,u.getPath)
+          }
+          res>>{ in=> modelFromInputStream(in,loc,encoding) }
+
+        }
+      })
+      http(handler)
+
+    }
+
+    // when fetching information from the web creating directories does not make sense
+    //perhaps the resource manager should be split into read/write sections?
+    def save(model: Model) =  throw new MethodNotSupportedException("not implemented")
+
+    def createDirectory(model: Model) =  throw new MethodNotSupportedException("not implemented")
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/AuthenticationFilter.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,84 @@
+/*
+ * 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() {}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/Authz.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,207 @@
+/*
+ * 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 unfiltered.filter.Plan
+import unfiltered.request._
+import collection.JavaConversions._
+import javax.security.auth.Subject
+import java.net.URL
+import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
+import sun.management.resources.agent
+import unfiltered.response.{ResponseFunction, Unauthorized}
+import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+import com.hp.hpl.jena.rdf.model.{RDFNode, ResourceFactory}
+import org.w3.readwriteweb.{Authoritative, Resource, ResourceManager, WebCache}
+
+/**
+ * @author hjs
+ * @created: 14/10/2011
+ */
+
+object HttpMethod {
+  def unapply(req: HttpRequest[_]): Option[Method] =
+    Some(
+      req.method match {
+        case "GET" => GET
+        case "PUT" => PUT
+        case "HEAD" => HEAD
+        case "POST" => POST
+        case "CONNECT" => CONNECT
+        case "OPTIONS" => OPTIONS
+        case "TRACE" => TRACE
+        case m => new Method(m)
+      })
+
+
+}
+
+object AuthZ {
+
+
+  implicit def x509toSubject(x509c: X509Claim)(implicit cache: WebCache): Subject = {
+    val subject = new Subject()
+    subject.getPublicCredentials.add(x509c)
+    val verified = for (
+      claim <- x509c.webidclaims;
+      if (claim.verified)
+    ) yield claim.principal
+    subject.getPrincipals.addAll(verified)
+    subject
+  }
+}
+
+class NullAuthZ[Request,Response] extends AuthZ[Request,Response] {
+  override def subject(req: Req): Option[Subject] = None
+
+  override def guard(m: Method, path: URL): Guard = null
+
+  override def protect(in: Req=>Res)(implicit  m: Manifest[Request]) = in
+}
+
+
+abstract class AuthZ[Request,Response] {
+  type Req = HttpRequest[Request]
+  type Res = ResponseFunction[Response]
+
+
+  def protect(in: Req=>Res)(implicit  m: Manifest[Request]): Req=>Res =  {
+      case req @ HttpMethod(method) & Authoritative(url,_) if guard(method, url).allow(() => subject(req)) => in(req)
+      case _ => Unauthorized
+    }
+  
+
+  protected def subject(req: Req): Option[Subject]
+
+  /** create the guard defined in subclass */
+  protected def guard(m: Method, path: URL): Guard
+
+  abstract class Guard(m: Method, url: URL) {
+
+    /**
+     * verify if the given request is authorized
+     * @param subj function returning the subject to be authorized if the resource needs authorization
+     */
+    def allow(subj: () => Option[Subject]): Boolean
+  }
+
+}
+
+
+class RDFAuthZ[Request,Response](val webCache: WebCache, rm: ResourceManager)
+  (implicit val m: Manifest[Request]) extends AuthZ[Request,Response] {
+  import AuthZ.x509toSubject
+  implicit val cache : WebCache = webCache
+
+  def subject(req: Req) = req match {
+    case X509Claim(claim) => Option(claim)
+    case _ => None
+  }
+
+  object RDFGuard {
+    val acl = "http://www.w3.org/ns/auth/acl#"
+    val foafAgent = ResourceFactory.createResource("http://xmlns.com/foaf/0.1/Agent")
+    val Read = acl+"Read"
+    val Write = acl+"Write"
+    val Control = acl+"Control"
+
+    val selectQuery = QueryFactory.create("""
+    		  PREFIX acl: <http://www.w3.org/ns/auth/acl#>
+    		  SELECT ?mode ?group ?agent
+    		  WHERE {
+              ?auth acl:accessTo ?res ;
+                    acl:mode ?mode .
+          OPTIONAL { ?auth acl:agentClass ?group . }
+	        OPTIONAL { ?auth acl:agent ?agent . }
+    		  }""")
+  }
+
+  def guard(method: Method, url: URL) = new Guard(method, url) {
+    import RDFGuard._
+    import org.w3.readwriteweb.util.wrapValidation
+    import org.w3.readwriteweb.util.ValidationW
+
+
+
+    def allow(subj: () => Option[Subject]) = {
+      val r: Resource = rm.resource(new URL(url,".meta.n3"))
+      val res: ValidationW[Boolean,Boolean] = for {
+        model <- r.get() failMap { x => true }
+      } yield {
+        val initialBinding = new QuerySolutionMap();
+        initialBinding.add("res", model.createResource(url.toString))  //todo: this will work only if the files are described with relative URLs
+        val qe = QueryExecutionFactory.create(selectQuery, model, initialBinding)
+        val agentsAllowed = try {
+          val exec = qe.execSelect()
+          val res = for (qs <- exec) yield {
+            val methods = qs.get("mode").toString match {
+              case Read => List(GET)
+              case Write => List(PUT, POST)
+              case Control => List(POST)
+              case _ => List(GET, PUT, POST, DELETE) //nothing everything is allowed
+            }
+            if (methods.contains(method)) Some((qs.get("agent"), qs.get("group")))
+            else None
+          }
+          res.flatten.toList
+        } finally {
+          qe.close()
+        }
+        if (agentsAllowed.size > 0) {
+          if (agentsAllowed.exists( pair =>  pair._2 == foafAgent )) true
+          else subj() match {
+            case Some(s) => {
+              agentsAllowed.exists{
+                p =>  s.getPrincipals(classOf[WebIdPrincipal]).
+                  exists(id=> {
+                  val ps = if (p._1 != null) p._1.toString else null;
+                  ps == id.webid
+                })
+              }
+            }
+            case None => false
+          }
+          //currently we just check for agent match. Group match would require us to have a store
+          //of trusted information of which groups someone was member of -- one would probably need a reasoning engine there.
+        } else false //
+      }
+      res.validation.fold()
+    } // end allow()
+
+
+  }
+
+}
+
+
+class ResourceGuard(path: String, reqMethod: Method) {
+
+
+  def allow(subjFunc: () => Option[Subject]) = {
+    subjFunc().isEmpty
+  }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/Principals.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,53 @@
+/*
+ * 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.Principal
+
+/**
+ * @author hjs
+ * @created: 13/10/2011
+ */
+
+/**
+ * @author Henry Story from http://bblfish.net/
+ * @created: 09/10/2011
+ */
+
+case class WebIdPrincipal(webid: String) extends Principal {
+  def getName = webid
+  override def equals(that: Any) = that match {
+    case other: WebIdPrincipal => other.webid == webid
+    case _ => false
+  }
+}
+
+case class Anonymous() extends Principal {
+  def getName = "anonymous"
+  override def equals(that: Any) =  that match {
+      case other: WebIdPrincipal => 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.
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/WebIdClaim.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,253 @@
+/*
+ * 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 com.hp.hpl.jena.rdf.model.RDFNode
+import java.math.BigInteger
+import java.security.PublicKey
+import org.w3.readwriteweb.WebCache
+import java.util.LinkedList
+import java.security.interfaces.RSAPublicKey
+import java.net.URL
+import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
+
+/**
+ * @author hjs
+ * @created: 13/10/2011
+ */
+
+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
+
+
+    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)
+    }
+
+
+   /**
+    * 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
+     }
+
+
+}
+
+/**
+ * An X509 Claim maintains information about the proofs associated with claims
+ * found in an X509 Certificate. It is the type of object that can be passed
+ * into the public credentials part of a Subject node
+ *
+ * todo: think of what this would look like for a chain of certificates
+ *
+ * @author bblfish
+ * @created 30/03/2011
+ */
+class WebIDClaim(val webId: String, val key: PublicKey) {
+
+	var errors = new LinkedList[java.lang.Throwable]()
+
+	lazy val principal = new WebIdPrincipal(webId)
+
+	var tests: List[Verification] = List()
+
+  private var valid = false
+
+  def verified(implicit cache: WebCache): Boolean = {
+    if (!valid) tests = verify(cache)
+    tests.exists(v => v.isInstanceOf[Verified])
+  }
+  
+  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:")) {
+        //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 initialBinding = new QuerySolutionMap();
+          initialBinding.add("webid",model.createResource(webId))
+          val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.selectQuery, 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
+          } finally {
+            qe.close()
+          }
+        }
+        res.either match {
+          case Right(tests) => tests
+          case Left(profileErr) => profileErr::Nil
+        }
+      }
+    } 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 Verification(msg: String)
+class Verified(msg: String) extends Verification(msg)
+class Unverified(msg: String) extends Verification(msg)
+
+class TestFailure(msg: String) extends Verification(msg)
+class ProfileError(msg: String,  t: Throwable ) extends TestFailure(msg)
+class KeyProblem(msg: String) extends TestFailure(msg)
+
+object unsupportedProtocol extends TestFailure("WebID protocol not supported")
+object noMatchingKey extends TestFailure("No keys in profile matches key in cert")
+object keyDoesNotMatch extends TestFailure("Key does not match")
+
+object verifiedWebID extends Verified("WebId verified")
+object notstarted extends Unverified("No verification attempt started")
+object failed extends Unverified("Tests failed")
+object certificateKeyTypeNotSupported extends TestFailure("The certificate key type is not supported. We only support RSA")
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/X509Cert.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,219 @@
+/*
+ * 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 javax.servlet.http.HttpServletRequest
+import unfiltered.netty.ReceivedMessage
+import java.util.Date
+import java.math.BigInteger
+import java.net.URL
+import unfiltered.request.{UserAgent, HttpRequest}
+import java.security.cert.{X509Certificate, Certificate}
+import java.security._
+import interfaces.RSAPublicKey
+import unfiltered.util.IO
+import sun.security.x509._
+
+
+object X509CertSigner {
+
+  def apply(keyStoreLoc: URL, keyStoreType: String, password: String, alias: String) = {
+    val keystore =  KeyStore.getInstance(keyStoreType)
+
+    IO.use(keyStoreLoc.openStream()) { in =>
+      keystore.load(in, password.toCharArray)
+    }
+    val privateKey = keystore.getKey(alias, password.toCharArray).asInstanceOf[PrivateKey]
+    val certificate = keystore.getCertificate(alias).asInstanceOf[X509Certificate]
+    //one could verify that indeed this is the private key corresponding to the public key in the cert.
+
+    new X509CertSigner(certificate,privateKey)
+  }
+}
+
+class X509CertSigner(signingCert: X509Certificate, signingKey: PrivateKey ) {
+  val WebID_DN="""O=FOAF+SSL, OU=The Community of Self Signers, CN=Not a Certification Authority"""
+
+  /**
+   * Adapted from http://bfo.com/blog/2011/03/08/odds_and_ends_creating_a_new_x_509_certificate.html
+   * The libraries used here are sun gpled code. This is much lighter to use than bouncycastle. All VMs that already
+   * have these classes don't need to download the code. It should be easy in scala to create a build that can decide
+   * if these need to be added to the classpath. I think the code just looks better than bouncycastle too.
+   *
+   * WARNING THIS IS   in construction
+   *
+   * Create a self-signed X.509 Certificate
+   * @param subjectDN the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
+   * @param pair the KeyPair
+   * @param days how many days from now the Certificate is valid for
+   * @param algorithm the signing algorithm, eg "SHA1withRSA"
+   */
+    def generate(subjectDN: String,
+                 subjectKey: RSAPublicKey,
+                 days: Int,
+                 webId: URL): X509Certificate = {   //todo: the algorithm should be deduced from private key in part
+
+
+      var info = new X509CertInfo
+      val from = new Date(System.currentTimeMillis()-10*1000*60) //start 10 minutes ago, to avoid network trouble
+      val to = new Date(from.getTime + days*24*60*60*1000) 
+      val interval = new CertificateValidity(from, to)
+      val serialNumber = new BigInteger(64, new SecureRandom)
+      val subjectXN = new X500Name(subjectDN)
+      val issuerXN = new X500Name(signingCert.getSubjectDN.toString)
+
+      info.set(X509CertInfo.VALIDITY, interval)
+      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNumber))
+      info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(subjectXN))
+      info.set(X509CertInfo.ISSUER, new CertificateIssuerName(issuerXN))
+      info.set(X509CertInfo.KEY, new CertificateX509Key(subjectKey))
+      info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3))
+
+      //
+      //extensions
+      //
+      val extensions = new CertificateExtensions();
+
+      val san = new SubjectAlternativeNameExtension(true, new GeneralNames().add(new GeneralName(new URIName(webId.toExternalForm))))
+      extensions.set(san.getName,san)
+
+      val basicCstrExt = new BasicConstraintsExtension(false,1)
+      extensions.set(basicCstrExt.getName,basicCstrExt)
+
+      { import KeyUsageExtension._
+        val keyUsage = new KeyUsageExtension()
+        List(DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, KEY_AGREEMENT, KEY_CERTSIGN).foreach {
+           usage => keyUsage.set(usage,true)
+        }
+        extensions.set(keyUsage.getName,keyUsage)
+      }
+
+      { import NetscapeCertTypeExtension._
+      val netscapeExt = new NetscapeCertTypeExtension()
+       List(SSL_CLIENT, S_MIME) foreach { ext => netscapeExt.set(ext,true) }
+        extensions.set(netscapeExt.getName, new NetscapeCertTypeExtension(false,netscapeExt.getValue))
+      }
+      
+      val subjectKeyExt = new SubjectKeyIdentifierExtension(new KeyIdentifier(subjectKey).getIdentifier)
+      extensions.set(subjectKeyExt.getName,subjectKeyExt)
+      
+      info.set(X509CertInfo.EXTENSIONS,extensions)
+
+      val algo = signingCert.getPublicKey.getAlgorithm match {
+        case "DSA" =>  new AlgorithmId(AlgorithmId.sha1WithDSA_oid )
+        case "RSA" =>  new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid)
+        case _ => throw new RuntimeException("Don't know how to sign with this type of key")  
+      }
+
+      info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo))
+
+      // Sign the cert to identify the algorithm that's used.
+      val tmpCert = new X509CertImpl(info)
+      tmpCert.sign(signingKey,algo.getName)
+
+      //update the algorithm and re-sign
+      val sigAlgo = tmpCert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId]
+      info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgo)
+      val cert = new X509CertImpl(info)
+      cert.sign(signingKey,algo.getName)
+      
+      cert.verify(signingCert.getPublicKey)
+      return cert
+    }
+
+
+}
+
+
+object Certs {
+
+
+  def unapplySeq[T](r: HttpRequest[T])(implicit m: Manifest[T]): Option[IndexedSeq[Certificate]] = {
+    if (m <:< manifest[HttpServletRequest]) unapplyServletRequest(r.asInstanceOf[HttpRequest[HttpServletRequest]])
+    else if (m <:< manifest[ReceivedMessage]) unapplyReceivedMessage(r.asInstanceOf[HttpRequest[ReceivedMessage]])
+    else None //todo: should  throw an exception here?
+  }
+
+
+  //todo: should perhaps pass back error messages, which they could in the case of netty
+
+  private def unapplyServletRequest[T <: HttpServletRequest](r: HttpRequest[T]):
+  Option[IndexedSeq[Certificate]] = {
+    r.underlying.getAttribute("javax.servlet.request.X509Certificate") match {
+      case certs: Array[Certificate] => Some(certs)
+      case _ => None
+    }
+  }
+
+  private def unapplyReceivedMessage[T <: ReceivedMessage](r: HttpRequest[T]):
+  Option[IndexedSeq[Certificate]] = {
+
+    import org.jboss.netty.handler.ssl.SslHandler
+    r.underlying.context.getPipeline.get(classOf[SslHandler]) match {
+      case sslh: SslHandler => try {
+        //return the client certificate in the existing session if one exists
+        Some(sslh.getEngine.getSession.getPeerCertificates)
+      } catch {
+        case e => {
+          // request a certificate from the user
+          sslh.setEnableRenegotiation(true)
+          r match {
+            case UserAgent(agent) if needAuth(agent) => sslh.getEngine.setNeedClientAuth(true)
+            case _ => sslh.getEngine.setWantClientAuth(true)  
+          }
+          
+          val future = sslh.handshake()
+          future.await(30000) //that's certainly way too long.
+          if (future.isDone) {
+            if (future.isSuccess) try {
+              Some(sslh.getEngine.getSession.getPeerCertificates)
+            } catch {
+              case e => None
+            } else {
+              None
+            }
+          } else {
+            None
+          }
+        }
+      }
+      case _ => None
+    }
+
+  }
+
+ /**
+  *  Some agents do not send client certificates unless required. This is a problem for them, as it ends up breaking the
+  *  connection for those agents if the client does not have a certificate...
+  *
+  *  It would be useful if this could be updated by server from time to  time from a file on the internet,
+  *  so that changes to browsers could update server behavior
+  *
+  */
+  def needAuth(agent: String): Boolean = {
+    agent.contains("Java")
+  }
+  
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/X509Claim.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,139 @@
+/*
+ * 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 org.slf4j.LoggerFactory
+import java.security.cert.X509Certificate
+import org.w3.readwriteweb.WebCache
+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
+
+/**
+ * @author hjs
+ * @created: 13/10/2011
+ */
+
+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)
+     })
+
+  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 _ => None
+    }
+
+
+
+  /**
+   * Extracts the URIs in the subject alternative name extension of an X.509
+   * certificate
+   *
+   * @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 {
+      case coll if (coll != null) => {
+        for (sanPair <- coll
+             if (sanPair.get(0) == 6)
+        ) yield sanPair(1).asInstanceOf[String]
+      }.iterator
+      case _ => Iterator.empty
+    }
+
+}
+
+
+/**
+ * An X509 Claim maintains information about the proofs associated with claims
+ * found in an X509 Certificate. It is the type of object that can be passed
+ * into the public credentials part of a Subject node
+ *
+ * todo: think of what this would look like for a chain of certificates
+ *
+ * @author bblfish
+ * @created: 30/03/2011
+ */
+class X509Claim(val cert: X509Certificate) extends Refreshable  {
+
+  import X509Claim._
+  val claimReceivedDate = new Date();
+  lazy val tooLate = claimReceivedDate.after(cert.getNotAfter())
+  lazy val tooEarly = claimReceivedDate.before(cert.getNotBefore())
+
+  /* a list of unverified principals */
+  lazy val webidclaims = getClaimedWebIds(cert).map {
+    webid =>new WebIDClaim(webid, cert.getPublicKey)
+  }.toSet
+
+
+
+  //note could also implement Destroyable
+  //
+  //http://download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html#Credentials
+  //
+  //if updating validity periods can also take into account the WebID reference, then it is possible
+  //that a refresh could have as consequence to do a fetch on the WebID profile
+  //note: one could also take the validity period to be dependent on the validity of the profile representation
+  //in which case updating the validity period would make more sense.
+
+  override
+  def refresh() {
+  }
+
+  /* The certificate is currently within the valid time zone */
+  override
+  def isCurrent(): Boolean = !(tooLate||tooEarly)
+
+  lazy val error = {}
+
+  def canEqual(other: Any) = other.isInstanceOf[X509Claim]
+
+  override
+  def equals(other: Any): Boolean =
+    other match {
+      case that: X509Claim => (that eq this) || (that.canEqual(this) && cert == that.cert)
+      case _ => false
+    }
+
+  override
+  lazy val hashCode: Int = 41 * (41 +
+    (if (cert != null) cert.hashCode else 0))
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/X509view.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,64 @@
+/*
+ * 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 unfiltered.request.Path
+import unfiltered.response.{Html, ContentType, Ok}
+import org.w3.readwriteweb.WebCache
+import unfiltered.Cycle
+
+/**
+ * This plan just described the X509 WebID authentication information.
+ * It works independently of the underlying Cycle.Intent implementations of Request and Response,
+ * so it can work with servlet filters just as well as with netty.
+ *
+ * This is a simple version. A future version will show EARL output, and so be useful for debugging the endpoint.
+ *
+ * @author hjs
+ * @created: 13/10/2011
+ */
+
+trait X509view[Request,Response]  {
+   implicit def wc: WebCache
+   implicit def manif: Manifest[Request]
+
+    def intent: Cycle.Intent[Request, Response] =  {
+      case req @ Path(path) if path startsWith "/test/auth/x509" =>
+        Ok ~> ContentType("text/html") ~> Html(
+          <html><head><title>Authentication Page</title></head>
+        { req match {
+          case X509Claim(xclaim) => <body>
+            <h1>Authentication Info received</h1>
+            <p>You were identified with the following WebIDs</p>
+             <ul>{xclaim.webidclaims.filter(cl=>cl.verified).map(p=> <li>{p.webId}</li>)}</ul>
+            <p>You sent the following certificate</p>
+            <pre>{xclaim.cert.toString}</pre>
+          </body>
+          case _ => <body><p>We received no Authentication information</p></body>
+        }
+          }</html>)
+
+      }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/netty/ReadWriteWebNetty.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,81 @@
+/*
+ * 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.netty
+
+import org.clapper.argot.ArgotUsageException
+import scala.Console._
+import org.w3.readwriteweb.auth.{X509view, RDFAuthZ}
+import org.w3.readwriteweb._
+import org.jboss.netty.handler.codec.http.HttpResponse
+import unfiltered.netty.{ServerErrorResponse, ReceivedMessage, cycle}
+
+/**
+ * ReadWrite Web for Netty server, allowing TLS renegotiation
+ *
+ * @author hjs
+ * @created: 21/10/2011
+ */
+
+object ReadWriteWebNetty extends ReadWriteWebArgs {
+
+  // regular Java main
+   def main(args: Array[String]) {
+
+     try {
+       parser.parse(args)
+     } catch {
+       case e: ArgotUsageException => err.println(e.message); sys.exit(1)
+     }
+ 
+     val filesystem =
+       new Filesystem(
+         rootDirectory.value.get,
+         baseURL.value.get,
+         lang=rdfLanguage.value getOrElse RDFXML)(mode.value getOrElse ResourcesDontExistByDefault)
+     
+     val rww = new cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with ReadWriteWeb[ReceivedMessage,HttpResponse]{
+          val rm = filesystem
+          def manif = manifest[ReceivedMessage]
+          override val authz = new RDFAuthZ[ReceivedMessage,HttpResponse](webCache,filesystem)
+     }
+
+     //this is incomplete: we should be able to start both ports.... not sure how to do this yet.
+     val service = httpsPort.value match {
+       case Some(port) => new KeyAuth_Https(port)
+       case None => new KeyAuth_Https(httpPort.value.get)
+     }
+
+     // configures and launches a Netty server
+     service.plan( x509v ).
+             plan( rww ).run()
+     
+   }
+
+  object x509v extends  cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with X509view[ReceivedMessage,HttpResponse] {
+    def wc = webCache
+    def manif = manifest[ReceivedMessage]
+  }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/netty/SslLoginTest.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,97 @@
+/*
+ * 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.netty
+
+import org.jboss.netty.handler.ssl.SslHandler
+import unfiltered.request.Path
+import unfiltered.response.ResponseString
+
+/**
+ * A very light weight plan to test SSL login using TLS renegotiation in netty.
+ * This shows how easy it is to to this, and can be useful to try out different browsers' implementations
+ * The certificate should only be requested of the client on going to /test/login .
+ *
+ * Note: to get this to work on all browsers, and if security is just less of a concern for you, you should
+ * set the sun.security.ssl.allowUnsafeRenegotiation=true and sun.security.ssl.allowLegacyHelloMessages=true
+ * see:
+ *
+ * http://download.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#workarounds
+ *
+ *
+ * @author hjs
+ * @created: 21/10/2011
+ */
+object SslLoginTest extends  NormalPlan {
+
+  def certAvailable(sslh: SslHandler): String =  try {
+       sslh.getEngine.getSession.getPeerCertificateChain.head.toString
+     } catch {
+       case e => e.getMessage
+     }
+
+
+  def intent = {
+
+    case req @ Path("/test/login") => {
+
+       req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler])  match {
+          case sslh: SslHandler => {
+            sslh.setEnableRenegotiation(true)
+            sslh.getEngine.setNeedClientAuth(true)
+//            sslh.getEngine.setWantClientAuth(true)
+            val future = sslh.handshake()
+            future.await(5000)
+            val res = if (future.isDone) {
+              var r ="We are in login & we have an https handler! "
+              if (future.isSuccess)
+                r +=  "\r\n"+"SSL handchake Successful. Did we get the certificate? \r\n\r\n"+certAvailable(sslh)
+              else {
+                r += "\r\n handshake failed. Cause \r\n" +future.getCause
+              }
+              r
+            } else {
+              "Still waiting for requested certificate"
+            }
+            ResponseString(res)
+           }
+          case _ =>ResponseString("We are in login but no https handler!")
+       }
+
+    }
+    case req => {
+      req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler]) match {
+        case sslh: SslHandler =>  {
+          ResponseString(certAvailable(sslh))
+        }
+        case null => ResponseString("Just a normal hello world")
+
+      }
+    }
+  }
+
+
+  def main(args: Array[String]) {
+    new KeyAuth_Https(8443).plan(SslLoginTest).run()
+  }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/netty/server.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,111 @@
+/*
+ * 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.netty
+
+
+import unfiltered.netty._
+import java.lang.String
+import org.jboss.netty.channel.{ChannelPipelineFactory, ChannelHandler}
+import java.security.cert.X509Certificate
+import javax.net.ssl.{SSLEngine, X509ExtendedTrustManager}
+import java.net.Socket
+
+trait NormalPlan extends cycle.Plan with cycle.ThreadPool with ServerErrorResponse
+
+
+class KeyAuth_Https(override val port: Int) extends Https(port)  with KeyAuth_Ssl
+
+
+/**
+ * a class that trusts all ssl certificates - as long as the tls handshake crypto works of course.
+ * Ie: we don't care about who signed the certificate. All we know when the certificate is received
+ * is that the client knew the private key of the given public key. It is the job of other layers,
+ * to follow through on claims made in the certificate.
+ */
+trait KeyAuth_Ssl extends Ssl {
+  
+  import java.security.SecureRandom
+  import javax.net.ssl.{SSLContext, TrustManager}
+
+  val nullArray = Array[X509Certificate]()
+
+  val trustManagers = Array[TrustManager](new X509ExtendedTrustManager {
+
+    def checkClientTrusted(chain: Array[X509Certificate], authType: String, socket: Socket) {}
+
+    def checkClientTrusted(chain: Array[X509Certificate], authType: String, engine: SSLEngine) {}
+
+    def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String) {}
+
+    def checkServerTrusted(chain: Array[X509Certificate], authType: String, socket: Socket) {}
+
+    def checkServerTrusted(chain: Array[X509Certificate], authType: String, engine: SSLEngine) {}
+
+    def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String) {}
+
+    def getAcceptedIssuers() = nullArray
+  })
+
+
+  override def initSslContext(ctx: SSLContext) = ctx.init(keyManagers, trustManagers, new SecureRandom)
+
+
+}
+
+//
+// Below is code mostly taken from unfiltered.netty, and so available under their licence. I am waiting
+// for them to make it easier to extend the netty.Https classes so that this code would not longer be
+// needed
+//
+
+object Https {
+
+   /** bind to a the loopback interface only */
+  def local(port: Int): Https =
+    new Https(port, "127.0.0.1")
+
+  /** bind to any available port on the loopback interface */
+  def anylocal = local(unfiltered.util.Port.any)
+}
+
+
+/** Http + Ssl implementation of the Server trait. */
+class Https(val port: Int,
+            val host: String,
+            val handlers: List[() => ChannelHandler],
+            val beforeStopBlock: () => Unit) extends HttpServer with KeyAuth_Ssl { self =>
+
+  def this(port: Int, host: String) = this(port, host, Nil, () => ())
+
+  def this(port: Int) = this(port, "0.0.0.0")
+
+  def pipelineFactory: ChannelPipelineFactory =
+    new SecureServerPipelineFactory(channels, handlers, this)
+
+  type ServerBuilder = Https
+  def handler(h: => ChannelHandler) = new Https(port, host, { () => h } :: handlers, beforeStopBlock)
+  def plan(plan: => ChannelHandler) = handler(plan)
+  def beforeStop(block: => Unit) = new Https(port, host, handlers, { () => beforeStopBlock(); block })
+
+}
--- a/src/main/scala/plan.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/plan.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,10 +1,8 @@
 package org.w3.readwriteweb
 
+import auth.{AuthZ, NullAuthZ}
 import org.w3.readwriteweb.util._
 
-import unfiltered.request._
-import unfiltered.response._
-
 import scala.io.Source
 import java.net.URL
 
@@ -18,29 +16,49 @@
               QueryTypeConstruct => CONSTRUCT,
               QueryTypeDescribe => DESCRIBE}
 
-import scalaz._
-import Scalaz._
+import scalaz.{Resource=>SzResource}
+import unfiltered.request._
+import unfiltered.Cycle
+import unfiltered.response._
 
 //object ReadWriteWeb {
-//  
+//
 //  val defaultHandler: PartialFunction[Throwable, HttpResponse[_]] = {
 //    case t => InternalServerError ~> ResponseString(t.getStackTraceString)
 //  }
-//  
+//
 //}
 
-class ReadWriteWeb(rm: ResourceManager) {
-  
+/**
+ * The ReadWriteWeb intent.
+ * It is independent of jetty or netty
+ */
+trait ReadWriteWeb[Req,Res] {
+  val rm: ResourceManager
+  implicit def manif: Manifest[Req]
+  implicit val authz: AuthZ[Req,Res] = new NullAuthZ[Req,Res]
+  // a few type short cuts to make it easier to reason with the code here
+  // one may want to generalise this code so that it does not depend so strongly on servlets.
+//  type Request = HttpRequest[Req]
+//  type Response = ResponseFunction[Res]
+
   val logger: Logger = LoggerFactory.getLogger(this.getClass)
 
-  /** I believe some documentation is needed here, as many different tricks
-   *  are used to make this code easy to read and still type-safe
-   *  
-   *  Planify.apply takes an Intent, which is defined in Cycle by
-   *  type Intent [-A, -B] = PartialFunction[HttpRequest[A], ResponseFunction[B]]
-   *  the corresponding syntax is: case ... => ...
-   *  
-   *  this code makes use of ScalaZ Validation. For example of how to use it, see
+  /**
+   * The partial function that if triggered sends to the readwrite web code.
+   * It wraps the ReadWriteWeb function with the AuthZ passed in the argument
+   * ( Note that we don't want to protect this intent, since that would be to apply the security to all other applications,
+   * many of which may want different authorization implementations )
+   */
+  def intent : Cycle.Intent[Req,Res] = {
+      case req @ Path(path) if path startsWith rm.basePath => authz.protect(rwwIntent)(manif)(req)
+  }
+
+  /**
+   * The core ReadWrite web function
+   * ( This is not a partial function and so is not a Plan.Intent )
+   *
+   *  This code makes use of ScalaZ Validation. For example of how to use it, see
    *  http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html
    *  
    *  the Resource abstraction returns Validation[Throwable, ?something]
@@ -53,99 +71,103 @@
    *  in the ScalaZ API so I made my own through an implicit.
    *  
    *  At last, Validation[ResponseFunction, ResponseFuntion] is exposed as a ResponseFunction
-   *  through another implicit conversion. It saves us the call to the Validation.lift() method
+   *  through another implicit conversion. It saves us the call to the Validation.fold() method
    */
-  val plan = unfiltered.filter.Planify {
-    case req @ Path(path) if path startsWith rm.basePath => {
-      val Authoritative(uri, representation) = req
-      val r: Resource = rm.resource(uri)
-      req match {
-        case GET(_) if representation == HTMLRepr => {
-          val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
-          val body = source.getLines.mkString("\n")
-          Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
-        }
-        case GET(_) | HEAD(_) =>
-          for {
-            model <- r.get() failMap { x => NotFound }
-            lang = representation match {
-              case RDFRepr(l) => l
-              case _ => Lang.default
-            }
-          } yield {
-            req match {
-              case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
-              case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
+  def rwwIntent  =  (req: HttpRequest[Req]) => {
+
+          val Authoritative(uri: URL, representation: Representation) = req
+          val r: Resource = rm.resource(uri)
+          val res: ResponseFunction[Res] = req match {
+            case GET(_) if representation == HTMLRepr => {
+              val source = Source.fromFile("src/main/resources/skin.html")("UTF-8")
+              val body = source.getLines.mkString("\n")
+              Ok ~> ViaSPARQL ~> ContentType("text/html") ~> ResponseString(body)
             }
-          }
-        case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
-          for {
-            bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
-            _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
-          } yield Created
-        }
-        case PUT(_) & RequestLang(lang) =>
-          for {
-            bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
-            _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
-          } yield Created
-        case PUT(_) =>
-          BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
-        case POST(_) & RequestContentType(ct) if Post.supportContentTypes contains ct => {
-          Post.parse(Body.stream(req), uri, ct) match {
-            case PostUnknown => {
-              logger.info("Couldn't parse the request")
-              BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
-            }
-            case PostUpdate(update) => {
-              logger.info("SPARQL UPDATE:\n" + update.toString())
+            case GET(_) | HEAD(_) =>
               for {
-                model <- r.get() failMap { t => NotFound }
-                // TODO: we should handle an error here
-                _ = UpdateAction.execute(update, model)
-                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-              } yield Ok
-            }
-            case PostRDF(diffModel) => {
-              logger.info("RDF content:\n" + diffModel.toString())
+                model <- r.get() failMap { x => NotFound }
+                lang = representation match {
+                  case RDFRepr(l) => l
+                  case _ => Lang.default
+                }
+              } yield {
+                val res = req match {
+                  case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                  case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
+                }
+                res ~> ContentLocation( uri.toString ) // without this netty (perhaps jetty too?) sends very weird headers, breaking tests
+              }
+            case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
               for {
-                model <- r.get() failMap { t => NotFound }
-                // TODO: we should handle an error here
-                _ = model.add(diffModel)
-                _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
-              } yield Ok
+                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+                _ <- r.createDirectory(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+              } yield Created
             }
-            case PostQuery(query) => {
-              logger.info("SPARQL Query:\n" + query.toString())
-              lazy val lang = RequestLang(req) getOrElse Lang.default
+            case PUT(_) & RequestLang(lang) =>
               for {
-                model <- r.get() failMap { t => NotFound }
-              } yield {
-                val qe: QueryExecution = QueryExecutionFactory.create(query, model)
-                query.getQueryType match {
-                  case SELECT =>
-                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
-                  case ASK =>
-                    Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
-                  case CONSTRUCT => {
-                    val result: Model = qe.execConstruct()
-                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
-                  }
-                  case DESCRIBE => {
-                    val result: Model = qe.execDescribe()
-                    Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                bodyModel <- modelFromInputStream(Body.stream(req), uri, lang) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
+                _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
+              } yield Created
+            case PUT(_) =>
+              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
+            case POST(_) & RequestContentType(ct) if Post.supportContentTypes contains ct => {
+              Post.parse(Body.stream(req), uri, ct) match {
+                case PostUnknown => {
+                  logger.info("Couldn't parse the request")
+                  BadRequest ~> ResponseString("You MUST provide valid content for given Content-Type: " + ct)
+                }
+                case PostUpdate(update) => {
+                  logger.info("SPARQL UPDATE:\n" + update.toString())
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                    // TODO: we should handle an error here
+                    _ = UpdateAction.execute(update, model)
+                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+                  } yield Ok
+                }
+                case PostRDF(diffModel) => {
+                  logger.info("RDF content:\n" + diffModel.toString())
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                    // TODO: we should handle an error here
+                    _ = model.add(diffModel)
+                    _ <- r.save(model) failMap { t =>  InternalServerError ~> ResponseString(t.getStackTraceString)}
+                  } yield Ok
+                }
+                case PostQuery(query) => {
+                  logger.info("SPARQL Query:\n" + query.toString())
+                  lazy val lang = RequestLang(req) getOrElse Lang.default
+                  for {
+                    model <- r.get() failMap { t => NotFound }
+                  } yield {
+                    val qe: QueryExecution = QueryExecutionFactory.create(query, model)
+                    query.getQueryType match {
+                      case SELECT =>
+                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execSelect())
+                      case ASK =>
+                        Ok ~> ContentType("application/sparql-results+xml") ~> ResponseResultSet(qe.execAsk())
+                      case CONSTRUCT => {
+                        val result: Model = qe.execConstruct()
+                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                      }
+                      case DESCRIBE => {
+                        val result: Model = qe.execDescribe()
+                        Ok ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
+                      }
+                    }
                   }
                 }
               }
             }
+            case POST(_) =>
+              BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
+            case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
           }
+          res
         }
-        case POST(_) =>
-          BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
-        case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
-      }
-    }
-
-  }
+      
+    
+    
+  
 
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/SpyInputStream.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,58 @@
+/*
+ * 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 org.w3.readwriteweb.util
+
+import java.io.{IOException, OutputStream, InputStream}
+
+
+/**
+ * Wrap an inputstream and write everything that comes in here
+ * @author hjs
+ * @created: 30/10/2011
+ */
+
+class SpyInputStream(val in: InputStream, val out: OutputStream) extends InputStream {
+  var stopOut = false
+  
+  def read() ={
+
+    val i = try {
+       in.read()
+   } catch {
+      case ioe: IOException => {
+        out.close()
+        stopOut=true
+        throw ioe;
+      }
+    }
+    if (!stopOut) try {
+      out.write(i)
+    } catch {
+      case ioe: IOException => {
+        stopOut = true
+      }
+    }
+    i
+  }
+}
\ No newline at end of file
--- a/src/main/scala/util/package.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/main/scala/util/package.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -1,14 +1,15 @@
 package org.w3.readwriteweb
 
 import java.io._
-import java.net.URL
 import com.hp.hpl.jena.rdf.model._
-
 import scalaz._
 import Scalaz._
+import java.net.URL
 
 package object util {
   
+
+
   def modelFromInputStream(
       is: InputStream,
       base: URL,
Binary file src/test/resources/KEYSTORE.jks has changed
--- a/src/test/scala/ReadWriteWebSpecs.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/test/scala/ReadWriteWebSpecs.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -13,7 +13,7 @@
       PutRDFXMLSpec, PostRDFSpec,
       PutInvalidRDFXMLSpec, PostOnNonExistingResourceSpec,
       // sparql query
-      PostSelectSpec, PostConstructSpec, PostAskSpec, 
+      PostSelectSpec, PostConstructSpec, PostAskSpec,
       // sparql update
       PostInsertSpec,
       // delete content
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/auth/CreateWebIDSpec.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,283 @@
+/*
+ * 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 org.w3.readwriteweb.auth
+
+import org.w3.readwriteweb.utiltest._
+
+import dispatch._
+import java.security.cert.X509Certificate
+import java.security._
+import interfaces.RSAPublicKey
+import java.net.{Socket, URL}
+import scala.collection.mutable
+import javax.net.ssl._
+import java.io.File
+import org.w3.readwriteweb.{Post, RDFXML, TURTLE}
+
+
+/**
+ * A key manager that can contain multiple keys, but where the client can take one of a number of identities
+ * One at a time - so this is not sychronised. It also assumes that the server will accept all CAs, which in
+ * these test cases it does.
+ */
+class FlexiKeyManager extends X509ExtendedKeyManager {
+  val keys = mutable.Map[String, Pair[Array[X509Certificate],PrivateKey]]()
+  
+  def addClientCert(alias: String,certs: Array[X509Certificate], privateKey: PrivateKey) {
+    keys.put(alias,Pair(certs,privateKey))
+  }
+  
+  var currentId: String = null
+  
+  def setId(alias: String) { currentId = if (keys.contains(alias)) alias else null }
+  
+  def getClientAliases(keyType: String, issuers: Array[Principal]) = if (currentId!=null) Array(currentId) else null
+  
+  def chooseClientAlias(keyType: Array[String], issuers: Array[Principal], socket: Socket) = currentId
+
+  def getServerAliases(keyType: String, issuers: Array[Principal]) = null
+
+  def chooseServerAlias(keyType: String, issuers: Array[Principal], socket: Socket) = ""
+
+  def getCertificateChain(alias: String) = keys.get(alias) match { case Some(certNKey) => certNKey._1; case None => null}
+
+  def getPrivateKey(alias: String) = keys.get(alias).map(ck=>ck._2).getOrElse(null)
+
+  override def chooseEngineClientAlias(keyType: Array[String], issuers: Array[Principal], engine: SSLEngine): String = currentId
+}
+
+/**
+ * @author hjs
+ * @created: 23/10/2011
+ */
+
+object CreateWebIDSpec extends SecureFileSystemBased {
+  lazy val peopleDirUri = host / "wiki/people/"
+  lazy val webidProfileDir = peopleDirUri / "Lambda/"
+  lazy val webidProfile = webidProfileDir / "Joe"
+  lazy val joeProfileOnDisk = new File(root,"people/Lambda/Joe")
+  lazy val lambdaMetaURI = webidProfileDir/".meta.n3"
+
+  lazy val directory = new File(root, "people")
+  lazy val lambdaDir = new File(directory,"Lambda")
+  lazy val lambdaMeta = new File(lambdaDir,".meta.n3")
+
+
+
+  val foaf = """
+       @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+       @prefix : <#> .
+
+       <> a foaf:PersonalProfileDocument;
+          foaf:primaryTopic :me .
+
+       :jL a foaf:Person;
+           foaf:name "Joe Lambda"@en .
+  """
+
+  val webID = new URL(webidProfile.secure.to_uri + "#jL")
+
+
+  val updatePk = """
+       PREFIX cert: <http://www.w3.org/ns/auth/cert#>
+       PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
+       PREFIX : <#>
+       INSERT DATA {
+         :jL cert:key [ rsa:modulus "%s"^^cert:hex;
+                        rsa:public_exponent "%s"^^cert:int ] .
+       }
+  """
+
+  val updateFriend = """
+       PREFIX foaf: <http://xmlns.com/foaf/0.1/>
+       PREFIX : <#>
+       INSERT DATA {
+          :jL foaf:knows <%s> .
+       }
+  """
+
+
+  "PUTing nothing on /people/" should {
+       "return a 201" in {
+         val httpCode = Http(peopleDirUri.secure.put(TURTLE, "") get_statusCode)
+         httpCode must_== 201
+       }
+       "create a directory on disk" in {
+         directory must be directory
+       }
+   }
+  
+
+  "PUTing nothing on /people/Lambda/" should { // but should it really? Should it not create a resource too? Perhaps index.html?
+     "return a 201" in {
+       val httpCode = Http(webidProfileDir.secure.put(TURTLE, "") get_statusCode)
+       httpCode must_== 201
+     }
+     "create a directory on disk" in {
+       lambdaDir must be directory
+     }
+   }
+  
+  
+   "PUTing a WebID Profile on /people/Lambda/" should {
+     "return a 201" in {
+       val httpCode = Http( webidProfile.secure.put(TURTLE, foaf) get_statusCode )
+        httpCode must_== 201
+     }
+     "create a resource on disk" in {
+        joeProfileOnDisk must be file
+     }
+   }
+
+
+   "POSTing public key into the /people/Lambda/Joe profile" should {
+
+     "first create signed WebID certificate and add it to local SSL keystore" in {
+       val keystore = getClass.getClassLoader.getResource("KEYSTORE.jks")
+       val signer = X509CertSigner(
+         keystore,
+         "JKS",
+         "secret",
+         "localhost"
+       )
+
+       val rsagen = KeyPairGenerator.getInstance("RSA")
+       rsagen.initialize(512)
+       val rsaKP = rsagen.generateKeyPair()
+
+
+       val testCert = signer.generate("CN=JoeLambda, OU=DIG, O=W3C", rsaKP.getPublic.asInstanceOf[RSAPublicKey], 1, webID)
+       
+       testCert mustNotBe null
+
+       testKeyManager.addClientCert("JoeLambda",Array(testCert),rsaKP.getPrivate)
+    }
+
+     "return a 200 when POSTing relations to profile" in {
+       val joeCert = testKeyManager.getCertificateChain("JoeLambda")
+
+       joeCert mustNotBe null
+
+       val joeKey = joeCert(0).getPublicKey.asInstanceOf[RSAPublicKey]
+
+       val updateQStr = updatePk.format(
+                     joeKey.getModulus.toString(16),
+                     joeKey.getPublicExponent()
+       )
+
+       val httpCode = Http(
+         webidProfile.secure.postSPARQL(updateQStr) get_statusCode )
+        httpCode must_== 200
+     }
+
+     "create 3 more relations" in {
+       val model = Http(webidProfile.secure as_model(baseURI(webidProfile.secure), RDFXML))
+       model.size() must_== 7
+         
+     }
+   }
+
+  val aclRestriction = """
+  @prefix acl: <http://www.w3.org/ns/auth/acl#> .
+  @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+  @prefix : <#> .
+
+  :a1 a acl:Authorization;
+     acl:accessTo <Joe>;
+     acl:mode acl:Write;
+     acl:agent <%s>, <http://bblfish.net/people/henry/card#me> .
+
+  :allRead a acl:Authorization;
+     acl:accessTo <Joe>;
+     acl:mode acl:Read;
+     acl:agentClass foaf:Agent .
+  """
+
+
+  "PUT access control statements in directory" should {
+    "return a 201" in {
+      val httpCode = Http( lambdaMetaURI.secure.put(TURTLE, aclRestriction.format(webID.toExternalForm)) get_statusCode )
+       httpCode must_== 201
+    }
+
+    "create a resource on disk" in {
+       lambdaMeta must be file
+    }
+    
+    "everybody can still read the profile" in {
+      testKeyManager.setId(null)
+      val model = Http(webidProfile.secure as_model(baseURI(webidProfile.secure), RDFXML))
+      model.size() must_== 7
+    }
+    
+    "no one other than the user can change the profile" in {
+      val httpCode = Http.when(_ == 401)(webidProfile.secure.put(TURTLE, foaf) get_statusCode)
+      httpCode must_== 401
+    }
+
+    "access it as the user - allow him to add a friend" in {
+      testKeyManager.setId("JoeLambda")
+
+/*    The code below was very useful to help me debug this.
+      Sometimes it helps to get back to basics. So I will leave this here.
+
+      val scon =webidProfile.secure.to_uri.toURL.openConnection().asInstanceOf[HttpsURLConnection]
+      scon.setSSLSocketFactory(sslContext.getSocketFactory)
+      scon.setRequestProperty("Content-Type",Post.SPARQL)
+      scon.setRequestProperty("User-Agent" , "Java/1.7.0")
+      scon.setRequestMethod("POST")
+      val msg = updateFriend.format("http://bblfish.net/#hjs").getBytes("UTF-8")
+      scon.setRequestProperty("Content-Length",msg.length.toString)
+      scon.setDoOutput(true)
+      scon.setDoInput(true)
+
+      val out = scon.getOutputStream
+      out.write(msg)
+      out.flush()
+      out.close()
+      scon.connect()
+
+      val httpCode = scon.getResponseCode
+*/
+
+      val req =webidProfile.secure.PUT <:< Map("User-Agent" -> "Java/1.7.0","Content-Type"->Post.SPARQL)
+      val req2 = req.copy(
+              method="POST",
+              body=Some(new RefStringEntity(updateFriend.format(webID.toExternalForm),Post.SPARQL,"UTF-8"))
+            )
+
+      val httpCode = Http( req2 get_statusCode )
+      httpCode must_== 200
+    }
+
+    "and so have one more relation in the foaf" in {
+      val model = Http(webidProfile.secure as_model(baseURI(webidProfile.secure), RDFXML))
+      model.size() must_== 8
+    }
+
+  }
+
+
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/auth/SecureReadWriteWebSpec.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,42 @@
+/*
+ * 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 org.w3.readwriteweb.auth
+
+import org.specs.Specification
+
+/**
+ * @author hjs
+ * @created: 25/10/2011
+ */
+
+object SecureReadWriteWebSpec extends Specification {
+  try {
+  "The Secure Read Write Web".isSpecifiedBy(
+     CreateWebIDSpec
+   )
+  } catch {
+    case e => e.printStackTrace(System.out)
+  }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/scala/auth/secure_specs.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,134 @@
+/*
+ * 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 org.w3.readwriteweb.auth
+
+import unfiltered.spec.netty.Started
+import org.specs.Specification
+import unfiltered.netty.{ReceivedMessage, ServerErrorResponse, cycle}
+import java.io.File
+import org.w3.readwriteweb._
+import grizzled.file.GrizzledFile._
+
+import java.security.cert.X509Certificate
+import org.apache.http.conn.scheme.Scheme
+import dispatch.Http
+import org.apache.http.client.HttpClient
+import javax.net.ssl.{SSLContext, X509TrustManager, KeyManager}
+
+/**
+ * @author hjs
+ * @created: 24/10/2011
+ */
+
+
+trait SecureServed extends Started {
+  import org.w3.readwriteweb.netty._
+
+  //todo: replace this with non property method of setting this.
+  System.setProperty("netty.ssl.keyStore",getClass.getClassLoader.getResource("KEYSTORE.jks").getFile)
+  System.setProperty("netty.ssl.keyStoreType","JKS")
+  System.setProperty("netty.ssl.keyStorePassword","secret")
+
+  def setup: (Https => Https)
+  lazy val server = setup( new KeyAuth_Https(port) )
+
+
+}
+
+object AcceptAllTrustManager extends X509TrustManager {
+      def checkClientTrusted(chain: Array[X509Certificate], authType: String) {}
+      def checkServerTrusted(chain: Array[X509Certificate], authType: String) {}
+      def getAcceptedIssuers = Array[X509Certificate]()
+}
+
+/**
+ * Netty resource managed with access control enabled
+ */
+trait SecureResourceManaged extends Specification with SecureServed {
+  import org.jboss.netty.handler.codec.http._
+
+  def resourceManager: ResourceManager
+
+  /**
+   * Inject flexible behavior into the client ssl so that it does not
+   * break on every localhost problem. It returns a key manager which can be used
+   * to allow the client to take on various guises
+   */
+  def flexi(client: HttpClient, km: KeyManager): SSLContext = {
+
+    val  sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
+  
+    sslContext.init(Array(km.asInstanceOf[KeyManager]), Array(AcceptAllTrustManager),null); // we are not trying to test our trust of localhost server
+
+    import org.apache.http.conn.ssl._
+    val sf = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
+    val scheme = new Scheme("https", 443, sf);
+    client.getConnectionManager.getSchemeRegistry.register(scheme)
+
+    sslContext
+  }
+
+
+
+  val webCache = new WebCache()
+  val serverSslContext = javax.net.ssl.SSLContext.getInstance("TLS");
+
+
+
+  flexi(webCache.http.client, new FlexiKeyManager)
+
+
+  val testKeyManager = new FlexiKeyManager();
+  val sslContext = flexi(Http.client,testKeyManager)
+  
+
+
+
+  val rww = new cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with ReadWriteWeb[ReceivedMessage,HttpResponse] {
+    val rm = resourceManager
+    def manif = manifest[ReceivedMessage]
+    override val authz = new RDFAuthZ[ReceivedMessage,HttpResponse](webCache,resourceManager)
+  }
+
+  def setup = { _.plan(rww) }
+
+}
+
+trait SecureFileSystemBased extends SecureResourceManaged {
+  lazy val mode: RWWMode = ResourcesDontExistByDefault
+
+  lazy val lang = TURTLE
+
+  lazy val baseURL = "/wiki"
+
+  lazy val root = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
+
+  lazy val resourceManager = new Filesystem(root, baseURL, lang)(mode)
+
+  doBeforeSpec {
+    if (root.exists) root.deleteRecursively()
+    root.mkdir()
+  }
+
+}
--- a/src/test/scala/util/specs.scala	Sat Nov 12 08:29:00 2011 -0500
+++ b/src/test/scala/util/specs.scala	Sat Nov 12 08:33:10 2011 -0500
@@ -2,48 +2,68 @@
 
 import org.w3.readwriteweb._
 
+import auth.RDFAuthZ
 import org.specs._
-import java.net.URL
-import unfiltered.response._
-import unfiltered.request._
 import dispatch._
 import java.io._
 
-import com.codecommit.antixml._
 import grizzled.file.GrizzledFile._
 
-import com.hp.hpl.jena.rdf.model._
-import com.hp.hpl.jena.query._
-import com.hp.hpl.jena.update._
+import org.w3.readwriteweb.utiltest._
+import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
+import unfiltered.filter.Planify
+import unfiltered.netty.{ReceivedMessage, ServerErrorResponse, cycle}
+import unfiltered.spec.netty.Started
 
-import org.w3.readwriteweb.util._
-import org.w3.readwriteweb.utiltest._
-
-trait ResourceManaged extends Specification with unfiltered.spec.jetty.Served {
+trait JettyResourceManaged extends Specification with unfiltered.spec.jetty.Served {
   
   def resourceManager: ResourceManager
-  
-  def setup = { _.filter(new ReadWriteWeb(resourceManager).plan) }
+
+  val rww = new ReadWriteWeb[HttpServletRequest,HttpServletResponse] {
+     val rm = resourceManager
+     def manif = manifest[HttpServletRequest]
+   }
+
+  def setup = { _.filter(Planify(rww.intent)) }
  
 }
 
+/**
+ * Netty Resource managed.
+ **/
+trait ResourceManaged extends Specification with unfiltered.spec.netty.Served {
+  import org.jboss.netty.handler.codec.http._
+
+  def resourceManager: ResourceManager
+
+  val rww = new cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with ReadWriteWeb[ReceivedMessage,HttpResponse] {
+    val rm = resourceManager
+    def manif = manifest[ReceivedMessage]
+  }
+
+  def setup = { _.plan(rww) }
+
+}
+
+
+
 trait FilesystemBased extends ResourceManaged {
-  
+
   lazy val mode: RWWMode = ResourcesDontExistByDefault
-  
+
   lazy val lang = RDFXML
-    
+
   lazy val baseURL = "/wiki"
-  
+
   lazy val root = new File(new File(System.getProperty("java.io.tmpdir")), "readwriteweb")
 
   lazy val resourceManager = new Filesystem(root, baseURL, lang)(mode)
-  
+
   doBeforeSpec {
     if (root.exists) root.deleteRecursively()
     root.mkdir()
   }
-  
+
 }
 
 trait SomeRDF extends SomeURI {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_www/.meta.n3	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,14 @@
+@prefix acl: <http://www.w3.org/ns/auth/acl#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix : <#> .
+
+:a1 a acl:Authorization;
+   acl:accessTo <foaf.n3>;
+   acl:mode acl:Write;
+   acl:agent <http://bblfish.net/people/henry/card#me> .
+
+    
+:readAll a acl:Authorization;
+   acl:accessTo <foaf.n3>;
+   acl:mode acl:Read;
+   acl:agentClass foaf:Agent .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_www/foaf.n3	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,9 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix : <#> .
+
+<> a foaf:PersonalProfileDocument;
+   foaf:primaryTopic :joe .
+
+:joe a foaf:Person;
+    foaf:name "John Doe";
+    foaf:knows <http://bblfish.net/people/henry/card#me> .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_www/hello.n3	Sat Nov 12 08:33:10 2011 -0500
@@ -0,0 +1,4 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+
+<> a foaf:Document .
+