WebIDP starting to function. Turned WebCache to object, as tracking it's around was too complicated. Added simple primitive caching to WebCache. X509Cache can now use google caching mechanism too because it no longer needs to be bothered with trying to use WebCache. Added singing code for stings needed by IDP.
--- a/src/main/resources/template/WebIDService.main.xhtml Sat Dec 03 22:27:00 2011 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<html xmlns="http://www.w3.org/1999/xhtml">
-<head id="head">
- <title>WebId Identity Provider Info Page</title>
- <script src="/public/logout.js" type="text/javascript" />
-</head>
-<body>
-
-<div id="tx-content">
- <p>This is a simple Identity Provider for <a href="http://webid.info/spec">WebID</a>. It is meant to help
- sites that would like to provide WebID authentication to their users quickly.</p>
- <p>If you are hosting such a site then you can rely on this service to help authenticate your users with WebID,
- without your needing to set up https on your server. When you are satisfied of its usefulness you can deploy it
- to your site.</p>
- <p>There are two stages to get going. First you need to create the login button linking to this service. Then you need to
- understand how to interpret what will be returned, so that you can write a script to authenticate
- your users with the given WebID - ie, set a cookie for them.</p>
-
- <h2>Create your login link</h2>
- <p>Create a login button or link that points to this service. This needs to contain an attribute as a URL to a
- script on your site so that we can send you the response. This will be done by redirecting the user's browser
- with a signed response containing his WebID. To create such a link enter the URL of your login service here:</p>
- <form action="" method="get">Requesting auth service URL:
- <input name="rs" size="80" type="text" />
- <input type="submit" value="Log into this service provider" />
- </form>
- <p>By clicking on the form you will land on a page whose URL is the one you should enter into your
- login button/link. You will also see what identity you were logged in as, and given some options to change
- it.
- </p>
-
- <h2>Understanding the response</h2>
- <p>The redirected to URL is constructed on the following pattern: </p>
- <pre><b>$relyingService?webid=$webid&ts=$timeStamp</b>&sig=$URLSignature</pre>
- <p>Where the above variables have the following meanings: </p>
- <ul>
- <li><code>$relyingService</code> is the URL passed by the server in
- the initial request as the <code>rs</code> parameter, and is the service to which the response is sent.</li>
- <li><code>$webid</code> is the WebID of the user connecting.</li>
- <li><code>$timeStamp</code> is a time stamp in XML Schema format
- (same as used by Atom). This is needed to reduce the ease of developing
- replay attacks.</li>
- <li><code>$URLSignature</code> is the signature of the whole URL
- in bold above using the public key shown below, and encoded in a
- <a href="http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString%28byte[]%29">URL friendly base64</a> encoding.</li>
- </ul>
-
- <h3>Error responses</h3>
- <p>In case of error the service gets redirected to <code>$relyingService?error=$code</code>Where
- $code can be either one of</p>
- <ul>
- <li><code>nocert</code>: when the client has no cert. </li>
- <li><code>noVerifiedWebId</code>: no verified WebId was found in the certificate</li>
- <li><code>noWebId</code>: todo: show this error when there are no webids at all</li>
- <li><code>IdPError</code>: for some error in the IdP setup. Warn
- the IdP administrator!</li>
- <li>other messages, not standardised yet</li>
- </ul>
-
- <h2>Verifiying the WebId</h2>
-
- <p>In order for the Relying Party to to be comfortable that the returned WebId
- was not altered in transit, the whole URL is signed by this server as
- shown above. Here are the public keys and algorithms this us using:</p>
-
-
- <p>The signature uses the RSA with SHA-1 algorithm.</p>
-
- <p>The public key used by this service that verifies the signature is: </p>
-
- <ul>
- <li>Key Type: <pre>http://www.w3.org/ns/auth/rsa#RSAPublicKey</pre></li>
- <li>public exponent (decimal): <pre>65537</pre> </li>
- <li>modulus (decimal):<br />
- <pre>84:56:e8:8b:04:b9:1f:3b:10:00:07:ab:18:e8:fc:66:4e:aa:bc:47:f6:
-41:56:ab:96:6f:9c:d5:fc:5d:e9:fd:ce:a1:0f:5e:ce:26:f5:2e:35:e2:
-b7:0f:b3:db:17:0b:1b:c9:73:69:39:8a:39:4d:23:c3:b2:99:a7:a5:8b:
-5b:a8:2a:84:05:a3:d8:14:35:2e:49:7d:47:b6:80:52:90:37:ca:99:39:
-da:08:a4:f2:ef:f9:26:25:a9:4e:dd:44:57:df:43:3f:95:cd:cf:34:3f:
-41:58:e4:bc:19:63:ad:8f:b5:65:e3:3e:5e:d2:b3:19:f6:ca:ed:e5:a1:
-e7:cd:f1:9f:70:04:ea:66:a9:ad:77:cb:02:8d:c1:8d:45:89:39:07:b4:
-54:71:98:82:b0:55:39:c4:50:ad:24:3a:df:8f:df:fa:39:36:da:d9:98:
-65:1c:dd:4d:3f:d9:09:a7:5e:2d:de:cd:af:22:1e:25:b1:2e:d1:6d:74:
-e4:96:2f:2a:87:5a:c1:23:37:ff:38:ed:e1:f5:c5:20:fc:81:cf:cb:c7:
-1e:61:d1:77:6b:32:0d:6a:94:cb:8e:98:55:07:ea:09:f5:01:75:79:07:
-6e:f5:50:06:d0:1f:bd:11:94:85:86:c5:42:6f:76:e9:a9:fa:cf:db:91:
-13:92:c2:69:
-</pre>
- </li>
- </ul>
-
- <p>For ease of use, depending on which tool you use, here is the public key in a PEM format:</p>
-<pre>
------BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhFboiwS5HzsQAAerGOj8
-Zk6qvEf2QVarlm+c1fxd6f3OoQ9ezib1LjXitw+z2xcLG8lzaTmKOU0jw7KZp6WL
-W6gqhAWj2BQ1Lkl9R7aAUpA3ypk52gik8u/5JiWpTt1EV99DP5XNzzQ/QVjkvBlj
-rY+1ZeM+XtKzGfbK7eWh583xn3AE6maprXfLAo3BjUWJOQe0VHGYgrBVOcRQrSQ6
-34/f+jk22tmYZRzdTT/ZCadeLd7NryIeJbEu0W105JYvKodawSM3/zjt4fXFIPyB
-z8vHHmHRd2syDWqUy46YVQfqCfUBdXkHbvVQBtAfvRGUhYbFQm926an6z9uRE5LC
-aQIDAQAB
------END PUBLIC KEY-----
-</pre>
-</div>
-
-</body>
-</html>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/template/WebIdService.about.xhtml Mon Dec 05 01:08:17 2011 +0100
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head id="head">
+ <title>WebId Identity Provider Info Page</title>
+ <script src="/public/logout.js" type="text/javascript" />
+</head>
+<body>
+
+<div id="tx-content">
+ <p>This is a simple Identity Provider for <a href="http://webid.info/spec">WebID</a>. It is meant to help
+ sites that would like to provide WebID authentication to their users quickly.</p>
+ <p>If you are hosting such a site then you can rely on this service to help authenticate your users with WebID,
+ without your needing to set up https on your server. When you are satisfied of its usefulness you can deploy it
+ to your site.</p>
+ <p>There are two stages to get going. First you need to create the login button linking to this service. Then you need to
+ understand how to interpret what will be returned, so that you can write a script to authenticate
+ your users with the given WebID - ie, set a cookie for them.</p>
+
+ <h2>Create your login link</h2>
+ <p>Create a login button or link that points to this service. This needs to contain an attribute as a URL to a
+ script on your site so that we can send you the response. This will be done by redirecting the user's browser
+ with a signed response containing his WebID. To create such a link enter the URL of your login service here:</p>
+ <form action="" method="get">Requesting auth service URL:
+ <input name="rs" size="80" type="text" />
+ <input type="submit" value="Log into this service provider" />
+ </form>
+ <p>By clicking on the form you will land on a page whose URL is the one you should enter into your
+ login button/link. You will also see what identity you were logged in as, and given some options to change
+ it.
+ </p>
+
+ <h2>Understanding the response</h2>
+ <p>The redirected to URL is constructed on the following pattern: </p>
+ <pre><b>$relyingService?webid=$webid&ts=$timeStamp</b>&sig=$URLSignature</pre>
+ <p>Where the above variables have the following meanings: </p>
+ <ul>
+ <li><code>$relyingService</code> is the URL passed by the server in
+ the initial request as the <code>rs</code> parameter, and is the service to which the response is sent.</li>
+ <li><code>$webid</code> is the WebID of the user connecting.</li>
+ <li><code>$timeStamp</code> is a time stamp in XML Schema format
+ (same as used by Atom). This is needed to reduce the ease of developing
+ replay attacks.</li>
+ <li><code>$URLSignature</code> is the signature of the whole URL
+ in bold above using the public key shown below, and encoded in a
+ <a href="http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString%28byte[]%29">URL friendly base64</a> encoding.</li>
+ </ul>
+
+ <h3>Error responses</h3>
+ <p>In case of error the service gets redirected to <code>$relyingService?error=$code</code>Where
+ $code can be either one of</p>
+ <ul>
+ <li><code>nocert</code>: when the client has no cert. </li>
+ <li><code>noVerifiedWebId</code>: no verified WebId was found in the certificate</li>
+ <li><code>noWebId</code>: todo: show this error when there are no webids at all</li>
+ <li><code>IdPError</code>: for some error in the IdP setup. Warn
+ the IdP administrator!</li>
+ <li>other messages, not standardised yet</li>
+ </ul>
+
+ <h2>Verifiying the WebId</h2>
+
+ <p>In order for the Relying Party to to be comfortable that the returned WebId
+ was not altered in transit, the whole URL is signed by this server as
+ shown above. Here are the public keys and algorithms this us using:</p>
+
+
+ <p>The signature uses the RSA with SHA-1 algorithm.</p>
+
+ <p>The public key used by this service that verifies the signature is: </p>
+
+ <ul>
+ <li>Key Type: <pre>http://www.w3.org/ns/auth/rsa#RSAPublicKey</pre></li>
+ <li>public exponent (decimal): <pre>65537</pre> </li>
+ <li>modulus (decimal):<br />
+ <pre>84:56:e8:8b:04:b9:1f:3b:10:00:07:ab:18:e8:fc:66:4e:aa:bc:47:f6:
+41:56:ab:96:6f:9c:d5:fc:5d:e9:fd:ce:a1:0f:5e:ce:26:f5:2e:35:e2:
+b7:0f:b3:db:17:0b:1b:c9:73:69:39:8a:39:4d:23:c3:b2:99:a7:a5:8b:
+5b:a8:2a:84:05:a3:d8:14:35:2e:49:7d:47:b6:80:52:90:37:ca:99:39:
+da:08:a4:f2:ef:f9:26:25:a9:4e:dd:44:57:df:43:3f:95:cd:cf:34:3f:
+41:58:e4:bc:19:63:ad:8f:b5:65:e3:3e:5e:d2:b3:19:f6:ca:ed:e5:a1:
+e7:cd:f1:9f:70:04:ea:66:a9:ad:77:cb:02:8d:c1:8d:45:89:39:07:b4:
+54:71:98:82:b0:55:39:c4:50:ad:24:3a:df:8f:df:fa:39:36:da:d9:98:
+65:1c:dd:4d:3f:d9:09:a7:5e:2d:de:cd:af:22:1e:25:b1:2e:d1:6d:74:
+e4:96:2f:2a:87:5a:c1:23:37:ff:38:ed:e1:f5:c5:20:fc:81:cf:cb:c7:
+1e:61:d1:77:6b:32:0d:6a:94:cb:8e:98:55:07:ea:09:f5:01:75:79:07:
+6e:f5:50:06:d0:1f:bd:11:94:85:86:c5:42:6f:76:e9:a9:fa:cf:db:91:
+13:92:c2:69:
+</pre>
+ </li>
+ </ul>
+
+ <p>For ease of use, depending on which tool you use, here is the public key in a PEM format:</p>
+<pre>
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhFboiwS5HzsQAAerGOj8
+Zk6qvEf2QVarlm+c1fxd6f3OoQ9ezib1LjXitw+z2xcLG8lzaTmKOU0jw7KZp6WL
+W6gqhAWj2BQ1Lkl9R7aAUpA3ypk52gik8u/5JiWpTt1EV99DP5XNzzQ/QVjkvBlj
+rY+1ZeM+XtKzGfbK7eWh583xn3AE6maprXfLAo3BjUWJOQe0VHGYgrBVOcRQrSQ6
+34/f+jk22tmYZRzdTT/ZCadeLd7NryIeJbEu0W105JYvKodawSM3/zjt4fXFIPyB
+z8vHHmHRd2syDWqUy46YVQfqCfUBdXkHbvVQBtAfvRGUhYbFQm926an6z9uRE5LC
+aQIDAQAB
+-----END PUBLIC KEY-----
+</pre>
+</div>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/template/WebIdService.login.xhtml Mon Dec 05 01:08:17 2011 +0100
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head id="head">
+ <title>WebId Identity Provider Info Page</title>
+ <script src="/public/logout.js" type="text/javascript" />
+</head>
+<body>
+
+<p>Welcome <span class="user_name">Henry Story</span></p>
+
+<table>
+ <tr>
+ <td>
+ <a class="rp_url" href="http://bblfish.net/?webid=http%3A%2F%2Fbblfish.net%2Fpeople%2Fhenry%2Fcard%23me&ts=2011-12-04T01%3A16%3A58-0800&sig=GgxhFmf__2M0aQDjFU4Yg1bfjk90npXOUo6Mn_5xL4Z7H5mVP1BpDmoBorFT5uVvkDWOaSRU6ka2oo04GgoIsL9xODiYej7u0Cg-lEnA8aqL_7_X2pv1j3C6nPj-huZctEDi3QmmggtKsOVRp_0q8l9mmqIU-TBRyLjkk3mk6yUcWyAUu-pMP6DHqV-XVwys5Y3UYmeM3lPqOS_6wdd-qS-GkSZadtJEXu-IPUBEFWrVzGEx_Bu-WWWqbU5r0N6iY5PxmsZkKCSSdxfjGV-kjtHNcTQFbSlaGPkKeEEAPDLtoxxpBdaRUy4oO6biAW5tfp_eId_sfVHxLhaxqU9jnA">
+ <button>login to <span class="rp_name">bblfish.net</span></button>
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <img class="mugshot" src="http://farm1.static.flickr.com/164/373663745_e2066a4950.jpg" alt="mugshot"/>
+ </td>
+ </tr>
+</table>
+
+<p>Or would you rather use a different Identity? <a href="" onclick="return logout()">logout</a> -- (only works on IE and Firefox)</p>
+
+</body>
+</html>
\ No newline at end of file
--- a/src/main/scala/ReadWriteWebMain.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/ReadWriteWebMain.scala Mon Dec 05 01:08:17 2011 +0100
@@ -1,6 +1,7 @@
package org.w3.readwriteweb
-import auth.{RDFAuthZ, X509view}
+import auth.X509CertSigner._
+import auth.{X509CertSigner, RDFAuthZ, X509view}
import org.w3.readwriteweb.util._
import unfiltered.jetty._
@@ -11,6 +12,7 @@
import org.clapper.argot._
import ArgotConverters._
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+import java.security.KeyStore
trait ReadWriteWebArgs {
val logger: Logger = LoggerFactory.getLogger(this.getClass)
@@ -64,7 +66,12 @@
}
}
- val webCache = new WebCache()
+ val signer = {
+ val keystore = new File(System.getProperty( "netty.ssl.keyStore")).toURI.toURL
+ val ksTpe = System.getProperty("netty.ssl.keyStoreType","JKS")
+ val ksPass = System.getProperty("netty.ssl.keyStorePassword")
+ X509CertSigner( keystore, ksTpe, ksPass, "selfsigned" )
+ }
val baseURL = parser.parameter[String]("baseURL", "base URL", false)
@@ -93,7 +100,7 @@
val rww = new ReadWriteWeb[HttpServletRequest,HttpServletResponse] {
val rm = filesystem
def manif = manifest[HttpServletRequest]
- override implicit val authz = new RDFAuthZ[HttpServletRequest,HttpServletResponse](webCache,filesystem)
+ override implicit val authz = new RDFAuthZ[HttpServletRequest,HttpServletResponse](filesystem)
}
@@ -116,7 +123,6 @@
object x509v extends X509view[HttpServletRequest,HttpServletResponse] {
- def wc = webCache
def manif = manifest[HttpServletRequest]
}
--- a/src/main/scala/WebCache.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/WebCache.scala Mon Dec 05 01:08:17 2011 +0100
@@ -28,6 +28,10 @@
import org.w3.readwriteweb.util._
import java.net.{ConnectException, URL}
import scalaz.{Scalaz, Validation}
+import java.io.File
+import java.util.concurrent.TimeUnit
+import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
+import org.w3.readwriteweb.Lang._
/**
@@ -36,10 +40,21 @@
*
* The WebCache currently does not cache
*/
-class WebCache extends ResourceManager {
+object WebCache extends ResourceManager {
import dispatch._
import Scalaz._
+ //this is a simple but quite stupid web cache so that graphs can stay in memory and be used a little
+ // bit across sessions
+ val cache: Cache[URL,Validation[Throwable,Model]] =
+ CacheBuilder.newBuilder()
+ .expireAfterAccess(5, TimeUnit.MINUTES)
+// .softValues()
+// .expireAfterWrite(30, TimeUnit.MINUTES)
+ .build(new CacheLoader[URL, Validation[Throwable,Model]] {
+ def load(url: URL) = getUrl(url)
+ })
+
val http = new Http with thread.Safety
def basePath = null //should be cache dir?
@@ -47,7 +62,17 @@
def sanityCheck() = true //cache dire exists? But is this needed for functioning?
def resource(u : URL) = new org.w3.readwriteweb.Resource {
- def get() = {
+ def get() = cache.get(u)
+
+ // 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")
+ }
+
+ private def getUrl(u: URL) = {
+
// 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)
@@ -71,20 +96,14 @@
}
})
try {
- val future = http(handler)
- future
+ val future = http(handler)
+ future
} catch {
case e: ConnectException => e.fail
}
}
- // 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")
- }
override def finalize() { http.shutdown() }
}
--- a/src/main/scala/auth/Authz.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/Authz.scala Mon Dec 05 01:08:17 2011 +0100
@@ -40,11 +40,11 @@
object AuthZ {
- implicit def x509toSubject(x509c: X509Claim)(implicit cache: WebCache): Subject = {
+ implicit def x509toSubject(x509c: X509Claim): Subject = {
val subject = new Subject()
subject.getPublicCredentials.add(x509c)
if (x509c.isCurrent()) {
- val verified = x509c.verifiedClaims
+ val verified = x509c.verified
subject.getPrincipals.addAll(verified.asJava)
}
subject
@@ -86,12 +86,11 @@
}
-class RDFAuthZ[Request, Response](val webCache: WebCache, rm: ResourceManager)
- (implicit val m: Manifest[Request]) extends AuthZ[Request,Response] {
+class RDFAuthZ[Request, Response](val 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)
--- a/src/main/scala/auth/Principals.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/Principals.scala Mon Dec 05 01:08:17 2011 +0100
@@ -75,8 +75,10 @@
case _ => false
}
- def getDefiningModel(implicit cache: WebCache): Validation[ProfileError, Model] =
- cache.resource(url).get() failMap {
+ //TODO: now that we are no longer passing the WebCache around it's questionable whether we still need this method
+ //in this class
+ def getDefiningModel: Validation[ProfileError, Model] =
+ WebCache.resource(url).get() failMap {
case ioe: WrappedIOException => new ProfileGetError("error fetching profile", Some(ioe),url)
case connE : ConnectException => new ProfileGetError("error fetching profile", Some(connE),url)
case other => new ProfileParseError("error parsing profile", Some(other),url)
--- a/src/main/scala/auth/WebIDSrvc.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/WebIDSrvc.scala Mon Dec 05 01:08:17 2011 +0100
@@ -23,38 +23,110 @@
package auth
-import org.w3.readwriteweb.WebCache
import java.io.File
import unfiltered.Cycle
-import unfiltered.request.{QueryString, Path}
import xml.{Elem, XML}
import unfiltered.response.{Html, Ok}
-import org.w3.readwriteweb.auth.NoX509
import org.fusesource.scalate.scuery.Transformer
+import unfiltered.request.{Params, Path}
+import unfiltered.request.&
+import java.net.{URLEncoder, URL}
+import java.util.Calendar
+import org.apache.commons.codec.binary.Base64
+import org.w3.readwriteweb.auth.{WebID, X509CertSigner, X509Claim}
+import java.text.SimpleDateFormat
+import org.w3.readwriteweb.util.trySome
+import unfiltered.request.Params.ParamMapper
+import com.hp.hpl.jena.sparql.vocabulary.FOAF
+import com.hp.hpl.jena.rdf.model.{RDFNode, ModelFactory}
/**
* @author Henry Story
*
*/
-
trait WebIDSrvc[Req,Res] {
- implicit def wc: WebCache
implicit def manif: Manifest[Req]
+ val signer: X509CertSigner
+
+
+ private val dateFormat: SimpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
+
+ /**
+ * @param webid a list of webIds identifying the user (only the first few will be used)
+ * @param replyTo the service that the response is sent to
+ * @return the URL of the response with the webid, timestamp appended and signed
+ */
+ private def createSignedResponse(webid: Seq[WebID], replyTo: URL): URL = {
+ var uri = replyTo.toExternalForm+"?"+ webid.slice(0,3).foldRight("") {
+ (wid,str) => "webid="+ URLEncoder.encode(wid.url.toExternalForm, "UTF-8")+"&"
+ }
+ uri = uri + "ts=" + URLEncoder.encode(dateFormat.format(Calendar.getInstance.getTime), "UTF-8")
+ val signedUri = uri +"&sig=" + URLEncoder.encode(new String(Base64.encodeBase64URLSafeString(signer.sign(uri))), "UTF-8")
+ return new URL(signedUri)
+ }
val fileDir: File = new File(this.getClass.getResource("/template/").toURI)
- lazy val webidSrvc: Elem = XML.loadFile(new File(fileDir, "WebIdService.main.xhtml"))
- lazy val noX509: Elem = XML.loadFile(new File(fileDir, "NoWebId.xhtml"))
+ lazy val webidSrvcPg: Elem = XML.loadFile(new File(fileDir, "WebIdService.login.xhtml"))
+ lazy val aboutPg: Elem = XML.loadFile(new File(fileDir, "WebIdService.about.xhtml"))
def intent : Cycle.Intent[Req,Res] = {
- case req @ Path("/srv/idp") => req match {
- case QueryString(query) => Ok ~> Html( new ServiceFiller().apply(webidSrvc) )
- case _ => Ok ~> Html (new NoX509().apply(noX509))
+ case req @ Path(path) if path.startsWith("/srv/idp") => req match {
+ case Params(RelyingParty(rp)) & X509Claim(claim) => Ok ~> Html( new ServiceTrans(rp,claim).apply(webidSrvcPg) )
+ case _ => Ok ~> Html(aboutTransform(aboutPg))
}
}
+
+ object aboutTransform extends Transformer
+
+
+ class ServiceTrans(relyingParty: URL, claim: X509Claim) extends Transformer {
+ val union = claim.verified.flatMap(_.getDefiningModel.toOption).fold(ModelFactory.createDefaultModel()){
+ (m1,m2)=>m1.add(m2)
+ }
+ $(".user_name").contents =
+ if (claim.verified.size==0)
+ claim.cert.getSubjectDN.getName //we have something, we can't rely on it, but we can use it
+ else {
+ val names = union.listObjectsOfProperty(union.createResource(claim.verified.head.url.toExternalForm),FOAF.name)
+ if (!names.hasNext) "nonname"
+ else {
+ val node: RDFNode = names.next()
+ if (node.isLiteral) node.asLiteral().getLexicalForm
+ else "anonymous"
+ }
+ }
+
+ $(".mugshot").attribute("src").value =
+ if (claim.verified.size==0)
+ "http://www.yourbdnews.com/wp-content/uploads/2011/08/anonymous.jpg"
+ else {
+ val names = union.listObjectsOfProperty(union.createResource(claim.verified.head.url.toExternalForm),FOAF.depiction)
+ if (!names.hasNext) "http://www.yourbdnews.com/wp-content/uploads/2011/08/anonymous.jpg"
+ else {
+ names.next.toString
+ }
+ }
+
+ $(".rp_name").contents = relyingParty.getHost
+ $(".rp_url").attribute("href").value = createSignedResponse(claim.verified,relyingParty).toExternalForm
+ }
+
}
-class ServiceFiller extends Transformer {
+/**
+ * similar to Extract superclass, but is useful when one has a number of attributes that
+ * have the same meaning. This can arise if one has legacy URLS or if one has code that
+ * has human readable and shorter ones
+ */
+import unfiltered.request.Params.Map
+class ExtractN[E,T](f: Map => Option[T]) extends Params.Extract[E,T](f) {
+ def this(names: Seq[String], f: Seq[String] => Option[T]) =
+ this({ params: Map => f(names.flatMap(s => params(s))) })
+}
-}
\ No newline at end of file
+object urlMap extends ParamMapper(_.flatMap(u=>trySome{new URL(u)}).headOption)
+object RelyingParty extends ExtractN(List("rs","authreqissuer"), urlMap )
+
+
--- a/src/main/scala/auth/WebIdClaim.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/WebIdClaim.scala Mon Dec 05 01:08:17 2011 +0100
@@ -23,15 +23,14 @@
package org.w3.readwriteweb.auth
-import org.w3.readwriteweb.WebCache
import java.security.interfaces.RSAPublicKey
import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
import scalaz.{Scalaz, Validation}
import Scalaz._
-import java.net.URL
import java.security.PublicKey
import com.hp.hpl.jena.rdf.model.Model
+import java.net.URL
/**
@@ -59,7 +58,7 @@
/**
* One has to construct a WebID using the object, that can do basic verifications
*/
-class WebIDClaim(val san: String, val key: PublicKey)(implicit cache: WebCache) {
+class WebIDClaim(val san: String, val key: PublicKey) {
import WebIDClaim.hex
import XSDDatatype._
@@ -83,8 +82,8 @@
case rsakey: RSAPublicKey =>
WebID(san).flatMap(webid=> webid.getDefiningModel.flatMap(rsaTest(webid, rsakey)) )
case _ => new UnsupportedKeyType("We only support RSA keys at present", key).fail
- }
}
+}
trait Err {
--- a/src/main/scala/auth/X509Cert.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/X509Cert.scala Mon Dec 05 01:08:17 2011 +0100
@@ -35,6 +35,8 @@
import unfiltered.util.IO
import sun.security.x509._
import org.w3.readwriteweb.util.trySome
+import actors.threadpool.TimeUnit
+import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
object X509CertSigner {
@@ -61,6 +63,13 @@
signingKey: PrivateKey ) {
val WebID_DN="""O=FOAF+SSL, OU=The Community of Self Signers, CN=Not a Certification Authority"""
+ val sigAlg = signingKey.getAlgorithm match {
+ case "RSA" => "SHA1withRSA"
+ case "DSA" => "SHA1withDSA"
+ //else will throw a case exception
+ }
+
+
/**
* 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
@@ -161,8 +170,21 @@
return cert
}
- def signString(): String = {
- return "todo"
+ val clonesig : Signature = sig
+
+ def sig: Signature = {
+ if (clonesig != null && clonesig.isInstanceOf[Cloneable]) clonesig.clone().asInstanceOf[Signature]
+ else {
+ val signature = Signature.getInstance(sigAlg)
+ signature.initSign(signingKey)
+ signature
+ }
+ }
+
+ def sign(string: String): Array[Byte] = {
+ val signature = sig
+ signature.update(string.getBytes("UTF-8"))
+ signature.sign
}
}
--- a/src/main/scala/auth/X509Claim.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/X509Claim.scala Mon Dec 05 01:08:17 2011 +0100
@@ -38,6 +38,9 @@
import scalaz.{Scalaz, Success, Validation}
import Scalaz._
import java.security.PublicKey
+import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
+import java.util.concurrent.TimeUnit
+import org.w3.readwriteweb.util.trySome
/**
* @author hjs
@@ -47,24 +50,18 @@
object X509Claim {
final val logger = LoggerFactory.getLogger(classOf[X509Claim])
- val idCache: HashMap[X509Certificate,X509Claim] = new HashMap
// this is cool because it is not in danger of running out of memory but it makes it impossible to create the claim
// with an implicit WebCache...
-// val idCache: Cache[X509Certificate, X509Claim] =
-// CacheBuilder.newBuilder()
-// .expireAfterWrite(30, TimeUnit.MINUTES)
-// .build(new CacheLoader[X509Certificate, X509Claim] {
-// def load(x509: X509Certificate) = new X509Claim(x509)
-// })
+ 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, _*) =>
- idCache.get(c1).orElse {
- val claim = new X509Claim(c1)
- idCache.put(c1,claim)
- Some(claim)
- }
+ def unapply[T](r: HttpRequest[T])(implicit m: Manifest[T]): Option[X509Claim] = r match {
+ case Certs(c1: X509Certificate, _*) => trySome(idCache.get(c1))
case _ => None
}
@@ -102,7 +99,7 @@
* @created: 30/03/2011
*/
// can't be a case class as it then creates object which clashes with defined one
-class X509Claim(val cert: X509Certificate)(implicit cache: WebCache) extends Refreshable {
+class X509Claim(val cert: X509Certificate) extends Refreshable {
import X509Claim._
val claimReceivedDate = new Date()
@@ -112,7 +109,7 @@
lazy val webidclaims: List[WebIDClaim] = getClaimedWebIds(cert) map { webid => new WebIDClaim(webid, cert.getPublicKey.asInstanceOf[RSAPublicKey]) }
- val verifiedClaims: List[WebID] = webidclaims.flatMap(_.verify.toOption)
+ lazy val verified: List[WebID] = webidclaims.flatMap(_.verify.toOption)
//note could also implement Destroyable
//
--- a/src/main/scala/auth/X509view.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/auth/X509view.scala Mon Dec 05 01:08:17 2011 +0100
@@ -47,7 +47,6 @@
*/
trait X509view[Req,Res] {
- implicit def wc: WebCache
implicit def manif: Manifest[Req]
val fileDir: File = new File(this.getClass.getResource("/template/").toURI)
@@ -73,7 +72,7 @@
$(".date").contents = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format(new Date)
}
-class X509Filler(x509: X509Claim)(implicit cache: WebCache) extends Transformer {
+class X509Filler(x509: X509Claim) extends Transformer {
$(".date").contents = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format( x509.claimReceivedDate)
$(".cert_test") { node =>
val x509Assertion = new Assertion(certOk,x509);
@@ -88,7 +87,7 @@
}
ff.flatten
}
- $(".no_webid") { node => if (x509.verifiedClaims.size==0) node else <span/> }
+ $(".no_webid") { node => if (x509.verified.size==0) node else <span/> }
$(".webid_test") { node =>
val ff = for (idclaim <- x509.webidclaims) yield {
val idAsrt = new Assertion(webidClaimTst, idclaim)
--- a/src/main/scala/netty/ReadWriteWebNetty.scala Sat Dec 03 22:27:00 2011 +0100
+++ b/src/main/scala/netty/ReadWriteWebNetty.scala Mon Dec 05 01:08:17 2011 +0100
@@ -23,6 +23,7 @@
package org.w3.readwriteweb.netty
+import _root_.auth.WebIDSrvc
import org.clapper.argot.ArgotUsageException
import scala.Console._
import org.w3.readwriteweb.auth.{X509view, RDFAuthZ}
@@ -60,7 +61,7 @@
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)
+ override val authz = new RDFAuthZ[ReceivedMessage,HttpResponse](filesystem)
}
//this is incomplete: we should be able to start both ports.... not sure how to do this yet.
@@ -72,6 +73,7 @@
// configures and launches a Netty server
service.plan(publicStatic).
plan( x509v ).
+ plan( webidp ).
plan( rww ).run()
}
@@ -93,9 +95,13 @@
}
object x509v extends cycle.Plan with cycle.ThreadPool with ServerErrorResponse with X509view[ReceivedMessage,HttpResponse] {
- def wc = webCache
def manif = manifest[ReceivedMessage]
}
+
+ object webidp extends cycle.Plan with cycle.ThreadPool with ServerErrorResponse with WebIDSrvc[ReceivedMessage, HttpResponse] {
+ def manif = manifest[ReceivedMessage]
+ val signer = ReadWriteWebNetty.signer
+ }
}