Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/50/add services #84

Merged
merged 17 commits into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: clojure
lein: 2.7.1
script:
- lein with-profile +${CLOJURE_VERSION},+test midje
- make ${CLOJURE_VERSION}-tests
jdk:
- oraclejdk8
- oraclejdk7
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ PROJECT=friend-oauth2
ROOT_DIR = $(shell pwd)

include dev-resources/make/docs.mk
include dev-resources/make/test.mk
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ repo.
### 0.3.0

This release introduces a new (and fully backwards-compatible) way of
configuring OAuth2 clients using records. Also note that a new dependency was
introdued that, due to its use of reader conditionals, only supports Clojure
version 1.7 and higher.
configuring OAuth2 clients using records. This was then used to provide
service-specific namespaces with convenience functions and URL configurations.
The non-legacy examples were updated with this approach. Also note that a new
dependency was introdued that, due to its use of reader conditionals, only
supports Clojure version 1.7 and higher.

For the complete change history, see the [documentation page][change history].

Expand Down
91 changes: 91 additions & 0 deletions dev-resources/make/test.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
OAUTH2_CLIENT_ID ?= testclientid
OAUTH2_CLIENT_SECRET ?= testlcientsecret
OAUTH2_CALLBACK_URL ?= http://localhost/auth

kibit:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test kibit

eastwood:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test eastwood \
"{:namespaces [:source-paths]}"

lint: kibit eastwood

lint-unused:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test eastwood \
"{:linters [:unused-fn-args \
:unused-locals \
:unused-namespaces \
:unused-private-vars \
:wrong-ns-form] \
:namespaces [:source-paths]}"

lint-ns:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test eastwood \
"{:linters [:unused-namespaces :wrong-ns-form] \
:namespaces [:source-paths]}"

kibit-all:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test,+examples kibit

eastwood-all:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test,+examples eastwood \
"{:namespaces [:source-paths]}"

lint-all: kibit-all eastwood-all

lint-unused-all:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test,+examples eastwood \
"{:linters [:unused-fn-args \
:unused-locals \
:unused-namespaces \
:unused-private-vars \
:wrong-ns-form] \
:namespaces [:source-paths]}"

lint-ns-all:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test,+examples eastwood \
"{:linters [:unused-namespaces :wrong-ns-form] \
:namespaces [:source-paths]}"

clj17-tests:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj17,+test midje

clj18-tests:
@OAUTH2_CLIENT_ID=$(OAUTH2_CLIENT_ID) \
OAUTH2_CLIENT_SECRET=$(OAUTH2_CLIENT_SECRET) \
OAUTH2_CALLBACK_URL=$(OAUTH2_CALLBACK_URL) \
lein with-profile +clj18,+test midje

all-tests: clj17-tests clj18-tests

check: kibit clj18-tests

check-all: lint clj17-tests clj18-tests
28 changes: 23 additions & 5 deletions docs/source/20-configurtion.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,31 @@ facilitated by several configuration utility functions that perform appropriate
tranformations from the record-based approach.


### Sources
## Service Configuration

If, instead of creating a service configuration from scratch, you chose to use
one of the [predefined `friend-oauth2` services][predefined services], the
steps required are fewer and a little bit different.

First of all, you won't call `friend-oauth2.workflow/workflow`; you'll call
`friend-oauth2.service.<name>/workflow` (e.g.,
`friend-oauth2.service.github/workflow`).

Secondly, you won't pass a map with `:config` set to an instance of
`friend-oauth2.config/Client`; you'll pass a map witb `:config` set to a simple
map data structure with just the bits you need (e.g., `{:scope "user"}`).

This is the approach used in the non-legacy examples.


## Configuration Sources

The fields for the record above are the combination of the fields taken from
the OAuth2 services supported by `friend-oauth2`. Each is covered below in its
own sub-section.


#### App.net
### App.net

