Skip to content

Commit 799d7a7

Browse files
committed
CMR-11017: adds support for federated jwt tokens
1 parent 8588edf commit 799d7a7

File tree

14 files changed

+175
-37
lines changed

14 files changed

+175
-37
lines changed

access-control-app/src/cmr/access_control/api/routes.clj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@
331331
;; Create a group
332332
(POST "/"
333333
{ctx :request-context params :params headers :headers body :body}
334-
(lt-validation/validate-launchpad-token ctx)
334+
(lt-validation/validate-write-token ctx)
335335
(pv/validate-create-group-route-params params)
336336
(create-group ctx
337337
headers
@@ -350,14 +350,14 @@
350350
;; Delete a group
351351
(DELETE "/"
352352
{ctx :request-context params :params}
353-
(lt-validation/validate-launchpad-token ctx)
353+
(lt-validation/validate-write-token ctx)
354354
(pv/validate-group-route-params params)
355355
(delete-group ctx group-id))
356356

357357
;; Update a group
358358
(PUT "/"
359359
{ctx :request-context params :params headers :headers body :body}
360-
(lt-validation/validate-launchpad-token ctx)
360+
(lt-validation/validate-write-token ctx)
361361
(pv/validate-group-route-params params)
362362
(update-group ctx headers (slurp body) group-id))
363363

@@ -370,13 +370,13 @@
370370

371371
(POST "/"
372372
{ctx :request-context params :params headers :headers body :body}
373-
(lt-validation/validate-launchpad-token ctx)
373+
(lt-validation/validate-write-token ctx)
374374
(pv/validate-group-route-params params)
375375
(add-members ctx headers (slurp body) group-id))
376376

377377
(DELETE "/"
378378
{ctx :request-context params :params headers :headers body :body}
379-
(lt-validation/validate-launchpad-token ctx)
379+
(lt-validation/validate-write-token ctx)
380380
(pv/validate-group-route-params params)
381381
(remove-members ctx headers (slurp body) group-id)))))
382382
(context "/groups" []))
@@ -399,7 +399,7 @@
399399
;; Create an ACL
400400
(POST "/"
401401
{ctx :request-context params :params headers :headers body :body}
402-
(lt-validation/validate-launchpad-token ctx)
402+
(lt-validation/validate-write-token ctx)
403403
(pv/validate-standard-params params)
404404
(create-acl ctx headers (slurp body)))
405405

@@ -409,13 +409,13 @@
409409
;; Update an ACL
410410
(PUT "/"
411411
{ctx :request-context headers :headers body :body}
412-
(lt-validation/validate-launchpad-token ctx)
412+
(lt-validation/validate-write-token ctx)
413413
(update-acl ctx concept-id headers (slurp body)))
414414

415415
;; Delete an ACL
416416
(DELETE "/"
417417
{ctx :request-context headers :headers}
418-
(lt-validation/validate-launchpad-token ctx)
418+
(lt-validation/validate-write-token ctx)
419419
(delete-acl ctx concept-id headers))
420420

421421
;; Retrieve an ACL

access-control-app/src/cmr/access_control/system.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
[:system-object :provider-object :single-instance-object])
105105
provider-cache/cache-key (provider-cache/create-cache)
106106
acl/collection-field-constraints-cache-key (acl/create-access-constraints-cache)
107+
acl/token-draft-cache-key (acl/create-token-draft-cache)
107108
common-enabled/write-enabled-cache-key (common-enabled/create-write-enabled-cache)
108109
common-health/health-cache-key (common-health/create-health-cache)
109110
launchpad-user-cache/launchpad-user-cache-key (launchpad-user-cache/create-launchpad-user-cache)

acl-lib/src/cmr/acl/core.clj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@
211211
(has-management-permission?
212212
context permission-type object-identity-type provider-id "PROVIDER_CONTEXT"))
213213

214+
(defn has-non-nasa-draft-permission?
215+
"Returns true if the user has been granted NON_NASA_DRAFT_USER permission.
216+
Required for EDL+MFA (assurance level 4) JWT tokens."
217+
[context permission-type object-identity-type provider-id]
218+
(has-management-permission?
219+
context permission-type object-identity-type provider-id "NON_NASA_DRAFT_USER"))
220+
214221
(defn- verify-management-permission
215222
"Verifies the current user has been granted the permission in permission-fn in ECHO ACLs"
216223
[context permission-type object-identity-type provider-id cache-key permission-fn]
@@ -308,3 +315,10 @@
308315
provider-id
309316
token-pc-cache-key
310317
has-provider-context-permission?)))
318+
319+
(defn verify-non-nasa-draft-permission
320+
"Verifies the user has NON_NASA_DRAFT_USER permission for a provider.
321+
Required for EDL+MFA (assurance level 4) JWT tokens."
322+
[context permission-type object-identity-type provider-id]
323+
(when-not (has-non-nasa-draft-permission? context permission-type object-identity-type provider-id)
324+
(errors/throw-service-error :unauthorized "You do not have permission to perform that action.")))
Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
(ns cmr.common-app.api.launchpad-token-validation
22
"Validate Launchpad token."
33
(:require
4+
[cmr.acl.core :as acl]
45
[cmr.common-app.config :as config]
6+
[cmr.common.log :refer [info]]
57
[cmr.common.services.errors :as errors]
68
[cmr.common.util :as common-util]
7-
[cmr.transmit.config :as transmit-config]))
9+
[cmr.transmit.config :as transmit-config]
10+
[cmr.transmit.tokens :as tokens]))
811

