Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
mbuczko committed Dec 19, 2017
2 parents a7a4b77 + ad7f3ce commit f38750c
Show file tree
Hide file tree
Showing 31 changed files with 417 additions and 258 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ dist/
*.paw
*-local.edn
gpg.edn
boot.properties
105 changes: 19 additions & 86 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ Cerber uses glorious [mount](https://github.com/tolitius/mount) to set up everyt
:defined []}
:clients {:store :sql
:defined []}
:scopes #{}
:landing-url "/"
:realm "http://defunkt.pl"
:endpoints {:authentication "/login"
Expand All @@ -83,17 +82,6 @@ Words of explanation:
* ```jdbc-pool``` (optional) is a sql database pool specification (look at [conman](https://github.com/luminus-framework/conman) for more info) for sql-based stores.
* ```endpoints``` (optional) should reflect cerber's routes to authentication and access approve/refuse endpoints.
* ```realm``` (required) is a realm presented in WWW-Authenticate header in case of 401/403 http error codes
* ```scopes``` (required) available set of [scopes](https://www.oauth.com/oauth2-servers/scope/defining-scopes/) for oauth2 clients.

#### Scopes

Scopes are configured as a set of unique strings like ```"user"```, ```"photos:read"``` or ```"profile:write"``` which may be structurized in kind of hierarchy.
For example one can define scopes as ```#{"photos" "photos:read" "photos:write"}``` which grants _read_ and _write_ permission to imaginary photos resoure and
a _photos_ permission which is a parent of _photos:read_ and _photos:write_ and implicitly includes both permissions.

Cerber also normalizes scope requests, so when client asks for ```#{"photos" "photos:read"}``` scopes, it's been simplified to ```#{"photos"}``` only.

Note, it's perfectly valid to have an empty set of scopes as they are optional in OAuth2 spec.

#### Users and clients

Expand All @@ -116,14 +104,28 @@ To configure users and/or clients as a part of environment, it's enough to list
:info "Default client"
:redirects ["http://localhost"]
:grants ["authorization_code" "password"]
:scopes ["photos:read" "photos:write"]
:scopes ["photo:read" "photo:write"]
:approved? true}]}}
```

#### Authorization Grant Types

Grant types allowed:

* ```authorization_code``` for [Authorization Code Grant](https://tools.ietf.org/html/rfc6749#section-4.1)
* ```token``` for [Implict Code Grant](https://tools.ietf.org/html/rfc6749#section-4.2)
* ```password``` for [Resource Owner Password Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.3)
* ```client_credentials``` for [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4)

#### Scopes

Client scopes are configured as a vector of unique strings like ```"user"```, ```"photo:read"``` or ```"profile:write"``` which may be structurized in kind of hierarchy.
For example one can define scopes as ```#{"photo" "photo:read" "photo:write"}``` which grants _read_ and _write_ permission to imaginary photo resoure and
a _photo_ permission which is a parent of _photo:read_ and _photo:write_ and implicitly includes both permissions.

Configuration may be a global one or specific to given environment (local / test / prod).
Cerber also normalizes scope requests, so when client asks for ```#{"photo" "photo:read"}``` scopes, it's been simplified to ```#{"photo"}``` only.

When speaking of environments...
Note, it's perfectly valid to have an empty set of scopes as they are optional in OAuth2 spec.

### Environments

Expand Down Expand Up @@ -228,78 +230,9 @@ This simply starts the Cerber system by mounting all stores and populates them w

## API

API functions are all grouped in ```cerber.oauth2.core``` namespace and allow to manipulate with clients and tokens at higher level.

### clients

```(create-client [info redirects & [grants scopes approved?]])```

Used to create new OAuth client, where:
- info is a non-validated info string (typically client's app name or URL to client's homepage)
- redirects is a validated vector of approved redirect-uris. Note that for security reasons redirect-uri provided with token request should match one of these entries.
- grants is an optional vector of allowed grants: "authorization_code", "token", "password" or "client_credentials". if nil - all grants are allowed.
- scopes is an optional vector of OAuth scopes that client may request an access to
- approved? is an optional parameter deciding whether client should be auto-approved or not. It's false by default which means that client needs user's approval when requesting access to protected resource.

Example:

```clojure
(require '[cerber.oauth2.core :as c])

(c/create-client "http://defunkt.pl"
["http://defunkt.pl/callback"]
["authorization_code" "password"]
["photos:read" "photos:list"]
true)
```

Each generated client has its own random client-id and a secret which both are used in OAuth flow.
Important thing is to keep the secret codes _really_ secret! Both client-id and secret authorize
client instance and it might be harmful to let attacker know what's your client's secret code is.

```(find-client [client-id])```

Looks up for client with given identifier.

