Skip to content
Browse files

redis store works with clients, tokens and users

  • Loading branch information...
1 parent 8d735cc commit ba7a6f3a6976480a65d77af4cfc8d7931ef8c876 @pelle committed Mar 20, 2012
View
25 README.md
@@ -74,16 +74,36 @@ Stores are used to store tokens and will be used to store clients and users as w
There is a generalized protocol called Store and currently a simple memory implementation used for it.
-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.
+It should be pretty simple to implement this Store with redis, sql, datomic or what have you.
-The stores used by the various parts are defined in an atom for each type. reset! each of them with your own implementation.
+It includes a simple Redis implementation.
+
+The stores used by the various parts are defined in an atom for each type. reset! each of them with your own implementation.
The following stores are currently defined:
* token-store is in clauth.token/token-store
* client-store is in clauth.client/client-store
* user-store is in clauth.user/user-store
+To use the redis store add the following to your code:
+
+ (reset! token-store (create-redis-store "tokens"))
+ (reset! client-store (create-redis-store "clients"))
+ (reset! user-store (create-redis-store "users"))
+
+And wrap your handler with a redis connection middleware similar to this:
+
+ (defn wrap-redis-store [app]
+ (fn [req]
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 14
+ }
+ (app req))))
+
+
## Run Demo App
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
The goal is to implement the full [OAuth2 spec](http://tools.ietf.org/html/draft-ietf-oauth-v2-25) in this order:
-* Redis Store implementation
* [Authorization Code Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.1)
* [Implicit Grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-4.2)
* [Refresh Tokens](http://tools.ietf.org/html/draft-ietf-oauth-v2-25#section-1.5)
View
228 docs/uberdoc.html
@@ -3029,28 +3029,51 @@
};
})(SyntaxHighlighter);
</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>
-</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">
+</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">
</td><td class="codes"><pre class="brush: clojure">(ns clauth.client
- (:use [clauth.token]))</pre></td></tr><tr><td class="docs"><p>In memory client store</p>
-</td><td class="codes"><pre class="brush: clojure">(def clients
- (atom {})) </pre></td></tr><tr><td class="docs"><p>create a unique client and store it in the client store</p>
+ (:use [clauth.token])
+ (:use [clauth.store]))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defonce client-store (atom (create-memory-store)))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defrecord ClientApplication
+ [client-id client-secret name url])</pre></td></tr><tr><td class="docs"><p>Create new client-application record</p>
+</td><td class="codes"><pre class="brush: clojure">(defn client-app
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? ClientApplication attrs) attrs
+ (instance? java.lang.String attrs) (client-app (cheshire.core/parse-string attrs true))
+ :default (ClientApplication. (attrs :client-id) (attrs :client-secret) (attrs :name) (attrs :url))))
+ ([] (client-app nil nil))
+ ([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>
+</td><td class="codes"><pre class="brush: clojure">(defn reset-client-store!
+ []
+ (reset-store! @client-store))</pre></td></tr><tr><td class="docs"><p>Find OAuth token based on the token string</p>
+</td><td class="codes"><pre class="brush: clojure">(defn fetch-client
+ [t]
+ (client-app (fetch @client-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given ClientApplication and return it.</p>
+</td><td class="codes"><pre class="brush: clojure">(defn store-client
+ [t]
+ (store @client-store :client-id t))</pre></td></tr><tr><td class="docs"><p>Sequence of clients</p>
+</td><td class="codes"><pre class="brush: clojure">(defn clients
+ []
+ (entries @client-store))</pre></td></tr><tr><td class="docs"><p>create a unique client and store it in the client store</p>
</td><td class="codes"><pre class="brush: clojure">(defn register-client
- [ attrs ]
- (let [client-id (generate-token)
- record (assoc attrs :client-id client-id :client-secret (generate-token))]
- (do
- (swap! clients assoc client-id record)
- record)))</pre></td></tr><tr><td class="docs"><p>authenticate client application using client<em>id and client</em>secret</p>
+ ([] (register-client nil nil))
+ ([ name url ]
+ (let [client (client-app name url)]
+ (store-client client))))</pre></td></tr><tr><td class="docs"><p>authenticate client application using client<em>id and client</em>secret</p>
</td><td class="codes"><pre class="brush: clojure">(defn authenticate-client
[client-id client-secret]
- (if-let [ client (@clients client-id)]
- (if (= client-secret (client :client-secret))
+ (if-let [ client (fetch-client client-id)]
+ (if (= client-secret (:client-secret client))
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">
</td><td class="codes"><pre class="brush: clojure">(ns clauth.demo
(:use [clauth.middleware])
(:use [clauth.endpoints])
(:use [clauth.client])
(:use [clauth.token])
+ (:use [clauth.store.redis])
+ (:require [redis.core :as redis])
(:use [ring.adapter.jetty])
(:use [ring.middleware.cookies])
(: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 @@
(str &quot;{\&quot;token\&quot;:\&quot;&quot; (str token) &quot;\&quot;}&quot;)
&quot;{}&quot;)})</pre></td></tr><tr><td class="docs">
</td><td class="codes"><pre class="brush: clojure">(defn routes [req]
- (do
- (prn req)
- (prn @clients)
(if (= &quot;/token&quot; (req :uri))
((token-handler) req )
- ((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>
+ ((require-bearer-token! handler) req)))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defn wrap-redis-store [app]
+ (fn [req]
+ (redis/with-server
+ {:host &quot;127.0.0.1&quot;
+ :port 6379
+ :db 14
+ }
+ (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>
<p> The function passed in this example to require-bearer-token is a clojure set containing the single value "secret".</p>
<p> You could instead use a hash for a simple in memory token database or a function querying a database.</p>
</td><td class="codes"><pre class="brush: clojure">(defn -main
[]
- (let [client (register-client {:name &quot;My App&quot;})]
- (println &quot;App starting up:&quot;)
- (prn client)
- (println &quot;Token endpoint /token&quot;)
- (println)
- (println &quot;Fetch a Client Credentialed access token:&quot;)
- (println)
- (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)]) )
- (println)
- (run-jetty (-&gt; routes
- (wrap-params)
- (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">
+ (do
+ (reset! token-store (create-redis-store &quot;tokens&quot;))
+ (reset! client-store (create-redis-store &quot;clients&quot;))
+ (redis/with-server
+ {:host &quot;127.0.0.1&quot;
+ :port 6379
+ :db 14
+ }
+ (let [client ( or (first (clients)) (register-client))]
+ (println &quot;App starting up:&quot;)
+ (prn client)
+ (println &quot;Token endpoint /token&quot;)
+ (println)
+ (println &quot;Fetch a Client Credentialed access token:&quot;)
+ (println)
+ (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)]) )
+ (println)
+ (run-jetty (-&gt; routes
+ (wrap-params)
+ (wrap-cookies)
+ (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">
</td><td class="codes"><pre class="brush: clojure">(ns clauth.endpoints
(:use [clauth.token])
(:use [clauth.client])
+ (:use [clauth.user])
(:use [cheshire.core])
(: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>
@@ -3139,6 +3177,13 @@
req
authenticator
(fn [req client] (respond-with-new-token client client))))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defmethod token-request-handler &quot;password&quot; [req authenticator]
+ (client-authenticated-request
+ req
+ authenticator
+ (fn [req client] (if-let [user (authenticate-user ((req :params) &quot;username&quot;) ((req :params) &quot;password&quot;))]
+ (respond-with-new-token client client)
+ (error-response &quot;invalid_grant&quot;)))))</pre></td></tr><tr><td class="docs">
</td><td class="codes"><pre class="brush: clojure">(defmethod token-request-handler :default [req authenticator]
(error-response &quot;unsupported_grant_type&quot;))</pre></td></tr><tr><td class="docs">
</td><td class="codes"><pre class="brush: clojure">(defn token-handler
@@ -3148,7 +3193,7 @@
(fn [req]
(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">
</td><td class="codes"><pre class="brush: clojure">(ns clauth.middleware
- (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>
+ (: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>
<p> A find-token function is passed the token and returns a clojure map describing the subject of the token.</p>
@@ -3205,12 +3250,55 @@
(fn [req]
(if (req :access-token)
(app req)
- (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">
+ (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">
+</td><td class="codes"><pre class="brush: clojure">(ns clauth.store)</pre></td></tr><tr><td class="docs"><p>Store OAuthTokens</p>
+</td><td class="codes"><pre class="brush: clojure">(defprotocol Store
+ (fetch [ e k ] &quot;Find the item based on a key.&quot;)
+ (store [ e key_param item ] &quot;Store the given map using the value of the keyword key_param and return it.&quot;)
+ (entries [e] &quot;sequence of entries&quot;)
+ (reset-store! [e] &quot;clear all entries&quot;))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defrecord MemoryStore [data]
+ Store
+ (fetch [this t] (@data t))
+ (store [this key_param item]
+ (do
+ (swap! data assoc (key_param item) item)
+ item))
+ (entries [this] (or (vals @data) []))
+ (reset-store! [this] (reset! data {})))</pre></td></tr><tr><td class="docs"><p>Create a memory token store</p>
+</td><td class="codes"><pre class="brush: clojure">(defn create-memory-store
+ ([] (create-memory-store {}))
+ ([data]
+ (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">
+</td><td class="codes"><pre class="brush: clojure">(ns clauth.store.redis
+ (:use [clauth.store])
+ (:require [redis.core :as redis])
+ (:require [cheshire.core]))</pre></td></tr><tr><td class="docs"><p>get namespaced list of keys</p>
+</td><td class="codes"><pre class="brush: clojure">(defn namespaced-keys
+ [namespace]
+ (redis/keys (str namespace &quot;/*&quot;)))</pre></td></tr><tr><td class="docs"><p>get all items in namespace</p>
+</td><td class="codes"><pre class="brush: clojure">(defn all-in-namespace
+ [namespace]
+ (let [ks (remove nil? (namespaced-keys namespace))]
+ (if (not-empty ks) (apply redis/mget ks)))) </pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defrecord RedisStore [namespace]
+ Store
+ (fetch [this t] (if-let [j (redis/get (str namespace &quot;/&quot; t))]
+ (cheshire.core/parse-string j true)))
+ (store [this key_param item]
+ (do
+ (redis/set (str namespace &quot;/&quot; (key_param item)) (cheshire.core/generate-string item))
+ item))
+ (entries [this] (map #( cheshire.core/parse-string % true) (all-in-namespace namespace) ))
+ (reset-store! [this] (redis/flushdb)))</pre></td></tr><tr><td class="docs"><p>Create a redis store</p>
+</td><td class="codes"><pre class="brush: clojure">(defn create-redis-store
+ ([namespace]
+ (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">
</td><td class="codes"><pre class="brush: clojure">(ns clauth.token
+ (:use [clauth.store])
(:require [crypto.random])
- (:require [clj-time.core :as time]))</pre></td></tr><tr><td class="docs"><p>In memory token store</p>
-</td><td class="codes"><pre class="brush: clojure">(def tokens
- (atom {})) </pre></td></tr><tr><td class="docs"><p>Check if object is valid</p>
+ (:require [clj-time.core :as time])
+ (:require [cheshire.core]))</pre></td></tr><tr><td class="docs"><p>Check if object is valid</p>
</td><td class="codes"><pre class="brush: clojure">(defprotocol Expirable
(is-valid? [ t ] &quot;is the object still valid&quot;))</pre></td></tr><tr><td class="docs">
</td><td class="codes"><pre class="brush: clojure">(extend-protocol Expirable clojure.lang.IPersistentMap
@@ -3235,23 +3323,83 @@
<li>object - An optional object authorized. Eg. account, photo</li>
</ul>
</td><td class="codes"><pre class="brush: clojure">(defn oauth-token
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? OAuthToken attrs) attrs
+ (instance? java.lang.String attrs) (oauth-token (cheshire.core/parse-string attrs true))
+ :default (OAuthToken. (attrs :token) (attrs :client) (attrs :subject) (attrs :expires) (attrs :scope) (attrs :object))))
([client subject]
(oauth-token client subject nil nil nil))
([client subject expires scope object]
(oauth-token (generate-token) client subject expires scope object))
([token client subject expires scope object]
- (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>
+ (OAuthToken. token client subject expires scope object)))</pre></td></tr><tr><td class="docs">
+</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>
+</td><td class="codes"><pre class="brush: clojure">(defn reset-token-store!
+ []
+ (reset-store! @token-store))</pre></td></tr><tr><td class="docs"><p>Find OAuth token based on the token string</p>
+</td><td class="codes"><pre class="brush: clojure">(defn fetch-token
+ [t]
+ (oauth-token (fetch @token-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given OAuthToken and return it.</p>
+</td><td class="codes"><pre class="brush: clojure">(defn store-token
+ [t]
+ (store @token-store :token t))</pre></td></tr><tr><td class="docs"><p>Sequence of tokens</p>
+</td><td class="codes"><pre class="brush: clojure">(defn tokens
+ []
+ (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>
</td><td class="codes"><pre class="brush: clojure">(defn create-token
([client subject]
(create-token (oauth-token client subject)))
([client subject expires scope object]
(create-token (oauth-token client subject expires scope object)))
([ token ]
- (do
- (swap! tokens assoc (:token token) token)
- token)))</pre></td></tr><tr><td class="docs"><p>return a token from the store if it is valid.</p>
+ (store-token token)))</pre></td></tr><tr><td class="docs"><p>return a token from the store if it is valid.</p>
</td><td class="codes"><pre class="brush: clojure">(defn find-valid-token
- [token]
- (if-let [t (@tokens token)]
- (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;
+ [t]
+ (if-let [token (fetch-token t)]
+ (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">
+</td><td class="codes"><pre class="brush: clojure">(ns clauth.user
+ (:use [clauth.store])
+ (:import [org.mindrot.jbcrypt BCrypt]))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defonce user-store (atom (create-memory-store)))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defrecord User
+ [login password name url])</pre></td></tr><tr><td class="docs"><p>Perform BCrypt hash of password</p>
+</td><td class="codes"><pre class="brush: clojure">(defn bcrypt
+ [password]
+ (BCrypt/hashpw password (BCrypt/gensalt)))</pre></td></tr><tr><td class="docs"><p>Verify that candidate password matches the hashed bcrypted password</p>
+</td><td class="codes"><pre class="brush: clojure">(defn valid-password?
+ [candidate hashed]
+ (BCrypt/checkpw candidate hashed))</pre></td></tr><tr><td class="docs"><p>Create new user record</p>
+</td><td class="codes"><pre class="brush: clojure">(defn new-user
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? User attrs) attrs
+ (instance? java.lang.String attrs) (new-user (cheshire.core/parse-string attrs true))
+ :default (User. (attrs :login) (attrs :password) (attrs :name) (attrs :url))))
+ ([ login password ] (new-user login password nil nil))
+ ([ 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>
+</td><td class="codes"><pre class="brush: clojure">(defn reset-user-store!
+ []
+ (reset-store! @user-store))</pre></td></tr><tr><td class="docs"><p>Find user based on login</p>
+</td><td class="codes"><pre class="brush: clojure">(defn fetch-user
+ [t]
+ (new-user (fetch @user-store t)))</pre></td></tr><tr><td class="docs"><p>Store the given User and return it.</p>
+</td><td class="codes"><pre class="brush: clojure">(defn store-user
+ [t]
+ (store @user-store :login t))</pre></td></tr><tr><td class="docs"><p>Sequence of users</p>
+</td><td class="codes"><pre class="brush: clojure">(defn users
+ []
+ (entries @user-store))</pre></td></tr><tr><td class="docs"><p>create a unique user and store it in the user store</p>
+</td><td class="codes"><pre class="brush: clojure">(defn register-user
+ ([ login password ] (register-user login password nil nil))
+ ([ login password name url ]
+ (let [user (new-user login password name url)]
+ (store-user user))))</pre></td></tr><tr><td class="docs"><p>authenticate user application using login and password</p>
+</td><td class="codes"><pre class="brush: clojure">(defn authenticate-user
+ [login password]
+ (if-let [ user (fetch-user login)]
+ (if (valid-password? password (:password user))
+ 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;
SyntaxHighlighter.all()</script></body></html>
View
8 src/clauth/client.clj
@@ -10,6 +10,12 @@
(defn client-app
"Create new client-application record"
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? ClientApplication attrs) attrs
+ (instance? java.lang.String attrs) (client-app (cheshire.core/parse-string attrs true))
+ :default (ClientApplication. (attrs :client-id) (attrs :client-secret) (attrs :name) (attrs :url))))
([] (client-app nil nil))
([name url] (ClientApplication. (generate-token) (generate-token) name url)))
@@ -21,7 +27,7 @@
(defn fetch-client
"Find OAuth token based on the token string"
[t]
- (fetch @client-store t))
+ (client-app (fetch @client-store t)))
(defn store-client
"Store the given ClientApplication and return it."
View
46 src/clauth/demo.clj
@@ -3,6 +3,8 @@
(:use [clauth.endpoints])
(:use [clauth.client])
(:use [clauth.token])
+ (:use [clauth.store.redis])
+ (:require [redis.core :as redis])
(:use [ring.adapter.jetty])
(:use [ring.middleware.cookies])
(:use [ring.middleware.params]))
@@ -23,22 +25,42 @@
((token-handler) req )
((require-bearer-token! handler) req)))
+(defn wrap-redis-store [app]
+ (fn [req]
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 14
+ }
+ (app req))))
+
(defn -main
"start web server. This first wraps the request in the cookies and params middleware, then requires a bearer token.
The function passed in this example to require-bearer-token is a clojure set containing the single value \"secret\".
You could instead use a hash for a simple in memory token database or a function querying a database."
[]
- (let [client (register-client)]
- (println "App starting up:")
- (prn client)
- (println "Token endpoint /token")
- (println)
- (println "Fetch a Client Credentialed access token:")
- (println)
- (println "curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u " (clojure.string/join ":" [(:client-id client) (:client-secret client)]) )
- (println)
- (run-jetty (-> routes
- (wrap-params)
- (wrap-cookies)) {:port 3000})))
+ (do
+
+ (reset! token-store (create-redis-store "tokens"))
+ (reset! client-store (create-redis-store "clients"))
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 14
+ }
+ (let [client ( or (first (clients)) (register-client))]
+ (println "App starting up:")
+ (prn client)
+ (println "Token endpoint /token")
+ (println)
+ (println "Fetch a Client Credentialed access token:")
+ (println)
+ (println "curl http://127.0.0.1:3000/token -d grant_type=client_credentials -u " (clojure.string/join ":" [(:client-id client) (:client-secret client)]) )
+ (println)
+
+ (run-jetty (-> routes
+ (wrap-params)
+ (wrap-cookies)
+ (wrap-redis-store)) {:port 3000})))))
View
2 src/clauth/middleware.clj
@@ -1,5 +1,5 @@
(ns clauth.middleware
- (use [clauth.token]))
+ (:use [clauth.token]))
(defn wrap-bearer-token
"Wrap request with a OAuth2 bearer token as defined in http://tools.ietf.org/html/draft-ietf-oauth-v2-bearer-08.
View
7 src/clauth/store/redis.clj
@@ -12,9 +12,8 @@
(defn all-in-namespace
"get all items in namespace"
[namespace]
- (let [ks (remove nil? (namespaced-keys namespace))
- gt `(redis.core/mget ~@ks)]
- (if (not-empty ks) (eval gt)))) ; This seems wrong to me. Is this the best way of doing it?
+ (let [ks (remove nil? (namespaced-keys namespace))]
+ (if (not-empty ks) (apply redis/mget ks))))
(defrecord RedisStore [namespace]
@@ -24,7 +23,7 @@
(store [this key_param item]
(do
- (redis/set (str namespace "/" (item key_param)) (cheshire.core/generate-string item))
+ (redis/set (str namespace "/" (key_param item)) (cheshire.core/generate-string item))
item)
)
(entries [this] (map #( cheshire.core/parse-string % true) (all-in-namespace namespace) ))
View
16 src/clauth/token.clj
@@ -1,7 +1,8 @@
(ns clauth.token
(:use [clauth.store])
(:require [crypto.random])
- (:require [clj-time.core :as time]))
+ (:require [clj-time.core :as time])
+ (:require [cheshire.core]))
(defprotocol Expirable
"Check if object is valid"
@@ -34,9 +35,12 @@
* scope - An optional vector of scopes authorized
* object - An optional object authorized. Eg. account, photo"
- ([attrs]
- (OAuthToken. (attrs "token") (attrs "client") (attrs "subject") (attrs "expires") (attrs "scope") (attrs "object"))
- )
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? OAuthToken attrs) attrs
+ (instance? java.lang.String attrs) (oauth-token (cheshire.core/parse-string attrs true))
+ :default (OAuthToken. (attrs :token) (attrs :client) (attrs :subject) (attrs :expires) (attrs :scope) (attrs :object))))
([client subject]
(oauth-token client subject nil nil nil)
)
@@ -58,7 +62,7 @@
(defn fetch-token
"Find OAuth token based on the token string"
[t]
- (fetch @token-store t))
+ (oauth-token (fetch @token-store t)))
(defn store-token
"Store the given OAuthToken and return it."
@@ -68,7 +72,7 @@
(defn tokens
"Sequence of tokens"
[]
- (entries @token-store))
+ (map oauth-token (entries @token-store)))
(defn create-token
"create a unique token and store it in the token store"
View
9 src/clauth/user.clj
@@ -20,6 +20,13 @@
(defn new-user
"Create new user record"
+ ([attrs] ; Swiss army constructor. There must be a better way.
+ (cond
+ (nil? attrs) nil
+ (instance? User attrs) attrs
+ (instance? java.lang.String attrs) (new-user (cheshire.core/parse-string attrs true))
+ :default (User. (attrs :login) (attrs :password) (attrs :name) (attrs :url))))
+
([ login password ] (new-user login password nil nil))
([ login password name url ] (User. login (bcrypt password) name url)))
@@ -31,7 +38,7 @@
(defn fetch-user
"Find user based on login"
[t]
- (fetch @user-store t))
+ (new-user (fetch @user-store t)))
(defn store-user
"Store the given User and return it."
View
60 test/clauth/test/store/redis.clj
@@ -1,5 +1,8 @@
(ns clauth.test.store.redis
(:use [clauth.store])
+ (:use [clauth.token])
+ (:use [clauth.client])
+ (:use [clauth.user])
(:use [clauth.store.redis])
(:require [redis.core :as redis])
(:use [clojure.test])
@@ -29,4 +32,59 @@
(is (= [] (entries st)))
(is (nil? (fetch st "item"))))))))
-
+
+ (deftest token-store-implementation
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 15
+ }
+ (reset! token-store (create-redis-store "tokens"))
+ (reset-token-store!)
+ (is (= 0 (count (tokens))) "starts out empty")
+ (let
+ [record (oauth-token "my-client" "my-user")]
+ (is (nil? (fetch-token (:token record))))
+ (do
+ (store-token record)
+ (is (= record (fetch-token (:token record))))
+ (is (= 1 (count (tokens))) "added one"))))
+ (reset! token-store (create-memory-store)))
+
+ (deftest client-store-implementation
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 15
+ }
+ (reset! client-store (create-redis-store "clients"))
+ (reset-client-store!)
+ (is (= 0 (count (clients))) "starts out empty")
+ (let
+ [ record (client-app)]
+ (is (nil? (fetch-client (:client-id record))))
+ (do
+ (store-client record)
+ (is (= record (fetch-client (:client-id record))))
+ (is (= 1 (count (clients))) "added one"))))
+ (reset! client-store (create-memory-store)))
+
+
+ (deftest user-store-implementation
+ (redis/with-server
+ {:host "127.0.0.1"
+ :port 6379
+ :db 15
+ }
+ (reset! user-store (create-redis-store "users"))
+ (reset-user-store!)
+ (is (= 0 (count (users))) "starts out empty")
+ (let
+ [ record (new-user "john@example.com" "password")]
+ (is (nil? (fetch-user "john@example.com")))
+ (do
+ (store-user record)
+ (is (= record (fetch-user "john@example.com")))
+ (is (= 1 (count (users))) "added one"))))
+ (reset! user-store (create-memory-store)))
+

0 comments on commit ba7a6f3

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