summaryrefslogtreecommitdiff
path: root/src/scm/webid-oidc/access-token.scm
diff options
context:
space:
mode:
authorVivien Kraus <vivien@planete-kraus.eu>2021-09-20 11:25:29 +0200
committerVivien Kraus <vivien@planete-kraus.eu>2021-09-21 22:28:51 +0200
commite910b3ba2ded990a5193f7ea0cfad525332e4171 (patch)
treeb04e74e7c06e0a0fde5edd7ac0b8773db94cd515 /src/scm/webid-oidc/access-token.scm
parentdcd329af1ec765ca0fac97ef2dc18a3177d34083 (diff)
JWS: use GOOPS
Diffstat (limited to 'src/scm/webid-oidc/access-token.scm')
-rw-r--r--src/scm/webid-oidc/access-token.scm410
1 files changed, 113 insertions, 297 deletions
diff --git a/src/scm/webid-oidc/access-token.scm b/src/scm/webid-oidc/access-token.scm
index 7e67270..7c23126 100644
--- a/src/scm/webid-oidc/access-token.scm
+++ b/src/scm/webid-oidc/access-token.scm
@@ -29,7 +29,17 @@
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (ice-9 exceptions)
+ #:use-module (ice-9 receive)
+ #:use-module (oop goops)
#:declarative? #t
+ #:re-export
+ (
+ alg iat exp iss
+ token->jwt
+ decode
+ encode
+ issue
+ )
#:export
(
@@ -37,21 +47,7 @@
make-invalid-access-token
invalid-access-token?
- the-access-token
- access-token?
-
- access-token-alg
-
- access-token-webid
- access-token-iss
- access-token-aud
- access-token-iat
- access-token-exp
- access-token-client-id
- access-token-cnf/jkt
-
- access-token-decode
- issue-access-token
+ <access-token> webid aud client-id cnf/jkt
))
(define-exception-type
@@ -60,289 +56,109 @@
make-invalid-access-token
invalid-access-token?)
-;; The order is meaningful in this module, the-access-token reorders
-;; them.
-(define (the-access-token x)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (cond
- ((invalid-jws? error)
- (if (exception-with-message? error)
- (format #f (G_ "this is not an access token, because it is not even a JWS: ~a")
- (exception-message error))
- (format #f (G_ "this is not an access token, because it is not even a JWS"))))
- (else
- (if (exception-with-message? error)
- (format #f (G_ "this is not an access token: ~a")
- (exception-message error))
- (format #f (G_ "this is not an access token")))))))
- (raise-exception
- (make-exception
- (make-invalid-access-token)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (match (the-jws x)
- ((header . payload)
- (let examine-payload ((payload payload)
- (webid #f)
- (iss #f)
- (aud #f)
- (iat #f)
- (exp #f)
- (cnf #f)
- (client-id #f)
- (other-fields '()))
- (match payload
- (()
- (unless (and webid iss aud iat exp cnf client-id)
- ;; Missing some things
- (fail (format #f (G_ "the payload is missing ~s")
- `(,@(if webid '() '("webid"))
- ,@(if iss '() '("iss"))
- ,@(if aud '() '("aud"))
- ,@(if iat '() '("iat"))
- ,@(if exp '() '("exp"))
- ,@(if cnf '() '("cnf"))
- ,@(if client-id '() '("client_id"))))))
- `(,header
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (aud . "solid")
- (iat . ,(time-second (date->time-utc iat)))
- (exp . ,(time-second (date->time-utc exp)))
- (client_id . ,(uri->string client-id))
- (cnf . ,cnf)
- ,@(reverse other-fields))))
- ((('webid . (? string? (= string->uri (? uri? webid-given)))) payload ...)
- (examine-payload payload
- (or webid webid-given)
- iss aud iat exp cnf client-id other-fields))
- ((('webid . infringing) payload ...)
- (fail (format #f (G_ "the \"webid\" field should be an URI, ~s is given")
- infringing)))
- ((('iss . (? string? (= string->uri (? uri? iss-given)))) payload ...)
- (examine-payload payload webid
- (or iss iss-given)
- aud iat exp cnf client-id other-fields))
- ((('iss . infringing) payload ...)
- (fail (format #f (G_ "the \"iss\" field should be an URI, ~s is given")
- infringing)))
- ((('aud . "solid") payload ...)
- (examine-payload payload webid iss #t iat exp cnf client-id other-fields))
- ((('aud . infringing) payload ...)
- (fail (format #f (G_ "the \"aud\" field should be set to \"solid\", ~s is given")
- infringing)))
- ((('iat . (? (cute >= <> 0) (? integer? iat-given))) payload ...)
- (examine-payload payload webid iss aud
- (or iat (time-utc->date (make-time time-utc 0 iat-given)))
- exp cnf client-id other-fields))
- ((('iat . infringing) payload ...)
- (fail (format #f (G_ "the \"iat\" field should be a timestamp, ~s is given")
- infringing)))
- ((('exp . (? (cute >= <> 0) (? integer? exp-given))) payload ...)
- (examine-payload payload webid iss aud iat
- (or exp (time-utc->date (make-time time-utc 0 exp-given)))
- cnf client-id other-fields))
- ((('exp . infringing) payload ...)
- (fail (format #f (G_ "the \"exp\" field should be a timestamp, ~s is given")
- infringing)))
- ((('cnf . cnf) payload ...)
- (let examine-cnf ((data cnf)
- (jkt #f)
- (other-cnf-fields '()))
- (match data
- (()
- (unless jkt
- (fail (format #f (G_ "the \"cnf\" / \"jkt\" field is missing"))))
- (examine-payload payload webid iss aud iat exp
- `((jkt . ,jkt)
- ,@(reverse other-cnf-fields))
- client-id other-fields))
- ((('jkt . (? string? jkt-given)) data ...)
- (examine-cnf data (or jkt jkt-given other-cnf-fields) other-cnf-fields))
- ((('jkt . infringing) _ ...)
- (fail (format #f (G_ "the \"cnf\" / \"jkt\" field should be a string, ~s is given")
- infringing)))
- ((field data ...)
- (examine-cnf data jkt `(,field ,@other-cnf-fields)))
- (data
- (fail (format #f (G_ "the \"cnf\" field should be an object, ~s is given")
- data))))))
- ((('client_id . (? string? (= string->uri (? uri? client-id-given)))) payload ...)
- (examine-payload payload webid iss aud iat exp cnf
- (or client-id client-id-given)
- other-fields))
- ((('client_id . infringing) payload ...)
- (fail (format #f (G_ "the \"client_id\" field should be an URI, ~s is given")
- infringing)))
- ((field payload ...)
- (examine-payload payload webid iss aud iat exp cnf client-id
- `(,field ,@other-fields))))))
- (else
- (scm-error 'wrong-type-arg "the-access-token"
- "expected a pair of lists"
- '()
- (list x)))))))
-
-(define (access-token? x)
- (false-if-exception (the-access-token x)))
-
-(define (access-token-alg code)
- (match (the-access-token code)
- ((header . _)
- (string->symbol (assq-ref header 'alg)))))
-
-(define (access-token-webid code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'webid)))))
-
-(define (access-token-iss code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'iss)))))
+(define-class <access-token> (<time-bound-token> <oidc-token>)
+ (webid #:init-keyword #:webid #:accessor webid)
+ (aud #:init-keyword #:aud #:accessor aud)
+ (client-id #:init-keyword #:client-id #:accessor client-id)
+ (cnf/jkt #:init-keyword #:cnf/jkt #:accessor cnf/jkt))
-(define (access-token-aud code)
- (match (the-access-token code)
- ((_ . payload)
- (assq-ref payload 'aud))))
-
-(define (access-token-iat code)
- (match (the-access-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'iat))))))
-
-(define (access-token-exp code)
- (match (the-access-token code)
- ((_ . payload)
- (time-utc->date
- (make-time time-utc 0 (assq-ref payload 'exp))))))
-
-(define (access-token-client-id code)
- (match (the-access-token code)
- ((_ . payload)
- (string->uri (assq-ref payload 'client-id)))))
-
-(define (access-token-cnf/jkt code)
- (match (the-access-token code)
- ((_ . payload)
- (assq-ref (assq-ref payload 'cnf) 'jkt))))
-
-(define* (access-token-decode str #:key (http-get http-get))
+(define-method (initialize (token <access-token>) initargs)
(with-exception-handler
(lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "the access token is invalid: ~a")
- (exception-message error))
- (format #f (G_ "the access token is invalid")))))
- (raise-exception
- (make-exception
- (make-invalid-access-token)
- (make-exception-with-message final-message)
- error))))
+ (raise-exception
+ (make-exception
+ (make-invalid-access-token)
+ (make-exception-with-message
+ (if (exception-with-message? error)
+ (format #f (G_ "invalid access token: ~a")
+ (exception-message error))
+ (G_ "invalid access token")))
+ error)))
(lambda ()
- (jws-decode
- str
- (lambda (token)
- (let* ((iss (access-token-iss token))
- (cfg
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the identity provider configuration: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the identity provider configuratioon")))))
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (get-oidc-configuration
- (uri-host iss)
- #:userinfo (uri-userinfo iss)
- #:port (uri-port iss)
- #:http-get http-get))))
- (jwks
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "I cannot query the identity provider public keys: ~a")
- (exception-message error))
- (format #f (G_ "I cannot query the identity provider public keys")))))
- (raise-exception
- (make-exception
- (make-cannot-query-identity-provider iss)
- (make-exception-with-message final-message)
- error))))
- (lambda ()
- (oidc-configuration-jwks cfg #:http-get http-get)))))
- (let ((iat (access-token-iat token))
- (exp (access-token-exp token))
- (current-date ((p:current-date))))
- (let ((iat-s (time-second (date->time-utc iat)))
- (exp-s (time-second (date->time-utc exp)))
- (current-s (time-second (date->time-utc current-date))))
- (when (>= iat-s (+ current-s 5))
- (let ((final-message
- (format #f (G_ "the access token is signed in the future, ~a, relative to current ~a")
- (date->string iat)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-signed-in-future iat current-date)
- (make-exception-with-message final-message)))))
- (when (>= current-s exp-s)
- (let ((final-message
- (format #f (G_ "the access token expired ~a, which is in the past (from ~a)")
- (date->string exp)
- (date->string current-date))))
- (raise-exception
- (make-exception
- (make-expired exp current-date)
- (make-exception-with-message final-message)))))))
- jwks))))))
-
-(define (access-token-encode access-token key)
- (with-exception-handler
- (lambda (error)
- (let ((final-message
- (if (exception-with-message? error)
- (format #f (G_ "cannot encode the access token: ~a")
- (exception-message error))
- (format #f (G_ "cannot encode the access token")))))
- (raise-exception
- (make-exception-with-message final-message))))
- (lambda ()
- (jws-encode access-token key))))
-
-(define* (issue-access-token
- issuer-key
- #:key
- (webid #f)
- (iss #f)
- (validity 3600)
- (client-key #f)
- (cnf/jkt #f)
- (client-id #f))
- (when client-key
- (set! cnf/jkt (jkt client-key)))
- (let* ((iat (time-second (date->time-utc ((p:current-date)))))
- (exp (+ iat validity)))
- (jws-encode
- (the-access-token
- `(((alg . ,(symbol->string (alg issuer-key))))
- . ((webid . ,(uri->string webid))
- (iss . ,(uri->string iss))
- (aud . "solid")
- (iat . ,iat)
- (exp . ,exp)
- (cnf . ((jkt . ,cnf/jkt)))
- (client_id . ,(uri->string client-id)))))
- issuer-key)))
+ (next-method)
+ (let-keywords
+ initargs #t
+ ((webid #f)
+ (aud "solid")
+ (client-id #f)
+ (cnf/jkt #f)
+ (client-key #f)
+ (jwt-header #f)
+ (jwt-payload #f))
+ (let do-initialize ((webid webid)
+ (aud aud)
+ (client-id client-id)
+ (cnf/jkt cnf/jkt)
+ (client-key client-key)
+ (jwt-header jwt-header)
+ (jwt-payload jwt-payload))
+ (cond
+ ((string? webid)
+ (do-initialize (string->uri webid)
+ aud
+ client-id
+ cnf/jkt
+ client-key
+ jwt-header
+ jwt-payload))
+ ((string? client-id)
+ (do-initialize webid
+ aud
+ (string->uri client-id)
+ cnf/jkt
+ client-key
+ jwt-header
+ jwt-payload))
+ ((and (not cnf/jkt) client-key)
+ (do-initialize webid aud client-id (jkt client-key) #f jwt-header jwt-payload))
+ ((and webid client-id cnf/jkt)
+ (unless (uri? webid)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:webid should be an URI")
+ '()
+ (list webid)))
+ (unless (uri? client-id)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:client-id should be an URI")
+ '()
+ (list client-id)))
+ (unless (string? cnf/jkt)
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:cnf/jkt should be a string")
+ '()
+ (list cnf/jkt)))
+ (unless (equal? aud "solid")
+ (scm-error 'wrong-type-arg "make"
+ (G_ "#:aud should be exactly \"solid\"")
+ '()
+ (list aud)))
+ (slot-set! token 'webid webid)
+ (slot-set! token 'aud aud)
+ (slot-set! token 'client-id client-id)
+ (slot-set! token 'cnf/jkt cnf/jkt))
+ ((and jwt-header jwt-payload)
+ (do-initialize (assq-ref jwt-payload 'webid)
+ (assq-ref jwt-payload 'aud)
+ (assq-ref jwt-payload 'client_id)
+ (assq-ref (assq-ref jwt-payload 'cnf) 'jkt)
+ #f #f #f))
+ (else
+ (raise-exception
+ (make-exception
+ (make-invalid-jws)
+ (make-exception-with-message
+ (G_ "when making an access token either its required fields (#:alg, #:webid, #:iss, #:aud, #:client-id, #:cnf/jkt, #:iat and #:exp) or (#:jwt-header and #:jwt-payload) should be passed")))))))))))
+
+(define-method (token->jwt (token <access-token>))
+ (receive (base-header base-payload)
+ (next-method)
+ (values
+ base-header
+ `((webid . ,(uri->string (webid token)))
+ (iss . ,(uri->string (iss token)))
+ (aud . ,(aud token))
+ (client_id . ,(uri->string (client-id token)))
+ (cnf . ((jkt . ,(cnf/jkt token))))
+ (iat . ,(time-second (date->time-utc (iat token))))
+ (exp . ,(time-second (date->time-utc (exp token))))
+ ,@base-payload))))