912
;; TODO - remove legacy token check after legacy token retirement
1013
(defn get-token-type
@@ -15,19 +18,76 @@
1518
(= (transmit-config/echo-system-token) token) "System"
1619
(re-seq #"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}" token) "Echo-Token"
1720
(common-util/is-legacy-token? token) "Legacy-EDL"
21+
(tokens/is-launchpad-federated-jwt? token) "JWT-Launchpad-Level-5"
22+
(tokens/is-edl-mfa-jwt? token) "JWT-EDL-MFA-Level-4"
1823
(common-util/is-jwt-token? token) "JWT"
1924
:else "Launchpad")))
2025

21-
(defn validate-launchpad-token
22-
"Validate the token in request context is a Launchpad Token if launchpad token enforcement
23-
is turned on. This function should be called on routes that will ingest into CMR.
24-
Ingest will only be allowed if the user is in the NAMS CMR Ingest group
25-
and also has the right ACLs which is based on Earthdata Login uid."
26-
[request-context]
27-
(let [token (:token request-context)]
28-
(when (and (config/launchpad-token-enforced)
29-
(not (common-util/is-launchpad-token? token))
30-
(not= (transmit-config/echo-system-token) token))
31-
(errors/throw-service-error
32-
:bad-request
33-
(format "Launchpad token is required. Token [%s] is not a launchpad token." (common-util/scrub-token token))))))
26+
(defn- get-token-info
27+
"Returns token validation info. Returns map with :type, :valid?, :assurance-level, :requires-draft-acl?"
28+
[token]
29+
(let [is-jwt? (common-util/is-jwt-token? token)
30+
assurance-level (when is-jwt? (tokens/get-assurance-level token))
31+
is-saml? (common-util/is-launchpad-token? token)
32+
33+
valid-saml? (and is-saml? (config/enable-launchpad-saml-authentication))
34+
valid-jwt? (and is-jwt?
35+
(config/enable-idfed-jwt-authentication)
36+
assurance-level
37+
(>= assurance-level (config/required-assurance-level)))]
38+
{:type (cond
39+
(= token (transmit-config/echo-system-token)) :system
40+
is-saml? :saml
41+
(and is-jwt? (= assurance-level 5)) :jwt-level-5
42+
(and is-jwt? (= assurance-level 4)) :jwt-level-4
43+
is-jwt? :jwt-other
44+
:else :unknown)
45+
:valid? (or valid-saml? valid-jwt?)
46+
:assurance-level assurance-level
47+
:requires-draft-acl? (and is-jwt? (= assurance-level 4))}))
48+
49+
(defn- build-error-message
50+
"Builds error message for invalid token."
51+
[token token-info]
52+
(let [{:keys [type assurance-level]} token-info
53+
saml-enabled? (config/enable-launchpad-saml-authentication)
54+
jwt-enabled? (config/enable-idfed-jwt-authentication)
55+
required-level (config/required-assurance-level)]
56+
(cond
57+
(not (or saml-enabled? jwt-enabled?))
58+
"Write operations are disabled. Both Launchpad SAML and IDFed JWT authentication are turned off."
59+
60+
(and (#{:jwt-level-4 :jwt-other} type) assurance-level jwt-enabled? (< assurance-level required-level))
61+
(format "Token [%s] has assurance_level %d, but level %d or higher is required."
62+
(common-util/scrub-token token) assurance-level required-level)
63+
64+
(and (#{:jwt-level-5 :jwt-level-4 :jwt-other} type) (not jwt-enabled?))
65+
(format "Token [%s] is a JWT token, but IDFed JWT authentication is disabled."
66+
(common-util/scrub-token token))
67+
68+
(and (= type :saml) (not saml-enabled?))
69+
(format "Token [%s] is a Launchpad SAML token, but SAML authentication is disabled."
70+
(common-util/scrub-token token))
71+
72+
:else
73+
(format "Token [%s] is not authorized for write operations."
74+
(common-util/scrub-token token)))))
75+
76+
(defn validate-write-token
77+
"Validates token is authorized for write operations. Level 4 JWT tokens require NON_NASA_DRAFT_USER ACL."
78+
([request-context]
79+
(validate-write-token request-context nil))
80+
([request-context provider-id]
81+
(let [token (:token request-context)]
82+
(when (and (config/launchpad-token-enforced)
83+
(not= token (transmit-config/echo-system-token)))
84+
85+
(let [token-info (get-token-info token)]
86+
(info (format "Token validation - Type: %s, Valid: %s" (:type token-info) (:valid? token-info)))
87+
88+
(when-not (:valid? token-info)
89+
(errors/throw-service-error :bad-request (build-error-message token token-info)))
90+
91+
(when (and provider-id (:requires-draft-acl? token-info))
92+
(acl/verify-non-nasa-draft-permission request-context :update :provider-object provider-id)))))))
93+

common-app-lib/src/cmr/common_app/config.clj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,28 @@
5151
"Write a HotSpotDiagnostic file to the path provided. If no path is provided,
5252
or the value of 'none' is given, then no file will be written."
5353
{:default ""})
54+
55+
(defconfig enable-idfed-jwt-authentication
56+
"Enable EDL Identity Federated JWT tokens with assurance_level for write operations.
57+
When true, JWT tokens with assurance_level 5 (Launchpad/PIV) will be accepted for ingest.
58+
When false, only traditional Launchpad SAML tokens are accepted for write operations.
59+
This allows gradual rollout of IDFed authentication and quick rollback if needed."
60+
{:default false
61+
:type Boolean})
62+
63+
(defconfig enable-launchpad-saml-authentication
64+
"Enable traditional Launchpad SAML tokens for write operations.
65+
When true, traditional Launchpad SAML tokens are accepted for ingest.
66+
When false, Launchpad SAML tokens are rejected (only IDFed JWT tokens accepted).
67+
This allows eventual deprecation of SAML tokens after full IDFed migration."
68+
{:default true
69+
:type Boolean})
70+
71+
(defconfig required-assurance-level
72+
"Minimum assurance level required for JWT tokens to perform write operations.
73+
Default is 4 (EDL+MFA). Only applies when enable-idfed-jwt-authentication is true.
74+
Assurance levels: 1=Social Login, 2=Social+MFA, 3=EDL, 4=EDL+MFA, 5=Launchpad/PIV
75+
Level 4 requires NON_NASA_DRAFT_USER ACL permission for the provider.
76+
Level 5 grants full access like traditional Launchpad SAML tokens."
77+
{:default 4
78+
:type Long})

ingest-app/src/cmr/ingest/api/bulk.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
(let [{:keys [body headers request-context]} request
2525
content (api-core/read-body! body)
2626
user-id (api-core/get-user-id request-context headers)]
27-
(lt-validation/validate-launchpad-token request-context)
27+
(lt-validation/validate-write-token request-context provider-id)
2828
(api-core/verify-provider-exists request-context provider-id)
2929
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)
3030
(let [task-id (bulk-update/validate-and-save-bulk-update
@@ -46,7 +46,7 @@
4646
(let [{:keys [body headers request-context]} request
4747
content (api-core/read-body! body)
4848
user-id (api-core/get-user-id request-context headers)]
49-
(lt-validation/validate-launchpad-token request-context)
49+
(lt-validation/validate-write-token request-context provider-id)
5050
(api-core/verify-provider-exists request-context provider-id)
5151
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)
5252
(let [task-id (gran-bulk-update/validate-and-save-bulk-granule-update

ingest-app/src/cmr/ingest/api/collections.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
(defn ingest-collection
4949
[provider-id native-id request]
5050
(let [{:keys [body content-type _params headers request-context]} request]
51-
(lt-validation/validate-launchpad-token request-context)
51+
(lt-validation/validate-write-token request-context provider-id)
5252
(api-core/verify-provider-exists request-context provider-id)
5353
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)
5454
(common-enabled/validate-write-enabled request-context "ingest")

ingest-app/src/cmr/ingest/api/core.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@
319319
:concept-type concept-type}
320320
(set-revision-id headers)
321321
(set-user-id request-context headers))]
322-
(lt-validation/validate-launchpad-token request-context)
322+
(lt-validation/validate-write-token request-context provider-id)
323323
(common-enabled/validate-write-enabled request-context "ingest")
324324
(verify-provider-exists request-context provider-id)
325325
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)

ingest-app/src/cmr/ingest/api/generic_documents.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
(:provider-id route-params))
120120
native-id (:native-id route-params)
121121
concept-type (concept-type->singular route-params)
122-
_ (lt-validation/validate-launchpad-token request-context)
122+
_ (lt-validation/validate-write-token request-context provider-id)
123123
_ (api-core/verify-provider-exists request-context provider-id)
124124
_ (if-not (is-draft-concept? request)
125125
(acl/verify-ingest-management-permission

ingest-app/src/cmr/ingest/api/granules.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
(defn ingest-granule
6262
[provider-id native-id request]
6363
(let [{:keys [body content-type headers request-context]} request]
64-
(lt-validation/validate-launchpad-token request-context)
64+
(lt-validation/validate-write-token request-context provider-id)
6565
(api-core/verify-provider-exists request-context provider-id)
6666
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)
6767
(common-enabled/validate-write-enabled request-context "ingest")
@@ -84,7 +84,7 @@
8484
:native-id native-id
8585
:concept-type :granule}
8686
headers)]
87-
(lt-validation/validate-launchpad-token request-context)
87+
(lt-validation/validate-write-token request-context provider-id)
8888
(api-core/verify-provider-exists request-context provider-id)
8989
(acl/verify-ingest-management-permission request-context :update :provider-object provider-id)
9090
(common-enabled/validate-write-enabled request-context "ingest")

0 commit comments

Comments
 (0)