Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

redis store works with clients, tokens and users

  • Loading branch information...
commit ba7a6f3a6976480a65d77af4cfc8d7931ef8c876 1 parent 8d735cc
Pelle Braendgaard authored
25 README.md
Source Rendered
@@ -74,9 +74,11 @@ Stores are used to store tokens and will be used to store clients and users as w
74 74
75 75 There is a generalized protocol called Store and currently a simple memory implementation used for it.
76 76
77   -It should be pretty simple to implement this Store with redis, sql, datomic or what have you. I will write a reference implementation using redis next.
  77 +It should be pretty simple to implement this Store with redis, sql, datomic or what have you.
78 78
79   -The stores used by the various parts are defined in an atom for each type. reset! each of them with your own implementation.
  79 +It includes a simple Redis implementation.
  80 +
  81 +The stores used by the various parts are defined in an atom for each type. reset! each of them with your own implementation.
80 82
81 83 The following stores are currently defined:
82 84
@@ -84,6 +86,24 @@ The following stores are currently defined:
84 86 * client-store is in clauth.client/client-store
85 87 * user-store is in clauth.user/user-store
86 88
  89 +To use the redis store add the following to your code:
  90 +
  91 + (reset! token-store (create-redis-store "tokens"))
  92 + (reset! client-store (create-redis-store "clients"))
  93 + (reset! user-store (create-redis-store "users"))
  94 +
  95 +And wrap your handler with a redis connection middleware similar to this:
  96 +
  97 + (defn wrap-redis-store [app]
  98 + (fn [req]
  99 + (redis/with-server
  100 + {:host "127.0.0.1"
  101 + :port 6379
  102 + :db 14
  103 + }
  104 + (app req))))
  105 +
  106 +
