Skip to content

Commit

Permalink
Merge pull request #292 from ianmcorvidae/login-tracking
Browse files Browse the repository at this point in the history
Fetch login info (IP, session ID) from Keycloak for recording in the DE database
  • Loading branch information
ianmcorvidae authored Dec 12, 2024
2 parents f80f50e + a12fafc commit e672bf5
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/terrain/clients/apps/raw.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1277,8 +1277,8 @@
:content-type :json}))))

(defn record-login
[ip-address user-agent]
(let [params (remove-nil-values {:ip-address ip-address :user-agent user-agent})]
[ip-address session-id login-time]
(let [params (remove-nil-values {:ip-address ip-address :session-id session-id :login-time login-time})]
(:body
(client/post (apps-url "users" "login")
(disable-redirects
Expand Down
61 changes: 61 additions & 0 deletions src/terrain/clients/keycloak/admin.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
(ns terrain.clients.keycloak.admin
(:require [cemerick.url :as curl]
[clj-http.client :as http]
[terrain.util.config :as config]))

(defn- keycloak-admin-url
"Builds a Keycloak admin API URL with the given path components."
[& components]
(str (apply curl/url (config/keycloak-admin-base-uri) "realms" (config/keycloak-realm) components)))

(defn- keycloak-admin-token-url
"Like keycloak-admin-url but for the 'master' realm to get a token to use with the API"
[& components]
(str (apply curl/url (config/keycloak-admin-base-uri) "realms" "master" components)))

(defn get-token
"Obtains authorization token data for the admin service account. You'll probably want the access_token field in the return value."
[]
(:body (http/post (keycloak-admin-token-url "protocol" "openid-connect" "token")
{:form-params {:grant_type "client_credentials"
:client_id (config/keycloak-client-id)
:client_secret (config/keycloak-client-secret)}
:as :json})))

; https://www.keycloak.org/docs-api/26.0.5/rest-api/#_get_adminrealmsrealmusers
(defn get-user
"Obtains user information from keycloak
This will be a map including keys at least :username and :id, which should be
what we need to make further requests"
([username]
(get-user username (:access_token (get-token))))
([username token]
(let [user-data (http/get (keycloak-admin-url "users")
{:query-params {:username username
:exact true}
:headers {:authorization (str "Bearer " token)}
:as :json})]
; the 'exact' query parameter doesn't seem to work on all keycloak versions, so we filter it
(->> user-data
(filter (fn [user] (= (:username user) username)))
first))))

; https://www.keycloak.org/docs-api/26.0.5/rest-api/#_get_adminrealmsrealmusersuser_idsessions
(defn get-user-session
"Obtains information about the user's current session from keycloak.
This will be a list of maps, which will include user ID, ip address, session ID, and clients at least."
([user-id]
(get-user-session user-id (:access_token (get-token))))
([user-id token]
(:body (http/get (keycloak-admin-url "users" user-id "sessions")
{:headers {:authorization (str "Bearer " token)}
:as :json}))))

(defn get-user-session-by-username
"Same as `get-user-session`, but by username by way of a request to `get-user` first."
([username]
(get-user-session-by-username username (:access_token (get-token))))
([username token]
(get-user-session (:id (get-user username token)) token)))
12 changes: 9 additions & 3 deletions src/terrain/services/bootstrap.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[slingshot.slingshot :refer [try+]]
[terrain.auth.user-attributes :refer [current-user]]
[terrain.clients.apps.raw :as apps-client]
[terrain.clients.keycloak.admin :as kc-client]
[terrain.clients.data-info :as data-info-client]
[terrain.services.user-prefs :as prefs]
[terrain.util.service :as service]))
Expand Down Expand Up @@ -33,9 +34,14 @@
{:error (str (:throwable &throw-context))})))

(defn- get-login-session
[ip-address user-agent]
[username]
(trap-bootstrap-request
#(select-keys (apps-client/record-login ip-address user-agent) [:login_time :auth_redirect])))
#(let [kc-resp (kc-client/get-user-session-by-username username)
current-session (first kc-resp) ;; TODO: choose most recent start/access using our known client ID
]
(select-keys (apps-client/record-login (:ipAddress current-session nil)
(:id current-session nil)
(:start current-session nil)) [:login_time :auth_redirect]))))

(defn- get-apps-info
[]
Expand All @@ -60,7 +66,7 @@
[ip-address user-agent]
(assertions/assert-valid user-agent "Missing or empty request parameter: user-agent")
(let [{user :shortUsername :keys [email firstName lastName username]} current-user
login-session (future (get-login-session ip-address user-agent))
login-session (future (get-login-session username))
apps-info (future (get-apps-info))
data-info (future (get-user-data-info user))
preferences (future (get-user-prefs username))]
Expand Down
18 changes: 18 additions & 0 deletions src/terrain/util/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,24 @@
[props config-valid configs]
"terrain.keycloak.client-secret")

(declare keycloak-admin-base-uri)
(cc/defprop-optstr keycloak-admin-base-uri
"The base URI to use for administrative requests to Keycloak."
[props config-valid configs]
"terrain.keycloak.admin-base-uri" "https://keycloaktest2.cyverse.org/auth/admin")

(declare keycloak-admin-client-id)
(cc/defprop-str keycloak-admin-client-id
"The Keycloak admin client ID to use."
[props config-valid configs]
"terrain.keycloak.admin-client-id")

(declare keycloak-admin-client-secret)
(cc/defprop-str keycloak-admin-client-secret
"The keycloak admin client secret to use."
[props config-valid configs]
"terrain.keycloak.admin-client-secret")

(declare dashboard-aggregator-url)
(cc/defprop-optstr dashboard-aggregator-url
"The URL to the dashboard-aggregator service."
Expand Down

0 comments on commit e672bf5

Please sign in to comment.