[App.net's OAuth2 service][app.net service] supports the following query
string parameters for the authorization phase:
Expand All @@ -169,7 +186,7 @@ And it supports the following for access code exchange:
* `grant_type` (value `authorization_code`)


#### Facebook
### Facebook

[Facebook's OAuth2 service][facebook service] supports the following query
string parameters for the authorization phase:
Expand All @@ -189,7 +206,7 @@ And it supports the following for access code exchange:
* `redirect_uri`


#### Github
### Github

[Github's OAuth2 service][github service] supports the following query
string parameters for the authorization phase:
Expand All @@ -209,7 +226,7 @@ And it supports the following for access code exchange:
* `state`


#### Google
### Google

[Google's OAuth2 service][google oauth2 service] supports the following query
string parameters for the authorization phase:
Expand Down Expand Up @@ -238,3 +255,4 @@ And it supports the following for access code exchange:
[facebook service]: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
[github service]: https://developer.github.com/v3/oauth/
[google oauth2 service]: https://developers.google.com/identity/protocols/OAuth2WebServer
[predefined services]: https://github.com/clojusc/friend-oauth2/tree/master/src/friend_oauth2/service
139 changes: 73 additions & 66 deletions docs/source/50-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,105 +61,112 @@ documentation itself, so we'll take on the burden of maintaining the following
text in the effort to provide a good docs experience for you :-)


Here's the Github `friend-oauth2` example, runable from the commandline
(with `lein`, `java`, etc.):
Here's the Google `friend-oauth2` example, runable from the commandline
(e.g., with `lein`):

```clj
(ns friend-oauth2.examples.github
(ns friend-oauth2.examples.google
(:require [cemerick.friend :as friend]
[cemerick.friend.workflows :as workflows]
[cemerick.friend.credentials :as creds]
[clj-http.client :as client]
[clojure.data.json :as json]
[clojure.tools.logging :as log]
[clojusc.twig :as logger]
[compojure.core :as compojure :refer [GET ANY defroutes]]
[compojure.handler :as handler]
[friend-oauth2.config :as config]
[friend-oauth2.workflow :as oauth2]
[friend-oauth2.service.google :as google]
[friend-oauth2.util :as util]
[org.httpkit.server :as server])
[org.httpkit.server :as server]
[ring.util.response :as response]
[ring.middleware.defaults :as ring-defaults])
(:gen-class))

(defn get-authentications
[request]
(get-in request [:session :cemerick.friend/identity :authentications]))

(defn get-token
([request]
(get-token request 0))
([request index]
(let [authentications (get-authentications request)]
(:access-token (nth (keys authentications) index)))))

(defn render-status-page [request]
(let [count (:count (:session request) 0)
session (assoc (:session request) :count (inc count))]
(-> (str "<p>We've hit the session page "
(:count session)
" times.</p><p>The current session: "
session
"</p>")
(ring.util.response/response)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Mini Webapp ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn html
[content]
(-> content
(response/response)
(response/header "Content-Type" "text/html")))

(def index-content
(str "<a href=\"/admin\">Admin Pages</a><br />"
"<a href=\"/authlink\">Authorized page</a><br />"
"<a href=\"/authlink2\">Authorized page 2</a><br />"
"<a href=\"/status\">Status</a><br />"
"<a href=\"/logout\">Log out</a>"))

(defn get-status-content
[count session]
(str "<p>We've hit the session page "
count
" times.</p><p>The current session: "
session
"</p>"))

(defn render-index-page
[req]
(html index-content))

(defn render-status-page
[req]
(let [count (:count (:session req) 0)
session (assoc (:session req) :count (inc count))]
(-> (get-status-content count session)
(html)
(assoc :session session))))

(defn get-github-repos
"Github API call for the current authenticated users repository list."
[access-token]
(let [url (str "https://api.github.com/user/repos?access_token=" access-token)
response (client/get url {:accept :json})
repos (json/read-str (:body response) :key-fn keyword)]
repos))

(defn render-repos-page
"Shows a list of the current users github repositories by calling the github api
with the OAuth2 access token that the friend authentication has retrieved."
[request]
(let [access-token (get-token request)
repos-response (get-github-repos access-token)]
(->> repos-response
(map :name)
(vec)
(str))))

(defroutes app-routes
(GET "/" request
(str "<a href=\"/repos\">My Github Repositories</a><br />"
"<a href=\"/status\">Status</a><br />"
"<a href=\"/logout\">Log out</a>"))
(GET "/status" request
(render-status-page request))
(GET "/repos" request
(friend/authorize #{::user} (render-repos-page request)))
(friend/logout (ANY "/logout" request (ring.util.response/redirect "/"))))

(def cfg
(config/client
:scope "user"
:auth-uri "https://github.com/login/oauth/authorize"
:token-uri "https://github.com/login/oauth/access_token"))
(GET "/" req
(render-index-page req))
(GET "/status" req
(render-status-page req))
(GET "/authlink" req
(friend/authorize
#{::user}
(html "Authorized page.")))
(GET "/authlink2" req
(friend/authorize
#{::user}
(html "Authorized page 2.")))
(GET "/admin" req
(friend/authorize
#{::admin}
(html "Only admins can see this page.")))
(friend/logout (ANY "/logout" req (response/redirect "/"))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; OAuth2 Configuration and Integration ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn credential-fn
[token]
;;lookup token in DB or whatever to fetch appropriate :roles
{:identity token :roles #{::user}})

(def workflow
(oauth2/workflow
{:config cfg
(google/workflow
{:config {:scope "email"}
:access-token-parsefn util/get-access-token-from-params
:credential-fn credential-fn}))

(def auth-opts
{:allow-anon? true
:workflows [workflow]})

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; App Server ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def app
(-> app-routes
(friend/authenticate auth-opts)
(handler/site)))
(ring-defaults/wrap-defaults ring-defaults/site-defaults)))

(defn -main
[& args]
(logger/set-level! '[ring friend friend-oauth2] :info)
(log/info "Starting example server using Google OAuth2 ...")
(server/run-server app {:port 8999}))
```

Expand Down
8 changes: 5 additions & 3 deletions docs/source/80-change-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ repo.
## 0.3.0

This release introduces a new (and fully backwards-compatible) way of
configuring OAuth2 clients using records. Also note that a new dependency was
introdued that, due to its use of reader conditionals, only supports Clojure
version 1.7 and higher.
configuring OAuth2 clients using records. This was then used to provide
service-specific namespaces with convenience functions and URL configurations.
The non-legacy examples were updated with this approach. Also note that a new
dependency was introdued that, due to its use of reader conditionals, only
supports Clojure version 1.7 and higher.


[0.1.3-transitional]: https://github.com/clojusc/friend-oauth2/releases/tag/0.1.3-transitional
Loading