Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
seki-seki committed Jul 14, 2017
2 parents 530c446 + 821e34c commit 7750e83
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 62 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.9
1.1.0-BETA
1 change: 1 addition & 0 deletions dev/dev.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

(def config
(meta-merge config/defaults
config/resource-file
config/environ
dev-config))

Expand Down
3 changes: 3 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
[ch.qos.logback/logback-classic "1.1.7"]
[org.jboss.weld.se/weld-se "2.2.7.Final"]
[net.unit8.weld/weld-prescan "0.1.0"]
[crypto-random "1.2.0"]
[ring/ring-codec "1.0.1"]
[org.clojure/data.codec "0.1.0"]

;; for Scheduler
[org.quartz-scheduler/quartz "2.2.3"]
Expand Down
174 changes: 136 additions & 38 deletions src/clj/job_streamer/control_bus/component/auth.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@
[liberator.core :as liberator]
[liberator.representation :refer [ring-response]]
[clojure.string :as str]
[clojure.data.json :as json]
[clojure.data.codec.base64 :as base64]
[org.httpkit.client :as http]
[ring.util.response :refer [response content-type header redirect]]
[ring.util.codec :refer [form-encode]]
(job-streamer.control-bus [validation :refer [validate]]
[util :refer [parse-body]])
[util :refer [parse-body generate-token]])
(job-streamer.control-bus.component [datomic :as d]
[token :as token])))

(defn- find-permissions [datomic user-id app-name]
(->> (d/query datomic
'{:find [?permission]
:in [$ ?app-name ?user-id]
:where [[?member :member/user ?user]
[?member :member/roles ?role]
[?role :role/permissions ?permission]
[?user :user/id ?user-id]
[?app :application/members ?member]
[?app :application/name ?app-name]]}
app-name user-id)
(apply concat)
set))

(defn- auth-by-password [datomic user-id password app-name]
(when (and (not-empty user-id) (not-empty password))
(when-let [user (d/query datomic
Expand All @@ -30,18 +48,21 @@
[?app :application/members ?member]
[?app :application/name ?app-name]]}
user-id password app-name)]
(let [permissions (->> (d/query datomic
'{:find [?permission]
:in [$ ?app-name ?user-id]
:where [[?member :member/user ?user]
[?member :member/rolls ?roll]
[?roll :roll/permissions ?permission]
[?user :user/id ?user-id]
[?app :application/members ?member]
[?app :application/name ?app-name]]}
app-name user-id)
(apply concat)
set)]

(let [permissions (find-permissions datomic user-id app-name)]
(assoc user :permissions permissions)))))

(defn- find-user [datomic user-id app-name]
(when (not-empty user-id)
(when-let [user (d/query datomic
'{:find [(pull ?s [:*]) .]
:in [$ ?uname ?app-name]
:where [[?s :user/id ?uname]
[?member :member/user ?s]
[?app :application/members ?member]
[?app :application/name ?app-name]]}
user-id app-name)]
(let [permissions (find-permissions datomic user-id app-name)]
(assoc user :permissions permissions)))))

(defvalidator unique-name-validator
Expand All @@ -53,16 +74,16 @@
:where [[?u :user/id ?id]]}
id)))

(defvalidator exist-roll-validator
(defvalidator exist-role-validator
{:default-message-format "%s is used by someone."}
[roll-name datomic]
[role-name datomic]
(d/query datomic
'[:find ?e .
:in $ ?n
:where [?e :roll/name ?n]]
roll-name))
:where [?e :role/name ?n]]
role-name))