87 107 ## Run Demo App
88 108
89 109 A mini server demo is available. It creates a client for you and prints out instructions on how to issue tokens with curl.
@@ -94,7 +114,6 @@ A mini server demo is available. It creates a client for you and prints out inst
94 114
95 115 The goal is to implement the full [OAuth2 spec](http://tools.ietf.org/html/draft-ietf-oauth-v2-25) in this order:
96 116
97   -* Redis Store implementation
98 117 * [Authorization Code Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.1)
99 118 * [Implicit Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.2)
100 119 * [Refresh Tokens](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-1.5)
228 docs/uberdoc.html
@@ -3029,28 +3029,51 @@
3029 3029 };
3030 3030 })(SyntaxHighlighter);
3031 3031 </script><title>clauth -- Marginalia</title></head><body><table><tr><td class="docs"><div class="header"><h1 class="project-name">clauth</h1><h2 class="project-version">1.0.0-SNAPSHOT</h2><br /><p>OAuth2 based authentication library for Ring</p>
3032   -</div><div class="dependencies"><h3>dependencies</h3><table><tr><td class="dep-name">org.clojure/clojure</td><td class="dotted"><hr /></td><td class="dep-version">1.3.0</td></tr><tr><td class="dep-name">crypto-random</td><td class="dotted"><hr /></td><td class="dep-version">1.1.0</td></tr><tr><td class="dep-name">commons-codec</td><td class="dotted"><hr /></td><td class="dep-version">1.6</td></tr><tr><td class="dep-name">ring/ring-core</td><td class="dotted"><hr /></td><td class="dep-version">1.0.2</td></tr><tr><td class="dep-name">cheshire</td><td class="dotted"><hr /></td><td class="dep-version">2.1.0</td></tr><tr><td class="dep-name">clj-time</td><td class="dotted"><hr /></td><td class="dep-version">0.3.7</td></tr></table></div><div class="dependencies"><h3>dev dependencies</h3><table><tr><td class="dep-name">ring/ring-jetty-adapter</td><td class="dotted"><hr /></td><td class="dep-version">1.0.0</td></tr><tr><td class="dep-name">lein-marginalia</td><td class="dotted"><hr /></td><td class="dep-version">0.7.0</td></tr></table></div></td><td class="codes" style="text-align: center; vertical-align: middle;color: #666;padding-right:20px"><br /><br /><br />(this space intentionally left almost blank)</td></tr><tr><td class="docs"><div class="toc"><a name="toc"><h3>namespaces</h3></a><ul><li><a href="#clauth.client">clauth.client</a></li><li><a href="#clauth.demo">clauth.demo</a></li><li><a href="#clauth.endpoints">clauth.endpoints</a></li><li><a href="#clauth.middleware">clauth.middleware</a></li><li><a href="#clauth.token">clauth.token</a></li></ul></div></td><td class="codes">&nbsp;</td></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.client" name="clauth.client"><h1 class="project-name">clauth.client</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3032 +</div><div class="dependencies"><h3>dependencies</h3><table><tr><td class="dep-name">org.clojure/clojure</td><td class="dotted"><hr /></td><td class="dep-version">1.3.0</td></tr><tr><td class="dep-name">crypto-random</td><td class="dotted"><hr /></td><td class="dep-version">1.1.0</td></tr><tr><td class="dep-name">commons-codec</td><td class="dotted"><hr /></td><td class="dep-version">1.6</td></tr><tr><td class="dep-name">ring/ring-core</td><td class="dotted"><hr /></td><td class="dep-version">1.0.2</td></tr><tr><td class="dep-name">cheshire</td><td class="dotted"><hr /></td><td class="dep-version">2.1.0</td></tr><tr><td class="dep-name">clj-time</td><td class="dotted"><hr /></td><td class="dep-version">0.3.7</td></tr><tr><td class="dep-name">org.mindrot/jbcrypt</td><td class="dotted"><hr /></td><td class="dep-version">0.3m</td></tr></table></div><div class="dependencies"><h3>dev dependencies</h3><table><tr><td class="dep-name">ring/ring-jetty-adapter</td><td class="dotted"><hr /></td><td class="dep-version">1.0.0</td></tr><tr><td class="dep-name">lein-marginalia</td><td class="dotted"><hr /></td><td class="dep-version">0.7.0</td></tr><tr><td class="dep-name">org.clojars.tavisrudd/redis-clojure</td><td class="dotted"><hr /></td><td class="dep-version">1.3.1</td></tr></table></div></td><td class="codes" style="text-align: center; vertical-align: middle;color: #666;padding-right:20px"><br /><br /><br />(this space intentionally left almost blank)</td></tr><tr><td class="docs"><div class="toc"><a name="toc"><h3>namespaces</h3></a><ul><li><a href="#clauth.client">clauth.client</a></li><li><a href="#clauth.demo">clauth.demo</a></li><li><a href="#clauth.endpoints">clauth.endpoints</a></li><li><a href="#clauth.middleware">clauth.middleware</a></li><li><a href="#clauth.store">clauth.store</a></li><li><a href="#clauth.store.redis">clauth.store.redis</a></li><li><a href="#clauth.token">clauth.token</a></li><li><a href="#clauth.user">clauth.user</a></li></ul></div></td><td class="codes">&nbsp;</td></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.client" name="clauth.client"><h1 class="project-name">clauth.client</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
3033 3033 </td><td class="codes"><pre class="brush: clojure">(ns clauth.client
3034   - (:use [clauth.token]))</pre></td></tr><tr><td class="docs"><p>In memory client store</p>
3035   -</td><td class="codes"><pre class="brush: clojure">(def clients
3036   - (atom {})) </pre></td></tr><tr><td class="docs"><p>create a unique client and store it in the client store</p>
  3034 + (:use [clauth.token])
  3035 + (:use [clauth.store]))</pre></td></tr><tr><td class="docs">
  3036 +</td><td class="codes"><pre class="brush: clojure">(defonce client-store (atom (create-memory-store)))</pre></td></tr><tr><td class="docs">
  3037 +</td><td class="codes"><pre class="brush: clojure">(defrecord ClientApplication
  3038 + [client-id client-secret name url])</pre></td></tr><tr><td class="docs"><p>Create new client-application record</p>
  3039 +</td><td class="codes"><pre class="brush: clojure">(defn client-app
  3040 + ([attrs] ; Swiss army constructor. There must be a better way.
  3041 + (cond
  3042 + (nil? attrs) nil
  3043 + (instance? ClientApplication attrs) attrs
  3044 + (instance? java.lang.String attrs) (client-app (cheshire.core/parse-string attrs true))
  3045 + :default (ClientApplication. (attrs :client-id) (attrs :client-secret) (attrs :name) (attrs :url))))
  3046 + ([] (client-app nil nil))
  3047 + ([name url] (ClientApplication. (generate-token) (generate-token) name url)))</pre></td></tr><tr><td class="docs"><p>mainly for used in testing. Clears out all clients.</p>
  3048 +</td><td class="codes"><pre class="brush: clojure">(defn reset-client-store!
  3049 + []
  3050 + (reset-store! @client-store))</pre></td></tr><tr><td class="docs"><p>Find OAuth token based on the token string</p>
  3051 +</td><td class="codes"><pre class="brush: clojure">(defn fetch-client
  3052 + [t]
  3053 + (client-app (fetch @client-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given ClientApplication and return it.</p>
  3054 +</td><td class="codes"><pre class="brush: clojure">(defn store-client
  3055 + [t]
  3056 + (store @client-store :client-id t))</pre></td></tr><tr><td class="docs"><p>Sequence of clients</p>
  3057 +</td><td class="codes"><pre class="brush: clojure">(defn clients
  3058 + []
  3059 + (entries @client-store))</pre></td></tr><tr><td class="docs"><p>create a unique client and store it in the client store</p>
3037 3060 </td><td class="codes"><pre class="brush: clojure">(defn register-client
3038   - [ attrs ]
3039   - (let [client-id (generate-token)
3040   - record (assoc attrs :client-id client-id :client-secret (generate-token))]
3041   - (do
3042   - (swap! clients assoc client-id record)
3043   - record)))</pre></td></tr><tr><td class="docs"><p>authenticate client application using client<em>id and client</em>secret</p>
  3061 + ([] (register-client nil nil))
  3062 + ([ name url ]
  3063 + (let [client (client-app name url)]
  3064 + (store-client client))))</pre></td></tr><tr><td class="docs"><p>authenticate client application using client<em>id and client</em>secret</p>
3044 3065 </td><td class="codes"><pre class="brush: clojure">(defn authenticate-client
3045 3066 [client-id client-secret]
3046   - (if-let [ client (@clients client-id)]
3047   - (if (= client-secret (client :client-secret))
  3067 + (if-let [ client (fetch-client client-id)]
  3068 + (if (= client-secret (:client-secret client))
3048 3069 client)))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.demo" name="clauth.demo"><h1 class="project-name">clauth.demo</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
3049 3070 </td><td class="codes"><pre class="brush: clojure">(ns clauth.demo
3050 3071 (:use [clauth.middleware])
3051 3072 (:use [clauth.endpoints])
3052 3073 (:use [clauth.client])
3053 3074 (:use [clauth.token])
  3075 + (:use [clauth.store.redis])
  3076 + (:require [redis.core :as redis])
3054 3077 (:use [ring.adapter.jetty])
3055 3078 (:use [ring.middleware.cookies])
3056 3079 (:use [ring.middleware.params]))</pre></td></tr><tr><td class="docs"><p>dummy ring handler. Returns json with the token if present.</p>
@@ -3062,33 +3085,48 @@
3062 3085 (str &quot;{\&quot;token\&quot;:\&quot;&quot; (str token) &quot;\&quot;}&quot;)
3063 3086 &quot;{}&quot;)})</pre></td></tr><tr><td class="docs">
3064 3087 </td><td class="codes"><pre class="brush: clojure">(defn routes [req]
3065   - (do
3066   - (prn req)
3067   - (prn @clients)
3068 3088 (if (= &quot;/token&quot; (req :uri))
3069 3089 ((token-handler) req )
3070   - ((require-bearer-token! handler) req))))</pre></td></tr><tr><td class="docs"><p>start web server. This first wraps the request in the cookies and params middleware, then requires a bearer token.</p>
  3090 + ((require-bearer-token! handler) req)))</pre></td></tr><tr><td class="docs">
  3091 +</td><td class="codes"><pre class="brush: clojure">(defn wrap-redis-store [app]
  3092 + (fn [req]
  3093 + (redis/with-server
  3094 + {:host &quot;127.0.0.1&quot;
  3095 + :port 6379
  3096 + :db 14
  3097 + }
  3098 + (app req))))</pre></td></tr><tr><td class="docs"><p>start web server. This first wraps the request in the cookies and params middleware, then requires a bearer token.</p>