```(delete-client [client])```

Removes client from store. Note that together with client all its access- and refresh-tokens are revoked as well.

### users

```(create-user [login name email password roles permissions enabled?])```

Creates new user with given login, descriptive name, user's email, password (stored as hash), roles and permissions.
```enabled?``` argument decides whether users is enabled by default (can authenticate) or not.

```(find-user [login])```

Looks up for a user with given login.

```(delete-user [login])```

Removes from store user with given login.

```(modify-user-status [login enabled?])```

Decides whether to enable or disable user with given login.


### tokens

```(find-tokens-by-client [client])```

Returns list of non-expirable refresh-tokens generated for given client.

```(find-tokens-by-user [user])```

Returns list of non-expirable refresh-tokens generated for clients operating on behalf of given user.

```(revoke-tokens [client])```

```(revoke-tokens [client login])```
API functions are all grouped in ```cerber.oauth2.core``` namespace and allow to manipulate with clients, users and tokens at higher level.

Revokes all access- and refresh-tokens bound with given client (and optional user's login).
Full documentation can be found [here](http://api.defunkt.pl/cerber/api/cerber.oauth2.core.html).

### errors

Expand Down
6 changes: 0 additions & 6 deletions boot.properties

This file was deleted.

21 changes: 15 additions & 6 deletions build.boot
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
:resource-paths #{"resources"}
:directories #{"config"}
:dependencies '[[org.clojure/clojure "1.8.0" :scope "provided"]
[com.taoensso/carmine "2.16.0" :scope "test"]
[com.taoensso/carmine "2.16.0"]
[org.mindrot/jbcrypt "0.4"]
[mbuczko/boot-flyway "0.1.1" :scope "test"]
[adzerk/bootlaces "0.1.13" :scope "test"]
Expand All @@ -13,21 +13,21 @@
[org.postgresql/postgresql "42.1.4" :scope "test"]
[funcool/boot-codeina "0.1.0-SNAPSHOT" :scope "test"]
[ring/ring-defaults "0.3.1"]
[midje "1.8.3" :scope "test"]
[midje "1.9.0" :scope "test"]
[peridot "0.5.0" :scope "test"]
[compojure "1.6.0" :scope "test"]
[http-kit "2.2.0" :scope "test"]
[cprop "0.1.11"]
[conman "0.6.8"]
[conman "0.7.4"]
[mount "0.1.11"]
[crypto-random "1.2.0"]
[selmer "1.11.1"]
[selmer "1.11.3"]
[failjure "1.2.0"]
[ring-anti-forgery "0.3.0"]
[ring-middleware-format "0.7.2"]
[digest "1.4.6"]])

(def +version+ "0.2.0")
(def +version+ "0.3.0")

