Authorization working: It currently only allows me, but that's an important starting point.
--- a/src/main/scala/Filesystem.scala Fri Oct 14 17:51:14 2011 +0200
+++ b/src/main/scala/Filesystem.scala Sat Oct 15 16:58:48 2011 +0200
@@ -40,7 +40,7 @@
val reader = model.getReader(lang)
reader.read(model, fis, url.toString)
} catch {
- case je:JenaException => error("@@@")
+ case je:JenaException => error(je.toString)
}
fis.close()
model.success
--- a/src/main/scala/ReadWriteWebMain.scala Fri Oct 14 17:51:14 2011 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala Sat Oct 15 16:58:48 2011 +0200
@@ -1,6 +1,6 @@
package org.w3.readwriteweb
-import auth.X509view
+import auth.{SimpleAuthZ, X509view}
import org.w3.readwriteweb.util._
import unfiltered.jetty._
@@ -64,7 +64,6 @@
implicit val webCache = new WebCache()
-
val baseURL = parser.parameter[String]("baseURL", "base URL", false)
// regular Java main
@@ -82,7 +81,7 @@
baseURL.value.get,
lang=rdfLanguage.value getOrElse "N3")(mode.value getOrElse ResourcesDontExistByDefault)
- val app = new ReadWriteWeb(filesystem)
+ val app = new ReadWriteWeb(filesystem, new SimpleAuthZ())
//this is incomplete: we should be able to start both ports.... not sure how to do this yet.
val service = httpsPort.value match {
@@ -95,8 +94,8 @@
context("/public"){ ctx:ContextBuilder =>
ctx.resources(ClasspathUtils.fromClasspath("public/").toURI.toURL)
}.
- filter(new X509view().plan).
- filter(app.plan).run()
+ filter(app.plan).
+ filter(new X509view().plan).run()
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/auth/Authz.scala Sat Oct 15 16:58:48 2011 +0200
@@ -0,0 +1,112 @@
+/*
+ * 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.Cycle
+import unfiltered.request._
+import unfiltered.response.{Unauthorized, BadRequest}
+import collection.JavaConversions._
+import javax.security.auth.Subject
+import org.w3.readwriteweb.WebCache
+import org.w3.readwriteweb.auth.Authz._
+import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+
+/**
+ * @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 {
+
+ def authMethod(s: String, httpMethod: Method) = new AuthzFunc
+
+
+
+ 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 NoAuthZ extends Authz {
+ def apply(in: Plan.Intent) = in
+}
+
+class SimpleAuthZ(implicit val WebCache: WebCache) extends Authz {
+
+ def apply(in: Plan.Intent): Plan.Intent = {
+ req: HttpRequest[HttpServletRequest] => req match {
+ case Path(path) & HttpMethod(m) => { //first get things that cost nothing
+ val autf = authMethod(path,m)
+ if (autf.requiresAuth) {
+ X509Claim.unapply(req) match {
+ case Some(claim) => {
+ if (autf.isAuthorized(claim,m,path)) in(req)
+ else Unauthorized
+ }
+ case None => Unauthorized
+ }
+ } else Unauthorized
+ }
+ case _ => BadRequest
+ }
+ }
+
+}
+
+trait Authz {
+ def apply(in: Plan.Intent): Plan.Intent // we may want to generalise more later to Cycle.Intent[A,B]
+
+}
+
+class AuthzFunc {
+ def requiresAuth(): Boolean = true
+ def isAuthorized(webid: Subject, m: Method, path: String ): Boolean =
+ webid.getPrincipals().exists(p=>p.getName=="http://bblfish.net/people/henry/card#me")
+
+}
--- a/src/main/scala/auth/X509Cert.scala Fri Oct 14 17:51:14 2011 +0200
+++ b/src/main/scala/auth/X509Cert.scala Sat Oct 15 16:58:48 2011 +0200
@@ -26,8 +26,6 @@
import javax.servlet.http.HttpServletRequest
import java.security.cert.X509Certificate
import unfiltered.request.HttpRequest
-import java.util.concurrent.TimeUnit
-import com.google.common.cache.{CacheLoader, CacheBuilder, Cache}
/**
* @author Henry Story, with help from Doug Tangren on unfiltered mailing list
--- a/src/main/scala/auth/X509view.scala Fri Oct 14 17:51:14 2011 +0200
+++ b/src/main/scala/auth/X509view.scala Sat Oct 15 16:58:48 2011 +0200
@@ -28,7 +28,7 @@
import org.w3.readwriteweb.WebCache
/**
- * This plan just described the authentication information.
+ * This plan just described the X509 WebID authentication information.
* This is a simple version. A future version will show EARL output, and so be useful for debugging the endpoint.
*
* @author hjs
--- a/src/main/scala/plan.scala Fri Oct 14 17:51:14 2011 +0200
+++ b/src/main/scala/plan.scala Sat Oct 15 16:58:48 2011 +0200
@@ -1,5 +1,6 @@
package org.w3.readwriteweb
+import auth.{NoAuthZ, Authz}
import org.w3.readwriteweb.util._
import unfiltered.request._
@@ -19,9 +20,8 @@
QueryTypeDescribe => DESCRIBE}
import scalaz._
-import Scalaz._
-class ReadWriteWeb(rm: ResourceManager) {
+class ReadWriteWeb(rm: ResourceManager, implicit val auth: Authz = new NoAuthZ()) {
val logger: Logger = LoggerFactory.getLogger(this.getClass)
@@ -53,6 +53,7 @@
* through another implicit conversion. It saves us the call to the Validation.lift() method
*/
val plan = unfiltered.filter.Planify {
+ auth {
case req @ Path(path) if path startsWith rm.basePath => {
val baseURI = req.underlying.getRequestURL.toString
val r: Resource = rm.resource(new URL(baseURI))
@@ -129,6 +130,7 @@
case _ => MethodNotAllowed ~> Allow("GET", "PUT", "POST")
}
}
+ }
}