summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2012-04-08 23:04:05 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2012-04-08 23:04:05 +0200
commit2d716e8f14d1c5c7aa830f9010287a14dd2f1fdc (patch)
tree5cf62973986fad360bc7f694ab4979cf571aa894
parent1ce4c0ac4485d67ed2b2395b380f4461ba087e15 (diff)
Implement an OpenID identity provider.
-rw-r--r--project.clj3
-rw-r--r--schema.sql9
-rw-r--r--src/mulk/benki/id.clj112
-rw-r--r--src/mulk/benki/main.clj2
4 files changed, 125 insertions, 1 deletions
diff --git a/project.clj b/project.clj
index f60689e..1384d4e 100644
--- a/project.clj
+++ b/project.clj
@@ -34,6 +34,9 @@
;; Additional libraries
;;[clj-oauth2 "0.0.1"]
[org.openid4java/openid4java-consumer "0.9.6" :extension "pom"]
+ [org.openid4java/openid4java-server "0.9.6" :extension "pom"]
+ [org.openid4java/openid4java-xri "0.9.6" :extension "pom"]
+ [org.openid4java/openid4java-infocard "0.9.6" :extension "pom"]
[org.jsoup/jsoup "1.6.1"]
[org.apache.abdera/abdera-parser "1.1.1"]
[clj-apache-http "2.3.2"]
diff --git a/schema.sql b/schema.sql
index 155c7a2..49e1e63 100644
--- a/schema.sql
+++ b/schema.sql
@@ -28,6 +28,15 @@ CREATE TABLE user_email_addresses(
FOREIGN KEY("user") REFERENCES users
);
+CREATE TABLE user_nicknames(
+ "user" INTEGER NOT NULL,
+ nickname VARCHAR NOT NULL,
+ PRIMARY KEY(nickname),
+ FOREIGN KEY("user") REFERENCES users
+);
+
+CREATE INDEX user_nicknames_user ON user_nicknames ("user");
+
CREATE TABLE page_keys(
"user" INTEGER NOT NULL,
page VARCHAR NOT NULL,
diff --git a/src/mulk/benki/id.clj b/src/mulk/benki/id.clj
new file mode 100644
index 0000000..1442903
--- /dev/null
+++ b/src/mulk/benki/id.clj
@@ -0,0 +1,112 @@
+(ns mulk.benki.id
+ (:refer-clojure)
+ (:use [clojure core repl pprint]
+ [hiccup core page-helpers]
+ [mulk.benki config util db]
+ [clojure.core.match
+ :only [match]]
+ [noir core]
+ [clojure.java.jdbc :only [transaction do-commands]])
+ (:require [noir.session :as session]
+ [noir.response :as response]
+ [noir.request :as request]
+ [clojure.java.jdbc :as sql]
+ [com.twinql.clojure.http :as http])
+ (:import [org.openid4java.server ServerManager]
+ [org.openid4java.message ParameterList AuthRequest DirectError]))
+
+
+(defonce manager
+ (doto (ServerManager.)
+ (.setOPEndpointUrl (str (:base-uri @benki-config) "/openid"))))
+
+(def profile-base-uri (str (:base-uri @benki-config) "/id/"))
+
+(defn user-owns-nickname? [user nickname]
+ (with-dbt
+ (sql/with-query-results results
+ ["SELECT 't' FROM user_nicknames WHERE nickname = ? AND \"user\" = ?"
+ nickname *user*]
+ (doall (seq results)))))
+
+(defn fail-authentication []
+ {:status 403, :type "text/plain", :body "Not authorized."})
+
+(defn nickname-from-profile-uri [uri]
+ (let [base-uri (.substring uri 0 (.length profile-base-uri))
+ nickname (.substring uri (.length profile-base-uri))]
+ (if (= base-uri profile-base-uri)
+ nickname
+ nil)))
+
+(defn format-openid-response [s]
+ {:status 200, :type "text/plain", :body s})
+
+(defn verify-openid [paramlist]
+ (let [auth-request (AuthRequest/createAuthRequest paramlist (.getRealmVerifier manager))
+ claimed-id (or (.getClaimed auth-request)
+ (get (:query-params (request/ring-request)) "openid.identity"))
+ nickname (nickname-from-profile-uri claimed-id)
+ okay? (and *user* (user-owns-nickname? *user* nickname))
+ response (.authResponse manager paramlist nil claimed-id (boolean okay?) false)]
+ (if (isa? (class response) DirectError)
+ (fail-authentication)
+ (do
+ (.sign manager response)
+ (redirect (.getDestinationUrl response true))))))
+
+(defn process-openid-request []
+ (let [query (:params (request/ring-request))
+ paramlist (ParameterList. query)
+ mode (query "openid.mode")]
+ (match [mode]
+ ["associate"]
+ (format-openid-response
+ (.keyValueFormEncoding (.associationRequest manager paramlist)))
+ ["check_authentication"]
+ (format-openid-response
+ (.keyValueFormEncoding (.verify manager paramlist)))
+ ["checkid_setup"]
+ (with-auth
+ (verify-openid paramlist))
+ ["checkid_immediate"]
+ (verify-openid paramlist)
+ [x]
+ {:status 200, :headers {"Content-Type" "text/plain; charset=utf-8"}, :body (str "Whaaaat? What is “" x "” supposed to mean?? This is what you sent:" (request/ring-request)
+ )})))
+
+(def profile-page {})
+
+(defn show-profile-page []
+ (layout profile-page "A Profile Page"
+ [:body
+ [:p "This is a profile page."]]))
+
+(defn render-xrds [nickname]
+ {:status 200
+ :headers {"Content-Type" "application/xrds+xml; charset=UTF-8"}
+ :body
+ (clojure.string/replace
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <xrds:XRDS xmlns:xrds=\"xri://$xrds\" xmlns=\"xri://$xrd*($v*2.0)\">
+ <XRD>
+ <Service>
+ <Type>http://openid.net/signon/1.0</Type>
+ <URI>{base-uri}/openid/api</URI>
+ </Service>
+ </XRD>
+ </xrds:XRDS>"
+ "{base-uri}"
+ (:base-uri @benki-config))})
+
+(defpage [:get "/openid/api"] {}
+ (process-openid-request))
+
+(defpage [:post "/openid/api"] {}
+ (process-openid-request))
+
+(defpage [:get "/id/:nickname"] {nickname :nickname}
+ (if (re-find #"application/xrds\+xml"
+ (get-in (request/ring-request) [:headers "accept"]))
+ (render-xrds nickname)
+ (show-profile-page)))
diff --git a/src/mulk/benki/main.clj b/src/mulk/benki/main.clj
index 7ec01ec..bdecdc1 100644
--- a/src/mulk/benki/main.clj
+++ b/src/mulk/benki/main.clj
@@ -5,7 +5,7 @@
[hiccup core page-helpers]
[mulk.benki util config db])
(:require [noir server options]
- [mulk.benki wiki auth book_marx]
+ [mulk.benki wiki auth book_marx id]
[ring.middleware.file]
[noir.session :as session]
[noir.request :as request]