;; to check the newest versions:
;; boot -d boot-deps ancient
Expand Down Expand Up @@ -76,12 +76,21 @@
:title "Cerber"
:sources #{"src"}
:description "OAuth2 provider for Clojure"
:reader :clojure
:src-uri "http://github.com/mbuczko/cerber-oauth2-provider/blob/master/"
:exclude ['cerber.config
'cerber.db
'cerber.error
'cerber.form
'cerber.handlers
'cerber.helpers
'cerber.middleware
'cerber.migration]}
'cerber.stores.authcode
'cerber.stores.client
'cerber.stores.user
'cerber.stores.token
'cerber.stores.session
'cerber.oauth2.response
'cerber.oauth2.context
'cerber.oauth2.authorization]}
migrate {:jdbc-url "jdbc:postgresql://localhost:5432/template1?user=postgres"})
1 change: 0 additions & 1 deletion config/cerber-test.edn
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@
:tokens {:store :sql :valid-for 180}
:clients {:store :sql}
:users {:store :sql}
:scopes {"photo:read" "photo:write"}
:landing-url "http://localhost/"}
1 change: 0 additions & 1 deletion resources/cerber.edn
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{:landing-url "http://localhost"
:realm "http://localhost"
:scopes #{}
:authcodes {:store :in-memory :valid-for 600}
:sessions {:store :in-memory :valid-for 600}
:tokens {:store :in-memory :valid-for 600}
Expand Down
10 changes: 9 additions & 1 deletion resources/db/cerber/clients.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ select * from clients where id=:id

-- :name insert-client :! :1
-- :doc Inserts new client
insert into clients (id, secret, info, redirects, scopes, grants, approved, created_at) values (:id, :secret, :info, :redirects, :scopes, :grants, :approved?, :created-at)
insert into clients (id, secret, info, redirects, scopes, grants, approved, enabled, created_at, activated_at) values (:id, :secret, :info, :redirects, :scopes, :grants, :approved?, :enabled?, :created-at, :activated-at)

-- :name enable-client :! :1
-- :doc Enables client
update clients set enabled=true, activated_at=:activated-at where id=:id

-- :name disable-client :! :1
-- :doc Disables client
update clients set enabled=false, blocked_at=:blocked-at where id=:id

-- :name delete-client :! :1
-- :doc Deletes client with given identifier
Expand Down
8 changes: 7 additions & 1 deletion resources/db/migrations/h2/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ create table users (
permissions varchar(1024),
enabled bit not null default true,
created_at datetime not null,
modified_at datetime,
activated_at datetime,
blocked_at datetime,
UNIQUE KEY users_login_unique (login)
Expand Down Expand Up @@ -56,8 +57,13 @@ create table clients (
secret varchar(32) not null,
info varchar(255),
approved bit not null default false,
enabled bit not null default true,
scopes varchar(1024),
grants varchar(255),
redirects varchar(512) not null,
created_at datetime not null
created_at datetime not null,
modified_at datetime,
activated_at datetime,
blocked_at datetime

);
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ create table users (
permissions varchar(1024),
enabled bit not null default true,
created_at datetime not null,
modified_at datetime,
activated_at datetime,
blocked_at datetime,
UNIQUE KEY users_login_unique (login)
Expand Down Expand Up @@ -52,8 +53,12 @@ create table clients (
secret varchar(32) not null,
info varchar(255),
approved bit not null default false,
enabled bit not null default true,
scopes varchar(1024),
grants varchar(255),
redirects varchar(512) not null,
created_at datetime not null
created_at datetime not null,
modified_at datetime,
activated_at datetime,
blocked_at datetime
);
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ create table users (
permissions varchar(1024),
enabled boolean not null default true,
created_at timestamp not null,
modified_at timestamp,
activated_at timestamp,
blocked_at timestamp
);
Expand Down Expand Up @@ -50,8 +51,12 @@ create table clients (
secret varchar(32) not null,
info varchar(255),
approved boolean not null default false,
enabled boolean not null default true,
scopes varchar(1024),
grants varchar(255),
redirects varchar(512) not null,
created_at timestamp not null
created_at timestamp not null,
modified_at timestamp,
activated_at timestamp,
blocked_at timestamp
);
6 changes: 2 additions & 4 deletions src/cerber/form.clj
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@
(render-login-form (assoc req :failed? true))

;; login succeeded. redirect either to session-stored or default landing url.
(let [{:keys [login roles permissions]} (::ctx/user result)]
(let [{:keys [id login]} (::ctx/user result)]
(-> (get-in req [:session :landing-url] (:landing-url app-config))
(redirect)
(assoc :session {:login login
:roles roles
:permissions permissions}))))))
(assoc :session {:id id :login login}))))))
12 changes: 7 additions & 5 deletions src/cerber/handlers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
[cerber.oauth2
[authorization :as auth]
[context :as ctx]]
[ring.util.request :refer [request-url]]
[ring.middleware
[anti-forgery :refer [wrap-anti-forgery]]
[format :refer [wrap-restful-format]]
[session :refer [wrap-session]]]))
[session :refer [wrap-session]]]
[failjure.core :as f]))

(def custom-store (session-store))

Expand All @@ -19,15 +21,15 @@
(if-let [error (:error response)]
(if (= (:code response) 302)
(error/error->redirect response (:state params) (:redirect_uri params))
(error/error->json response (:state params) (:headers req) (:uri req)))
(error/error->json response (:state params) (:headers req) (request-url req)))
response))))

(defn wrap-authorization [handler]
(fn [req]
(let [result (or (ctx/user-logged? req)
(let [result (or (and (-> req :session :login)
(ctx/user-authenticated? req))
(ctx/bearer-valid? req))]

(if (:error result)
(if (f/failed? result)
result
(handler result)))))

Expand Down
20 changes: 10 additions & 10 deletions src/cerber/helpers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,21 @@
[item ttl]
(assoc item :expires-at (now-plus-seconds ttl)))

(defn str->vec
"Decomposes string into vector of space separated substrings.
Returns empty vector if string was either null or empty."
(defn str->coll
"Decomposes string into collection of space separated substrings.
Returns given collection if string was either null or empty."

[^String str]
[coll ^String str]
(or (and str
(> (.length str) 0)
(str/split str #" "))
[]))
(into coll (str/split str #" ")))
coll))

(defn vec->str
"Serializes vector of strings into single (space-separated) string."
(defn coll->str
"Serializes collection of strings into single (space-separated) string."

[vec]
(str/join " " vec))
[coll]
(str/join " " coll))

(defn expires->ttl
"Returns number of seconds between now and expires-at."
Expand Down
Loading

0 comments on commit f38750c

Please sign in to comment.