3071 3099
3072 3100 <p> The function passed in this example to require-bearer-token is a clojure set containing the single value "secret".</p>
3073 3101
3074 3102 <p> You could instead use a hash for a simple in memory token database or a function querying a database.</p>
3075 3103 </td><td class="codes"><pre class="brush: clojure">(defn -main
3076 3104 []
3077   - (let [client (register-client {:name &quot;My App&quot;})]
3078   - (println &quot;App starting up:&quot;)
3079   - (prn client)
3080   - (println &quot;Token endpoint /token&quot;)
3081   - (println)
3082   - (println &quot;Fetch a Client Credentialed access token:&quot;)
3083   - (println)
3084   - (println &quot;curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u &quot; (clojure.string/join &quot;:&quot; [(:client-id client) (:client-secret client)]) )
3085   - (println)
3086   - (run-jetty (-&gt; routes
3087   - (wrap-params)
3088   - (wrap-cookies)) {:port 3000})))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.endpoints" name="clauth.endpoints"><h1 class="project-name">clauth.endpoints</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3105 + (do
  3106 + (reset! token-store (create-redis-store &quot;tokens&quot;))
  3107 + (reset! client-store (create-redis-store &quot;clients&quot;))
  3108 + (redis/with-server
  3109 + {:host &quot;127.0.0.1&quot;
  3110 + :port 6379
  3111 + :db 14
  3112 + }
  3113 + (let [client ( or (first (clients)) (register-client))]
  3114 + (println &quot;App starting up:&quot;)
  3115 + (prn client)
  3116 + (println &quot;Token endpoint /token&quot;)
  3117 + (println)
  3118 + (println &quot;Fetch a Client Credentialed access token:&quot;)
  3119 + (println)
  3120 + (println &quot;curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u &quot; (clojure.string/join &quot;:&quot; [(:client-id client) (:client-secret client)]) )
  3121 + (println)
  3122 + (run-jetty (-&gt; routes
  3123 + (wrap-params)
  3124 + (wrap-cookies)
  3125 + (wrap-redis-store)) {:port 3000})))))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.endpoints" name="clauth.endpoints"><h1 class="project-name">clauth.endpoints</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