(defn- signup [datomic user roll-name]
(defn- signup [datomic user role-name]
(when-let [user (let [salt (nonce/random-nonce 16)
password (some-> (not-empty (:user/password user))
(.getBytes)
Expand All @@ -78,11 +99,11 @@
:user/salt salt})
(when-let [token (:user/token user)]
{:user/token token}))))]
(let [roll-id (d/query datomic
(let [role-id (d/query datomic
'[:find ?e .
:in $ ?n
:where [?e :roll/name ?n]]
roll-name)
:where [?e :role/name ?n]]
role-name)
member-id (d/tempid :db.part/user)
app (d/query datomic
'[:find (pull ?e [*]) .
Expand All @@ -92,11 +113,39 @@
(let [result (d/transact datomic [user
{:db/id member-id
:member/user (select-keys user [:db/id])
:member/rolls [roll-id]}
:member/roles [role-id]}
(update-in (select-keys app [:db/id :application/members]) [:application/members] #(conj % member-id))])]
(log/infof "Signup %s as %s succeeded." (:user/id user) roll-name)
(log/infof "Signup %s as %s succeeded." (:user/id user) role-name)
result))))

(defn fetch-access-token [{:keys [oauth-providers control-bus-url]} provider-id state code]
(when (and (not-empty code) (oauth-providers provider-id))
(log/info "Fetch access-token with code :" code)
(let [{:keys [domain token-endpoint client-id client-secret]} (oauth-providers provider-id)
url (str domain "/" token-endpoint)
query (-> (merge {:code code
:state state
:grant_type "authorization_code"
:client_id client-id
:redirect_uri (str control-bus-url "/oauth/" provider-id "/cb")}
(when client-secret {:client_secret client-secret}))
form-encode)
auth-header (->> (str client-id ":" client-secret)
.getBytes
base64/encode
String.
(str "Basic "))
{:keys [body status] :as res} @(http/post (str url "?" query)
{:body query
:headers {"Accept" "application/json"
"Authorization" auth-header
"Content-Type" "application/x-www-form-urlencoded"}})]
(when (= 200 status)
(let [{:keys [access_token]} (json/read-str body :key-fn keyword)]
(when access_token
(log/info "access-token :" access_token)
access_token))))))

(defn auth-resource
[{:keys [datomic token] :as component}]
(liberator/resource
Expand All @@ -115,7 +164,7 @@
(ring-response {:session {:identity (select-keys user [:user/id :permissions])}
:body (pr-str {:token (str access-token)})}))
(do (log/info "Login attempt failed because of authentification failure.")
(ring-response {:status 401 :body (pr-str {:messages ["Autification failure."]})})))))
(ring-response {:status 401 :body (pr-str {:messages ["Authentication failure."]})})))))
:handle-no-content (fn [_] (ring-response {:session {}}))))

(defn list-resource
Expand Down Expand Up @@ -143,49 +192,98 @@
[v/min-count 3 :message "Username must be at least 3 characters long."]
[v/max-count 20 :message "Username is too long."]
[unique-name-validator datomic]]
:roll [[v/required]
:role [[v/required]
[v/matches #"^[\w\-]+$"]
[exist-roll-validator datomic]]))
[exist-role-validator datomic]]))
:post! (fn [{user :edn}]
(let [roll-name (:roll user)
(let [role-name (:role user)
user (select-keys user [:user/id :user/password])]
(signup datomic user roll-name)))
(signup datomic user role-name)))
:delete! (fn [_]
(d/transact datomic
[[:db.fn/retractEntity [:user/id user-id]]]))))

(defn oauth-resource [{:keys [oauth-providers]}]
(liberator/resource
:available-media-types ["application/edn" "application/json"]
:allowed-methods [:get]
:exists? (fn [_]
{:providers (->> oauth-providers
(map (fn [[id provider]]
[id (select-keys provider [:name :class-name])]))
(into {}))})
:handle-ok (fn [{:keys [providers]}]
providers)))

(defn redirect-to-auth-provider [{:keys [oauth-providers control-bus-url console-url]} provider-id]
(fn [request]
(if-let [{:keys [domain auth-endpoint client-id client-secret scope]} (oauth-providers provider-id)]
(let [state (generate-token)
session-with-state (assoc (:session request) :state state)
url (str domain "/" auth-endpoint)
query (merge {:client_id client-id
:response_type "code"
:redirect_uri (str control-bus-url "/oauth/" provider-id "/cb")
:state state}
(when client-secret {:client_secret client-secret})
(when scope {:scope scope}))]
(log/info "Redirect to auth provider :" provider-id)
(-> (str url "?" (form-encode query))
redirect
(assoc :session session-with-state)))
(do (log/infof "Redirect attempt to auth provider, %s, failed because of no configuration." provider-id)
(-> (redirect (str console-url "/login"))
(assoc :body (pr-str {:messages [(str "No configuration : " provider-id)]})))))))

(defn oauth-callback [{:keys [datomic console-url] :as auth} provider-id]
(fn [request]
(let [app-name "default"
{:keys [state code error]} (:params request)
session-state (get-in request [:session :state])]
(if (and (some? code)
(= state session-state))
(if-let [token (fetch-access-token auth provider-id state code)]
(let [identity (-> (find-user datomic "guest" app-name)
(select-keys [:user/id :permissions]))]
(log/info "Login attempt succeeded :" (:user/id identity))
(-> (redirect console-url)
(assoc-in [:session :identity] identity)))
(redirect (str console-url "/login")))
(redirect (str console-url "/login"))))))

