Permalink
Browse files

OMG documentation

  • Loading branch information...
1 parent b21cc6f commit cfe257781b4a5069f29801ab43587f6aedcdffd0 @cemerick committed Apr 6, 2012
Showing with 281 additions and 81 deletions.
  1. +247 −55 README.md
  2. +18 −7 { → docs}/workflow.graffle
  3. BIN docs/workflow.png
  4. +4 −7 project.clj
  5. +12 −12 test/test_friend/mock_app.clj
View
@@ -1,8 +1,8 @@
# Friend
-An authentication and authorization library for
+An extensible authentication and authorization library for
[Clojure](http://clojure.org) [Ring](http://github.com/mmcgrana/ring)
-applications and services.
+applications and services.
```
Picking up his staff he stood before the rock and said in a clear voice:
@@ -25,103 +25,294 @@ suspicious days. Those were happier times. Now let us go!"
```
— J.R.R. Tolkien, _Lord of the Rings_
-## "Installation"
+## Overview
-## Usage
+Friend is intended to provide a foundation for addressing
+all of the security concerns associated with web apps:
-### Features
+* channel security (restricting certain resources to a particular
+ protocol/scheme, usually HTTPS)
+* user agent authentication; Friend currently includes support for form,
+ HTTP Basic, and OpenId authentication, and makes it easy to:
+ * implement and use other workflows
+ * integrate app-specific user-credential checks
+* role-based authentication
+ * optionally uses Clojure's ad-hoc hierarchies to model hierarchical
+ roles
+* `su` capabilities (a.k.a. "log in as"), enabling administrators to
+ easily take on user identities for debugging or support purposes (**in
+progress**)
+* and the creature comforts:
+ * Ring middlewares for configuring and defining the scopes of
+ authentication, authorization, and channel security
+ * Macros to clearly demarcate the scope of authentication and
+ authorization within code that is "below" the level of Ring handlers
+ where you can't use middlewares.
+ * A reasonable Clojure API around the jbcrypt library for hashing
+ sensitive bits.
+ * Enables DRY routes and configuration, e.g. no need to configure your
+ routes in Compojure or Noir or Moustache, and separately specify
+ which routes fall under the jurisdiction of Friend's security machinery
+ * Purely functional in nature: authentications, roles, and session
+ data are obtained, retained, and passed around as good ol'
+ persistent data structures (just as Ring intended). No stateful session
+ or context is ever bashed in place, making it easier to reason about
+ what's going on.
+
+### Why?
+
+Nothing like Friend exists, and it needs to. Securing Ring applications
+and services is (charitably speaking) a PITA right now, with everyone
+rolling their own, or starting with relatively low-level middlewares and
+frameworks. This will never do. Serious web applications need to take
+security seriously, and need to readily interoperate with all sorts of
+authentication mechanisms that have come to litter the web as well as
+internal networks.
+
+Friend has been built with one eye on a number of frameworks.
+
+* [warden](https://github.com/hassox/warden/wiki)
+* [spring-security](http://static.springsource.org/spring-security/)
+* [everyauth](https://github.com/bnoguchi/everyauth)
+* [omniauth](https://github.com/intridea/omniauth)
+* [sandbar](https://github.com/brentonashworth/sandbar)
+
+### Status
+
+Friend is brand-spanking-new. It's also obviously involved in security
+matters. While it's hardly untested, and _is_ in use in production,
+it's obviously not seen the kind of beating and vetting that established
+security libraries and frameworks have had (i.e. spring-security, JAAS
+stuff, etc).
+
+So, proceed happily, but mindfully. Only with your help will we have a
+widely-tested Ring application security library.
+
+### Known issues
+
+* Configuration keys need a bit of tidying, especially for those that
+ can/should apply to multiple authorization workflows. Fixes for such
+things will break the existing API.
+* the `su` mechanism is in-progress
+* the OpenId authentication workflow needs to be broken out into a
+ separate project so that those who aren't using it don't suffer its
+transitive dependencies. (The form and HTTP Basic workflows are
+dependency-free, and will likely remain here.)
+* …surely there's more. File issues.
+
+## "Installation"
-#### Channel security
+Friend is available in Clojars. Add this `:dependency` to your Leiningen
+`project.clj`:
```clojure
-(use '[cemerick.friend :only (requires-scheme *default-scheme-ports*)])
+[com.cemerick/friend "0.0.1"]
+```
-(def https-routes (requires-scheme routes :https))
+Or, add this to your Maven project's `pom.xml`:
-(def http-routes (requires-scheme routes :http))
+```xml
+<repository>
+ <id>clojars</id>
+ <url>http://clojars.org/repo</url>
+</repository>
-(def custom-https-port-routes (requires-scheme routes :https {:https 8443}))
+<dependency>
+ <groupId>com.cemerick</groupId>
+ <artifactId>friend</artifactId>
+ <version>0.0.1</version>
+</dependency>
+```
-(binding [*default-scheme-ports* {:http 8080 :https 8443}]
- (def http-routes (requires-scheme routes :http))
- (def https-routes (requires-scheme routes :https)))
+Friend is compatible with Clojure 1.2.0 - 1.4.0.
+
+## Usage
+
+There is a fairly ornate Ring application
+[here](test/test_friend/mock_app.clj) that is the basis for Friend's
+functional tests that you can look at. That's likely a little hard to
+navigate though, so a simpler introduction is worthwhile.
+
+Here's probably the most self-contained Friend usage possible:
+
+```clojure
+(ns your.ring.app
+ (:require [cemerick.friend :as friend]
+ (cemerick.friend [workflows :as workflows]
+ [credentials :as creds])))
+
+; a dummy in-memory user "database"
+(def users {"root" {:username "root"
+ :password (creds/hash-bcrypt "admin_password")
+ :roles #{::admin}}
+ "jane" {:username "jane"
+ :password (creds/hash-bcrypt "user_password")
+ :roles #{::user}}})
+
+(def ring-app ; ... assemble routes however you like ...
+ )
+
+(def secured-app
+ (->> ring-app
+ (friend/authenticate {:credential-fn (partial creds/bcrypt-credential-fn users)
+ :workflows [(workflows/interactive-form)]})
+ ; ...required Ring middlewares ...
+ ))
```
-#### Credential functions
+We have an unadorned (and unsecured) Ring application (`ring-app`, which
+can be any Ring handler), and then the usage of Friend's `authenticate`
+middleware. This is where all of the authentication work will be done,
+with the return value being a secured Ring application (`secured-app`),
+the requests to which are subject to the configuration provided to
+`authenticate` and the authorization contexts that are defined within
+`ring-app` (which we'll get to shortly).
-Workflows use a credential function to verify the credentials provided to them.
-Credential functions can be specified either as a `:credential-fn` option to
-`cemerick.friend/authenticate`, or often as (an overriding) `:credential-fn`
-option to individual workflow functions.
+There are two key abstractions employed during authentication: workflows
+and credential functions.
-All credential functions take a single argument, a map containing the available
-credentials, and hopefully a `:cemerick.friend/workflow` slot identifying which
-workflow has produced the credential. For example, the default form-based
-authentication credential map looks like this:
+(Note that Friend itself requires some core Ring middlewares: `params`,
+`keyword-params` and `nested-params`. Most workflows will additionally
+require `session` in order to support post-authentication redirection to
+previously-unauthorized resources, retention of tokens and nonces for
+workflows like OpenId and oAuth, etc. HTTP Basic is the only provided
+workflow that does not require `session` middleware.)
+
+### Workflows
+
+Individual authentication methods (e.g., form-based auth, HTTP Basic, OpenID,
+oAuth, etc.) are implemented as _workflows_ in Friend. A workflow is a
+regular Ring handler function, except that a workflow function can _opt_
+to return an _authentication map_ instead of a Ring response if a
+request is authenticated. A diagram may help:
+
+![](https://github.com/cemerick/friend/raw/master/docs/workflow.png)
+
+You can define any number of workflows in a `:workflows` kwarg to
+`authenticate`. Incoming requests are always run through the configured
+workflows prior to potentially being passed along to the secured Ring
+application.
+
+If a workflow returns an authentication map, then the `authenticate`
+middleware will either:
+
+* carry on processing the request if the workflow allows for credentials
+ to be provided in requests to any resource (i.e. HTTP Basic); control
+ of this is entirely up to each workflow, and will be described later.
+* redirect the user agent to a secured resource that it was previously
+ barred from accessing via Friend's authorization machinery
+
+If a workflow returns a Ring response, then that response is sent back
+to the user agent straight away (after some bookkeeping by the
+`authenticate` middleware to preserve session states and such). This
+makes it possible for a workflow to control a "local" dataflow between
+itself, the user agent, and any necessary external authorities (e.g. by
+redirecting a user agent to an OpenId endpoint, performing token
+exchange in the case of oAuth, etc., eventually returning a complete
+authentication map that will allow the user agent to proceed on its
+desired vector).
+
+### Credential functions and authentication maps
+
+Workflows use a _credential function_ to verify the credentials provided
+to them via requests. Credential functions can be specified either as a
+`:credential-fn` option to `cemerick.friend/authenticate`, or often as
+an (overriding) `:credential-fn` option to individual workflow
+functions.
+
+All credential functions take a single argument, a map containing the
+available credentials that additionally contains a
+`:cemerick.friend/workflow` slot identifying which workflow has produced
+the credential. For example, the default form-based authentication
+credential map looks like this:
```clojure
-{:username "" :password "" :cemerick.friend/workflow :form}
+{:username "...", :password "...", :cemerick.friend/workflow :form}
```
HTTP Basic credentials are much the same, but with a workflow value of
`:http-basic`, etc. Different workflows may have significantly different
-credential maps (e.g. an OpenID workflow would not provide username and
-password, but rather a token returned by an OpenID provider).
+credential maps (e.g. an OpenID workflow does not provide username and
+password, but rather a token returned by an OpenID provider along with
+potentially some number of "attributes" like the user's name, email
+address, default language, etc.), and unique credential verification
+requirements (again, contrast the simple username/password verification
+of form or HTTP Basic credentials and OpenId, which, in
+general, when presented with unknown credentials, should _register_ the
+indicated identity rather than verifying it).
+
+In summary, the contract of what exactly must be in the map provided to
+credential functions is entirely at the discretion of each workflow
+function, as is the semantics of the credential function.
If a map of credentials is verified by a credential function, it should return a
_authentication map_ that aggregates all authentication and authorization
information available for the identified user. This map may contain many
entries, depending upon the authentication information that is relevant for the
-workflow in question and the user data relevant to the application:
+workflow in question and the user data relevant to the application, but two entries are priviliged:
* `:identity` (**required**) corresponds with e.g. the username in a form or
- HTTP Basic authentication, an oAuth token, etc.
-* `:roles`, an optional set of values enumerating the roles for which the user
+ HTTP Basic authentication, an oAuth token, etc.; this value _must_ be
+ unique across all users within the application
+* `:roles`, an optional collection of values enumerating the roles for which the user
is authorized.
-If a map of credentials is found to be invalid, the credential function must
-return nil.
+_If a map of credentials is found to be invalid, the credential function must
+return nil._
-#### Authentication retention (or not)
+### Authentication retention (or not)
+### Authorization
+#### Simple vs. hierarchical roles
-#### Logout
+### Channel security
-Logging a user out is accomplished by directing the user to a route that
-has had `cemerick.friend/logout` middleware applied to it:
+_Channel security_ is the redirection of requests for a given resource
+through a specific channel, i.e. requiring that logins or a payment
+workflow is performed over HTTPS instead over HTTP.
+
+`requires-scheme` is Ring middleware that enforces channel security for
+a given Ring handler:
```clojure
-(use '[cemerick.friend :only (logout)])
-(def logout-route (logout logout-handler))
-```
+(use '[cemerick.friend :only (requires-scheme *default-scheme-ports*)])
+; HTTP requests routed to https-routes will be redirected to the
+; corresponding HTTPS URL on the default port
+(def https-routes (requires-scheme routes :https))
-`:cemerick.friend/auth` entry from their session. You can do this manually, or
-use the `logout` middleware to ensure that that entry
+; HTTP requests routed to custom-https-port-routes be redirected to the
+; corresponding HTTPS URL on port 8443
+(def custom-https-port-routes (requires-scheme routes :https {:https 8443}))
+; alternative default ports for HTTP and HTTPS may be bound dynamically
+; to simplify configuration of multiple routes
+(binding [*default-scheme-ports* {:http 8080 :https 8443}]
+ (def http-routes (requires-scheme routes :http))
+ (def https-routes (requires-scheme routes :https)))
+```
-#### Workflows
+Note that `requires-scheme` is unrelated to the authentication,
+authorization, etc facilities in Friend, and can be used in isolation.
+
+## TODO
-Individual authentication methods (e.g., form-based auth, HTTP Basic, OpenID,
-oAuth, etc.) are implemented as _workflows_ in Friend. A workflow is a regular
-Ring handler function, except that, rather than potentially returning a Ring
-response, a workflow function can opt to return an authentication map if a
-request has been authenticated fully.
-
-* salts
-* crypts/ciphers
-* remember-me
-* role-based authentication
* run-as/sudo/multi-user login
+* alternative hashing methods and salting strategies
+ * good to encourage bcrypt, but existing apps have tons of sha-X, md5,
+ etc passwords
+* remember-me?
* interop
* recognize / provide access to servlet principal
* spring-security
-
-## TODO
-
-* use hierarchies and `isa?` instead of sets of simple values for
- authorization
+* make `:cemerick.friend/workflow` metadata
+* documentation
+ * authentication map metadata:
+ * `:type`
+ * `::friend/workflow`
+ * `::friend/transient`
+ * `::friend/redirect-on-auth?`
## Need Help?
@@ -134,3 +325,4 @@ like to contribute patches.
Copyright ©2012 [Chas Emerick](http://cemerick.com)
Distributed under the Eclipse Public License, the same as Clojure.
+Please see the `epl-v10.html` file at the top level of this repo.
Oops, something went wrong.

0 comments on commit cfe2577

Please sign in to comment.