3089 3126 </td><td class="codes"><pre class="brush: clojure">(ns clauth.endpoints
3090 3127 (:use [clauth.token])
3091 3128 (:use [clauth.client])
  3129 + (:use [clauth.user])
3092 3130 (:use [cheshire.core])
3093 3131 (:import [org.apache.commons.codec.binary Base64]))</pre></td></tr><tr><td class="docs"><p>Take a token map and decorate it according to specs</p>
3094 3132
@@ -3139,6 +3177,13 @@
3139 3177 req
3140 3178 authenticator
3141 3179 (fn [req client] (respond-with-new-token client client))))</pre></td></tr><tr><td class="docs">
  3180 +</td><td class="codes"><pre class="brush: clojure">(defmethod token-request-handler &quot;password&quot; [req authenticator]
  3181 + (client-authenticated-request
  3182 + req
  3183 + authenticator
  3184 + (fn [req client] (if-let [user (authenticate-user ((req :params) &quot;username&quot;) ((req :params) &quot;password&quot;))]
  3185 + (respond-with-new-token client client)
  3186 + (error-response &quot;invalid_grant&quot;)))))</pre></td></tr><tr><td class="docs">
3142 3187 </td><td class="codes"><pre class="brush: clojure">(defmethod token-request-handler :default [req authenticator]
3143 3188 (error-response &quot;unsupported_grant_type&quot;))</pre></td></tr><tr><td class="docs">
3144 3189 </td><td class="codes"><pre class="brush: clojure">(defn token-handler
@@ -3148,7 +3193,7 @@
3148 3193 (fn [req]
3149 3194 (token-request-handler req authenticator))))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.middleware" name="clauth.middleware"><h1 class="project-name">clauth.middleware</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
3150 3195 </td><td class="codes"><pre class="brush: clojure">(ns clauth.middleware
3151   - (use [clauth.token]))</pre></td></tr><tr><td class="docs"><p>Wrap request with a OAuth2 bearer token as defined in http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08.</p>
  3196 + (:use [clauth.token]))</pre></td></tr><tr><td class="docs"><p>Wrap request with a OAuth2 bearer token as defined in http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08.</p>
