From 2d716e8f14d1c5c7aa830f9010287a14dd2f1fdc Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Sun, 8 Apr 2012 23:04:05 +0200 Subject: Implement an OpenID identity provider. --- project.clj | 3 ++ schema.sql | 9 ++++ src/mulk/benki/id.clj | 112 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mulk/benki/main.clj | 2 +- 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/mulk/benki/id.clj 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 + " + + + + http://openid.net/signon/1.0 + {base-uri}/openid/api + + + " + "{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] -- cgit v1.2.3