(defrecord Auth [datomic]
component/Lifecycle

(start [component]

;; Create an initil user and rolls.
;; Create an initil user and roles.
(->> [{:db/id (d/tempid :db.part/user)
:roll/name "admin"
:roll/permissions [:permission/read-job
:role/name "admin"
:role/permissions [:permission/read-job
:permission/create-job
:permission/update-job
:permission/delete-job
:permission/execute-job]}
{:db/id (d/tempid :db.part/user)
:roll/name "operator"
:roll/permissions [:permission/read-job
:role/name "operator"
:role/permissions [:permission/read-job
:permission/execute-job]}
{:db/id (d/tempid :db.part/user)
:roll/name "watcher"
:roll/permissions [:permission/read-job]}]
:role/name "watcher"
:role/permissions [:permission/read-job]}]
(filter #(nil? (d/query datomic
'[:find ?e .
:in $ ?n
:where [?e :roll/name ?n]]
(:roll/name %))))
:where [?e :role/name ?n]]
(:role/name %))))
(d/transact datomic))
(when-not (d/query datomic
'[:find ?e .
:in $ ?n
:where [?e :user/id ?n]]
"admin")
(signup datomic {:user/id "admin" :user/password "password123"} "admin"))
(signup datomic {:user/id "admin" :user/password "password123"} "admin")
(signup datomic {:user/id "guest" :user/password "password123"} "operator"))

component)

Expand Down
7 changes: 7 additions & 0 deletions src/clj/job_streamer/control_bus/config.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
(ns job-streamer.control-bus.config
(:require [environ.core :refer [env]]
[clojure.java.io :as io]
[clojure.edn :as edn]
[job-streamer.control-bus.model :as model]))

(def defaults
Expand All @@ -24,3 +26,8 @@
:auth {:access-control-allow-origin access-control-allow-origin}
:datomic {:uri datomic-uri}}))

(def resource-file
(some-> "job-streamer-control-bus/config.edn"
io/resource
slurp
edn/read-string))
6 changes: 6 additions & 0 deletions src/clj/job_streamer/control_bus/endpoint/api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
(ANY "/users" [] (auth/list-resource auth))
(ANY "/user" [] (auth/entry-resource auth nil))
(ANY ["/user/:user-id" :user-id #".*"] [user-id] (auth/entry-resource auth user-id))
(ANY "/oauth" []
(auth/oauth-resource auth))
(ANY "/oauth/:provider-id" [provider-id]
(auth/redirect-to-auth-provider auth provider-id))
(ANY "/oauth/:provider-id/cb" [provider-id]
(auth/oauth-callback auth provider-id))

;; Job
(ANY "/:app-name/jobs" [app-name]
Expand Down
1 change: 1 addition & 0 deletions src/clj/job_streamer/control_bus/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

(def config
(meta-merge config/defaults
config/resource-file
config/environ
prod-config))

Expand Down
4 changes: 2 additions & 2 deletions src/clj/job_streamer/control_bus/model.clj
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@
(schema member
(fields
[user :ref]
[rolls :ref :many]))
[roles :ref :many]))
(schema user
(fields
[id :string :indexed :unique-value]
[password :string]
[salt :bytes]
[token :string]))
(schema roll
(schema role
(fields
[name :string :indexed :unique-value]
[permissions :keyword :many]))])
2 changes: 1 addition & 1 deletion src/clj/job_streamer/control_bus/system.clj
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
(header "Access-Control-Allow-Origin" access-control-allow-origin)
(header "Access-Control-Allow-Credentials" "true"))))))

(def access-rules [{:pattern #"^/(?!auth|user|healthcheck|version).*$"
(def access-rules [{:pattern #"^/(?!auth|oauth|user|healthcheck|version).*$"
:handler authenticated?}])

(defn token-base [token-provider]
Expand Down
9 changes: 8 additions & 1 deletion src/clj/job_streamer/control_bus/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
[clojure.java.io :as io]
[datomic.api :as d]
[ring.util.request :refer [content-type]]
[clojure.data.json :as json])
[clojure.data.json :as json]
[clojure.string :as string]
[crypto.random :as random])
(:import [org.jsoup Jsoup]))

(defn to-int [n default-value]
Expand Down Expand Up @@ -136,3 +138,8 @@
(catch Exception e
(log/error e "fail to parse edn.")
{:message (format "IOException: %s" (.getMessage e))}))))

(defn generate-token
"Generates random string for anti-forgery-token."
[]
(string/replace (random/base64 60) #"[\+=/]" "-"))
1 change: 1 addition & 0 deletions test/clj/job_streamer/control_bus/component/apps_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

(def config
(meta-merge config/defaults
config/resource-file
config/environ
test-config))

Expand Down
Loading

0 comments on commit 7750e83

Please sign in to comment.