3152 3197
3153 3198 <p> A find-token function is passed the token and returns a clojure map describing the subject of the token.</p>
3154 3199
@@ -3205,12 +3250,55 @@
3205 3250 (fn [req]
3206 3251 (if (req :access-token)
3207 3252 (app req)
3208   - (athentication-required-response req ))) find-token)))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.token" name="clauth.token"><h1 class="project-name">clauth.token</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3253 + (athentication-required-response req ))) find-token)))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.store" name="clauth.store"><h1 class="project-name">clauth.store</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3254 +</td><td class="codes"><pre class="brush: clojure">(ns clauth.store)</pre></td></tr><tr><td class="docs"><p>Store OAuthTokens</p>
  3255 +</td><td class="codes"><pre class="brush: clojure">(defprotocol Store
  3256 + (fetch [ e k ] &quot;Find the item based on a key.&quot;)
  3257 + (store [ e key_param item ] &quot;Store the given map using the value of the keyword key_param and return it.&quot;)
  3258 + (entries [e] &quot;sequence of entries&quot;)
  3259 + (reset-store! [e] &quot;clear all entries&quot;))</pre></td></tr><tr><td class="docs">
  3260 +</td><td class="codes"><pre class="brush: clojure">(defrecord MemoryStore [data]
  3261 + Store
  3262 + (fetch [this t] (@data t))
  3263 + (store [this key_param item]
  3264 + (do
  3265 + (swap! data assoc (key_param item) item)
  3266 + item))
  3267 + (entries [this] (or (vals @data) []))
  3268 + (reset-store! [this] (reset! data {})))</pre></td></tr><tr><td class="docs"><p>Create a memory token store</p>
  3269 +</td><td class="codes"><pre class="brush: clojure">(defn create-memory-store
  3270 + ([] (create-memory-store {}))
  3271 + ([data]
  3272 + (MemoryStore. (atom data))))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.store.redis" name="clauth.store.redis"><h1 class="project-name">clauth.store.redis</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3273 +</td><td class="codes"><pre class="brush: clojure">(ns clauth.store.redis
  3274 + (:use [clauth.store])
  3275 + (:require [redis.core :as redis])
  3276 + (:require [cheshire.core]))</pre></td></tr><tr><td class="docs"><p>get namespaced list of keys</p>
  3277 +</td><td class="codes"><pre class="brush: clojure">(defn namespaced-keys
  3278 + [namespace]
  3279 + (redis/keys (str namespace &quot;/*&quot;)))</pre></td></tr><tr><td class="docs"><p>get all items in namespace</p>
  3280 +</td><td class="codes"><pre class="brush: clojure">(defn all-in-namespace
  3281 + [namespace]
  3282 + (let [ks (remove nil? (namespaced-keys namespace))]
  3283 + (if (not-empty ks) (apply redis/mget ks)))) </pre></td></tr><tr><td class="docs">
  3284 +</td><td class="codes"><pre class="brush: clojure">(defrecord RedisStore [namespace]
  3285 + Store
  3286 + (fetch [this t] (if-let [j (redis/get (str namespace &quot;/&quot; t))]
  3287 + (cheshire.core/parse-string j true)))
  3288 + (store [this key_param item]
  3289 + (do
  3290 + (redis/set (str namespace &quot;/&quot; (key_param item)) (cheshire.core/generate-string item))
  3291 + item))
  3292 + (entries [this] (map #( cheshire.core/parse-string % true) (all-in-namespace namespace) ))
  3293 + (reset-store! [this] (redis/flushdb)))</pre></td></tr><tr><td class="docs"><p>Create a redis store</p>
  3294 +</td><td class="codes"><pre class="brush: clojure">(defn create-redis-store
  3295 + ([namespace]
  3296 + (RedisStore. namespace)))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.token" name="clauth.token"><h1 class="project-name">clauth.token</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
3209 3297 </td><td class="codes"><pre class="brush: clojure">(ns clauth.token
  3298 + (:use [clauth.store])
3210 3299 (:require [crypto.random])
3211   - (:require [clj-time.core :as time]))</pre></td></tr><tr><td class="docs"><p>In memory token store</p>
3212   -</td><td class="codes"><pre class="brush: clojure">(def tokens
3213   - (atom {})) </pre></td></tr><tr><td class="docs"><p>Check if object is valid</p>
  3300 + (:require [clj-time.core :as time])
  3301 + (:require [cheshire.core]))</pre></td></tr><tr><td class="docs"><p>Check if object is valid</p>
3214 3302 </td><td class="codes"><pre class="brush: clojure">(defprotocol Expirable
3215 3303 (is-valid? [ t ] &quot;is the object still valid&quot;))</pre></td></tr><tr><td class="docs">
3216 3304 </td><td class="codes"><pre class="brush: clojure">(extend-protocol Expirable clojure.lang.IPersistentMap
@@ -3235,23 +3323,83 @@
3235 3323 <li>object - An optional object authorized. Eg. account, photo</li>
3236 3324 </ul>
3237 3325 </td><td class="codes"><pre class="brush: clojure">(defn oauth-token
  3326 + ([attrs] ; Swiss army constructor. There must be a better way.
  3327 + (cond
  3328 + (nil? attrs) nil
  3329 + (instance? OAuthToken attrs) attrs
  3330 + (instance? java.lang.String attrs) (oauth-token (cheshire.core/parse-string attrs true))
  3331 + :default (OAuthToken. (attrs :token) (attrs :client) (attrs :subject) (attrs :expires) (attrs :scope) (attrs :object))))
3238 3332 ([client subject]
3239 3333 (oauth-token client subject nil nil nil))
3240 3334 ([client subject expires scope object]
3241 3335 (oauth-token (generate-token) client subject expires scope object))
3242 3336 ([token client subject expires scope object]
3243   - (OAuthToken. token client subject expires scope object)))</pre></td></tr><tr><td class="docs"><p>create a unique token and store it in the token store</p>
  3337 + (OAuthToken. token client subject expires scope object)))</pre></td></tr><tr><td class="docs">
  3338 +</td><td class="codes"><pre class="brush: clojure">(defonce token-store (atom (create-memory-store)))</pre></td></tr><tr><td class="docs"><p>mainly for used in testing. Clears out all tokens.</p>
  3339 +</td><td class="codes"><pre class="brush: clojure">(defn reset-token-store!
  3340 + []
  3341 + (reset-store! @token-store))</pre></td></tr><tr><td class="docs"><p>Find OAuth token based on the token string</p>
  3342 +</td><td class="codes"><pre class="brush: clojure">(defn fetch-token
  3343 + [t]
  3344 + (oauth-token (fetch @token-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given OAuthToken and return it.</p>
  3345 +</td><td class="codes"><pre class="brush: clojure">(defn store-token
  3346 + [t]
  3347 + (store @token-store :token t))</pre></td></tr><tr><td class="docs"><p>Sequence of tokens</p>
  3348 +</td><td class="codes"><pre class="brush: clojure">(defn tokens
  3349 + []
  3350 + (map oauth-token (entries @token-store)))</pre></td></tr><tr><td class="docs"><p>create a unique token and store it in the token store</p>
3244 3351 </td><td class="codes"><pre class="brush: clojure">(defn create-token
3245 3352 ([client subject]
3246 3353 (create-token (oauth-token client subject)))
3247 3354 ([client subject expires scope object]
3248 3355 (create-token (oauth-token client subject expires scope object)))
3249 3356 ([ token ]
3250   - (do
3251   - (swap! tokens assoc (:token token) token)
3252   - token)))</pre></td></tr><tr><td class="docs"><p>return a token from the store if it is valid.</p>
  3357 + (store-token token)))</pre></td></tr><tr><td class="docs"><p>return a token from the store if it is valid.</p>
3253 3358 </td><td class="codes"><pre class="brush: clojure">(defn find-valid-token
3254   - [token]
3255   - (if-let [t (@tokens token)]
3256   - (if (is-valid? t) t )))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr></table><div class="footer">Generated by <a href="https://github.com/fogus/marginalia">Marginalia</a>.&nbsp;&nbsp;Syntax highlighting provided by Alex Gorbatchev's <a href="http://alexgorbatchev.com/SyntaxHighlighter/">SyntaxHighlighter</a></div><script type="text/javascript">SyntaxHighlighter.defaults['gutter'] = false;
  3359 + [t]
  3360 + (if-let [token (fetch-token t)]
  3361 + (if (is-valid? token) token )))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#clauth.user" name="clauth.user"><h1 class="project-name">clauth.user</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs">
  3362 +</td><td class="codes"><pre class="brush: clojure">(ns clauth.user
  3363 + (:use [clauth.store])
  3364 + (:import [org.mindrot.jbcrypt BCrypt]))</pre></td></tr><tr><td class="docs">
  3365 +</td><td class="codes"><pre class="brush: clojure">(defonce user-store (atom (create-memory-store)))</pre></td></tr><tr><td class="docs">
  3366 +</td><td class="codes"><pre class="brush: clojure">(defrecord User
  3367 + [login password name url])</pre></td></tr><tr><td class="docs"><p>Perform BCrypt hash of password</p>
  3368 +</td><td class="codes"><pre class="brush: clojure">(defn bcrypt
  3369 + [password]
  3370 + (BCrypt/hashpw password (BCrypt/gensalt)))</pre></td></tr><tr><td class="docs"><p>Verify that candidate password matches the hashed bcrypted password</p>
  3371 +</td><td class="codes"><pre class="brush: clojure">(defn valid-password?
  3372 + [candidate hashed]
  3373 + (BCrypt/checkpw candidate hashed))</pre></td></tr><tr><td class="docs"><p>Create new user record</p>
  3374 +</td><td class="codes"><pre class="brush: clojure">(defn new-user
  3375 + ([attrs] ; Swiss army constructor. There must be a better way.
  3376 + (cond
  3377 + (nil? attrs) nil
  3378 + (instance? User attrs) attrs
  3379 + (instance? java.lang.String attrs) (new-user (cheshire.core/parse-string attrs true))
  3380 + :default (User. (attrs :login) (attrs :password) (attrs :name) (attrs :url))))
  3381 + ([ login password ] (new-user login password nil nil))
  3382 + ([ login password name url ] (User. login (bcrypt password) name url)))</pre></td></tr><tr><td class="docs"><p>mainly for used in testing. Clears out all users.</p>
  3383 +</td><td class="codes"><pre class="brush: clojure">(defn reset-user-store!
  3384 + []
  3385 + (reset-store! @user-store))</pre></td></tr><tr><td class="docs"><p>Find user based on login</p>
  3386 +</td><td class="codes"><pre class="brush: clojure">(defn fetch-user
  3387 + [t]
  3388 + (new-user (fetch @user-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given User and return it.</p>
  3389 +</td><td class="codes"><pre class="brush: clojure">(defn store-user
  3390 + [t]
  3391 + (store @user-store :login t))</pre></td></tr><tr><td class="docs"><p>Sequence of users</p>
  3392 +</td><td class="codes"><pre class="brush: clojure">(defn users
  3393 + []
  3394 + (entries @user-store))</pre></td></tr><tr><td class="docs"><p>create a unique user and store it in the user store</p>
  3395 +</td><td class="codes"><pre class="brush: clojure">(defn register-user
  3396 + ([ login password ] (register-user login password nil nil))
  3397 + ([ login password name url ]
  3398 + (let [user (new-user login password name url)]
  3399 + (store-user user))))</pre></td></tr><tr><td class="docs"><p>authenticate user application using login and password</p>
  3400 +</td><td class="codes"><pre class="brush: clojure">(defn authenticate-user
  3401 + [login password]
  3402 + (if-let [ user (fetch-user login)]
  3403 + (if (valid-password? password (:password user))
  3404 + user)))</pre></td></tr><tr><td class="spacer docs">&nbsp;</td><td class="codes" /></tr></table><div class="footer">Generated by <a href="https://github.com/fogus/marginalia">Marginalia</a>.&nbsp;&nbsp;Syntax highlighting provided by Alex Gorbatchev's <a href="http://alexgorbatchev.com/SyntaxHighlighter/">SyntaxHighlighter</a></div><script type="text/javascript">SyntaxHighlighter.defaults['gutter'] = false;
3257 3405 SyntaxHighlighter.all()</script></body></html>
8 src/clauth/client.clj
@@ -10,6 +10,12 @@
10 10
11 11 (defn client-app
12 12 "Create new client-application record"
  13 + ([attrs] ; Swiss army constructor. There must be a better way.
  14 + (cond
  15 + (nil? attrs) nil
  16 + (instance? ClientApplication attrs) attrs
  17 + (instance? java.lang.String attrs) (client-app (cheshire.core/parse-string attrs true))
  18 + :default (ClientApplication. (attrs :client-id) (attrs :client-secret) (attrs :name) (attrs :url))))
13 19 ([] (client-app nil nil))
14 20 ([name url] (ClientApplication. (generate-token) (generate-token) name url)))
15 21
@@ -21,7 +27,7 @@
21 27 (defn fetch-client
22 28 "Find OAuth token based on the token string"
23 29 [t]
24   - (fetch @client-store t))
  30 + (client-app (fetch @client-store t)))
25 31
26 32 (defn store-client
27 33 "Store the given ClientApplication and return it."
46 src/clauth/demo.clj
@@ -3,6 +3,8 @@
3 3 (:use [clauth.endpoints])
4 4 (:use [clauth.client])
5 5 (:use [clauth.token])
  6 + (:use [clauth.store.redis])
  7 + (:require [redis.core :as redis])
6 8 (:use [ring.adapter.jetty])
7 9 (:use [ring.middleware.cookies])
8 10 (:use [ring.middleware.params]))
@@ -23,6 +25,15 @@
23 25 ((token-handler) req )
24 26 ((require-bearer-token! handler) req)))
25 27
  28 +(defn wrap-redis-store [app]
  29 + (fn [req]
  30 + (redis/with-server
  31 + {:host "127.0.0.1"
  32 + :port 6379
  33 + :db 14
  34 + }
  35 + (app req))))
  36 +
26 37 (defn -main
27 38 "start web server. This first wraps the request in the cookies and params middleware, then requires a bearer token.
28 39
@@ -30,15 +41,26 @@
30 41
31 42 You could instead use a hash for a simple in memory token database or a function querying a database."
32 43 []
33   - (let [client (register-client)]
34   - (println "App starting up:")
35   - (prn client)
36   - (println "Token endpoint /token")
37   - (println)
38   - (println "Fetch a Client Credentialed access token:")
39   - (println)
40   - (println "curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u " (clojure.string/join ":" [(:client-id client) (:client-secret client)]) )
41   - (println)
42   - (run-jetty (-> routes
43   - (wrap-params)
44   - (wrap-cookies)) {:port 3000})))
  44 + (do
  45 +
  46 + (reset! token-store (create-redis-store "tokens"))
  47 + (reset! client-store (create-redis-store "clients"))
  48 + (redis/with-server
  49 + {:host "127.0.0.1"
  50 + :port 6379
  51 + :db 14
  52 + }
  53 + (let [client ( or (first (clients)) (register-client))]
  54 + (println "App starting up:")
  55 + (prn client)
  56 + (println "Token endpoint /token")
  57 + (println)
  58 + (println "Fetch a Client Credentialed access token:")
  59 + (println)
  60 + (println "curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u " (clojure.string/join ":" [(:client-id client) (:client-secret client)]) )
  61 + (println)
  62 +
  63 + (run-jetty (-> routes
  64 + (wrap-params)
  65 + (wrap-cookies)
  66 + (wrap-redis-store)) {:port 3000})))))
2  src/clauth/middleware.clj
... ... @@ -1,5 +1,5 @@
1 1 (ns clauth.middleware
2   - (use [clauth.token]))
  2 + (:use [clauth.token]))
3 3
4 4 (defn wrap-bearer-token
5 5 "Wrap request with a OAuth2 bearer token as defined in http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08.
7 src/clauth/store/redis.clj
@@ -12,9 +12,8 @@
12 12 (defn all-in-namespace
13 13 "get all items in namespace"
14 14 [namespace]
15   - (let [ks (remove nil? (namespaced-keys namespace))
16   - gt `(redis.core/mget ~@ks)]
17   - (if (not-empty ks) (eval gt)))) ; This seems wrong to me. Is this the best way of doing it?
  15 + (let [ks (remove nil? (namespaced-keys namespace))]
  16 + (if (not-empty ks) (apply redis/mget ks))))
18 17
19 18
20 19 (defrecord RedisStore [namespace]
@@ -24,7 +23,7 @@
24 23
25 24 (store [this key_param item]
26 25 (do
27   - (redis/set (str namespace "/" (item key_param)) (cheshire.core/generate-string item))
  26 + (redis/set (str namespace "/" (key_param item)) (cheshire.core/generate-string item))
28 27 item)
29 28 )
30 29 (entries [this] (map #( cheshire.core/parse-string % true) (all-in-namespace namespace) ))
16 src/clauth/token.clj
... ... @@ -1,7 +1,8 @@
1 1 (ns clauth.token
2 2 (:use [clauth.store])
3 3 (:require [crypto.random])
4   - (:require [clj-time.core :as time]))
  4 + (:require [clj-time.core :as time])
  5 + (:require [cheshire.core]))
5 6
6 7 (defprotocol Expirable
7 8 "Check if object is valid"
@@ -34,9 +35,12 @@
34 35 * scope - An optional vector of scopes authorized
35 36 * object - An optional object authorized. Eg. account, photo"
36 37
37   - ([attrs]
38   - (OAuthToken. (attrs "token") (attrs "client") (attrs "subject") (attrs "expires") (attrs "scope") (attrs "object"))
39   - )
  38 + ([attrs] ; Swiss army constructor. There must be a better way.
  39 + (cond
  40 + (nil? attrs) nil
  41 + (instance? OAuthToken attrs) attrs
  42 + (instance? java.lang.String attrs) (oauth-token (cheshire.core/parse-string attrs true))
  43 + :default (OAuthToken. (attrs :token) (attrs :client) (attrs :subject) (attrs :expires) (attrs :scope) (attrs :object))))
40 44 ([client subject]
41 45 (oauth-token client subject nil nil nil)
42 46 )
@@ -58,7 +62,7 @@
58 62 (defn fetch-token
59 63 "Find OAuth token based on the token string"
60 64 [t]
61   - (fetch @token-store t))
  65 + (oauth-token (fetch @token-store t)))
62 66
63 67 (defn store-token
64 68 "Store the given OAuthToken and return it."
@@ -68,7 +72,7 @@
68 72 (defn tokens
69 73 "Sequence of tokens"
70 74 []
71   - (entries @token-store))
  75 + (map oauth-token (entries @token-store)))
72 76
73 77 (defn create-token
74 78 "create a unique token and store it in the token store"
9 src/clauth/user.clj
@@ -20,6 +20,13 @@
20 20
21 21 (defn new-user
22 22 "Create new user record"
  23 + ([attrs] ; Swiss army constructor. There must be a better way.
  24 + (cond
  25 + (nil? attrs) nil
  26 + (instance? User attrs) attrs
  27 + (instance? java.lang.String attrs) (new-user (cheshire.core/parse-string attrs true))
  28 + :default (User. (attrs :login) (attrs :password) (attrs :name) (attrs :url))))
  29 +
23 30 ([ login password ] (new-user login password nil nil))
24 31 ([ login password name url ] (User. login (bcrypt password) name url)))
25 32
@@ -31,7 +38,7 @@
31 38 (defn fetch-user
32 39 "Find user based on login"
33 40 [t]
34   - (fetch @user-store t))
  41 + (new-user (fetch @user-store t)))
35 42
36 43 (defn store-user
37 44 "Store the given User and return it."
60 test/clauth/test/store/redis.clj
... ... @@ -1,5 +1,8 @@
1 1 (ns clauth.test.store.redis
2 2 (:use [clauth.store])
  3 + (:use [clauth.token])
  4 + (:use [clauth.client])
  5 + (:use [clauth.user])
3 6 (:use [clauth.store.redis])
4 7 (:require [redis.core :as redis])
5 8 (:use [clojure.test])
@@ -29,4 +32,59 @@
29 32 (is (= [] (entries st)))
30 33 (is (nil? (fetch st "item"))))))))
31 34
32   -
  35 +
  36 + (deftest token-store-implementation
  37 + (redis/with-server
  38 + {:host "127.0.0.1"
  39 + :port 6379
  40 + :db 15
  41 + }
  42 + (reset! token-store (create-redis-store "tokens"))
  43 + (reset-token-store!)
  44 + (is (= 0 (count (tokens))) "starts out empty")
  45 + (let
  46 + [record (oauth-token "my-client" "my-user")]
  47 + (is (nil? (fetch-token (:token record))))
  48 + (do
  49 + (store-token record)
  50 + (is (= record (fetch-token (:token record))))
  51 + (is (= 1 (count (tokens))) "added one"))))
  52 + (reset! token-store (create-memory-store)))
  53 +
  54 + (deftest client-store-implementation
  55 + (redis/with-server
  56 + {:host "127.0.0.1"
  57 + :port 6379
  58 + :db 15
  59 + }
  60 + (reset! client-store (create-redis-store "clients"))
  61 + (reset-client-store!)
  62 + (is (= 0 (count (clients))) "starts out empty")
  63 + (let
  64 + [ record (client-app)]
  65 + (is (nil? (fetch-client (:client-id record))))
  66 + (do
  67 + (store-client record)
  68 + (is (= record (fetch-client (:client-id record))))
  69 + (is (= 1 (count (clients))) "added one"))))
  70 + (reset! client-store (create-memory-store)))
  71 +
  72 +
  73 + (deftest user-store-implementation
  74 + (redis/with-server
  75 + {:host "127.0.0.1"
  76 + :port 6379
  77 + :db 15
  78 + }
  79 + (reset! user-store (create-redis-store "users"))
  80 + (reset-user-store!)
  81 + (is (= 0 (count (users))) "starts out empty")
  82 + (let
  83 + [ record (new-user "john@example.com" "password")]
  84 + (is (nil? (fetch-user "john@example.com")))
  85 + (do
  86 + (store-user record)
  87 + (is (= record (fetch-user "john@example.com")))
  88 + (is (= 1 (count (users))) "added one"))))
  89 + (reset! user-store (create-memory-store)))
  90 +

0 comments on commit ba7a6f3

Please sign in to comment.
Something went wrong with that request. Please try again.