diff --git a/resources/docs/get-admin-token.md b/resources/docs/get-admin-token.md new file mode 100644 index 00000000..2f5e8b4d --- /dev/null +++ b/resources/docs/get-admin-token.md @@ -0,0 +1,27 @@ +This service allows administrative users to obtain an impersonation token in order to act on behalf of another +user. This feature is useful for troubleshooting in cases where a user is seeing a problem that the administrator can't +reproduce. You must be logged in using HTTP basic authorization to use this endpoint. The token endpoints are the only +endpoints that use basic authorization. To log in, click the Authorize button above, enter your username and password +under `Basic authentication`, and click the Authorize button underneath the password text box. + +Once you have the access token, you can use it to authorize calls to other endpoints in the Swagger UI. First, remove +the basic authentication credentials by clicking the Authorize button above and clicking the Logout button in the `Basic +authenitcation` section of authorization window. Second, click the Authorization button again and type the word `Bearer` +followed by a single space in the Value text box of the `Api key authorization` section of the window. Paste in the +access token from this endpoint's response body then click the Authorize button underneath the Value text box. + +You can use `curl` and `jq`, which is available from [the jq web site](https://stedolan.github.io/jq/), to obtain an +access token from the command line. The easiest way to do this on Unix-like operating systems is to define an +environment variable containing the authorization header: + +``` +export AUTH_HEADER=\"Authorization: Bearer $(curl -su username https://de.cyverse.org/terrain/admin/token?username=foo \ + | jq -r .access_token)\" +``` + +Once you have the authorization header stored in an environment variable, you can include it in calls to other Terrain +endpoints: + +``` +curl -sH \"$AUTH_HEADER\" \"https://de.cyverse.org/terrain/apps?search=word\" +``` diff --git a/src/terrain/clients/keycloak.clj b/src/terrain/clients/keycloak.clj index da73968b..5456c2ab 100644 --- a/src/terrain/clients/keycloak.clj +++ b/src/terrain/clients/keycloak.clj @@ -26,3 +26,15 @@ :username username :password password} :as :json}))) + +(defn get-impersonation-token + "Obtains an impersonation token for troubleshooting purposes." + [subject-token username] + (:body (http/post (keycloak-url "protocol" "openid-connect" "token") + {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:token-exchange" + :client_id (config/keycloak-client-id) + :client_secret (config/keycloak-client-secret) + :subject_token subject-token + :requested_token_type "urn:ietf:params:oauth:token-type:access_token" + :requested_subject username} + :as :json}))) diff --git a/src/terrain/routes.clj b/src/terrain/routes.clj index 5283a9d4..fe234f4f 100644 --- a/src/terrain/routes.clj +++ b/src/terrain/routes.clj @@ -167,6 +167,7 @@ (defn unsecured-routes [] (util/flagged-routes + (admin-token-routes) (token-routes) (unsecured-misc-routes) (unsecured-notification-routes))) @@ -247,6 +248,7 @@ {:name "admin-reference-genomes", :description "Admin Reference Genome Endpoints"} {:name "admin-requests", :description "Admin Request Endpoints"} {:name "admin-settings", :description "Admin Setting Endpoints"} + {:name "admin-token", :description "Admin OAuth Tokens"} {:name "admin-tools", :description "Admin Tool Endpoints"} {:name "admin-tool-requests", :description "Admin Tool Request Endpoints"} {:name "admin-user-info", :description "User Info Administration Endpoints"} diff --git a/src/terrain/routes/schemas/token.clj b/src/terrain/routes/schemas/token.clj index c6832d10..4a346407 100644 --- a/src/terrain/routes/schemas/token.clj +++ b/src/terrain/routes/schemas/token.clj @@ -26,3 +26,6 @@ (optional-key :scope) (describe String "The scopes granted to the access token")}) + +(defschema AdminKeycloakTokenParams + {:username (describe String "The username of the person to impersonate")}) diff --git a/src/terrain/routes/token.clj b/src/terrain/routes/token.clj index a249f6a3..4ef7860d 100644 --- a/src/terrain/routes/token.clj +++ b/src/terrain/routes/token.clj @@ -27,3 +27,23 @@ :return AccessTokenResponse :description-file "docs/get-token.md" (oauth/get-keycloak-token authorization))))) + +(defn admin-token-routes + [] + (routes + (context "/admin/token" [] + :tags ["admin-token"] + + (GET "/" [:as {{:strs [authorization]} :headers}] + :query [{:keys [username]} AdminKeycloakTokenParams] + :summary "Obtain Impersonation Tokens" + :return AccessTokenResponse + :description-file "docs/get-admin-token.md" + (oauth/get-admin-token authorization username)) + + (GET "/keycloak" [:as {{:strs [authorization]} :headers}] + :query [{:keys [username]} AdminKeycloakTokenParams] + :summary "Obtain Keycloak OIDC Impersonation Tokens" + :return AccessTokenResponse + :description-file "docs/get-admin-token.md" + (oauth/get-keycloak-admin-token authorization username))))) diff --git a/src/terrain/services/oauth.clj b/src/terrain/services/oauth.clj index 39acd96d..48b0978d 100644 --- a/src/terrain/services/oauth.clj +++ b/src/terrain/services/oauth.clj @@ -25,5 +25,15 @@ (http-response/ok (keycloak/get-token username password)) (http-response/unauthorized))) -;; Make CAS the default identity provider for now. +;; Make CAS the default identity provider for standard tokens for now. (def get-token get-cas-token) + +(defn get-keycloak-admin-token [authorization username] + (if-let [[username password] (get-basic-auth-credentials authorization)] + (http-response/ok (-> (keycloak/get-token username password) + :access_token + (keycloak/get-impersonation-token username))) + (http-response/unauthorized))) + +;; Make Keycloak the default identity provider for impersonation tokens. +(def get-admin-token get-keycloak-admin-token)