diff options
author | Vivien Kraus <vivien@planete-kraus.eu> | 2021-08-07 22:45:06 +0200 |
---|---|---|
committer | Vivien Kraus <vivien@planete-kraus.eu> | 2021-08-13 01:06:38 +0200 |
commit | db55d55e5c36c940986f437d26da1ff3c601c3b4 (patch) | |
tree | 0ecec5b2bd0b0bc6a02981a7c3b9ccafbb891c3b /tests | |
parent | 0b5d0622e11c1f919ce660893067d3121e2583a0 (diff) |
Make a better client API
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/client-authorization.scm | 134 | ||||
-rw-r--r-- | tests/client-token.scm | 137 | ||||
-rw-r--r-- | tests/client-workflow.scm | 140 |
4 files changed, 141 insertions, 273 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am index 086ccbd..e09ad57 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -57,8 +57,7 @@ TESTS = %reldir%/load-library.scm \ %reldir%/token-endpoint-refresh.scm \ %reldir%/provider-confirmation.scm \ %reldir%/resource-server.scm \ - %reldir%/client-authorization.scm \ - %reldir%/client-token.scm \ + %reldir%/client-workflow.scm \ %reldir%/client-manifest-not-modified.scm \ %reldir%/server-content.scm \ %reldir%/server-path.scm \ diff --git a/tests/client-authorization.scm b/tests/client-authorization.scm deleted file mode 100644 index af95893..0000000 --- a/tests/client-authorization.scm +++ /dev/null @@ -1,134 +0,0 @@ -;; webid-oidc, implementation of the Solid specification -;; Copyright (C) 2020, 2021 Vivien Kraus - -;; This program is free software: you can redistribute it and/or modify -;; it under the terms of the GNU Affero General Public License as -;; published by the Free Software Foundation, either version 3 of the -;; License, or (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU Affero General Public License for more details. - -;; You should have received a copy of the GNU Affero General Public License -;; along with this program. If not, see <https://www.gnu.org/licenses/>. - -(use-modules (webid-oidc client) - (webid-oidc testing) - ((webid-oidc stubs) #:prefix stubs:) - (web uri) - (web response) - (srfi srfi-19) - (ice-9 optargs) - (ice-9 receive) - (ice-9 hash-table)) - -;; We need to test different things. - -;; 1. It works when passed a host -;; 2. It works when passed a webid with foreign identity providers -;; 3. It works when passed a webid without foreign identity providers - -(with-test-environment - "client-authorization" - (lambda () - (define* (http-get uri #:key (headers '())) - (cond - ;; 1. We pass a host name - ((equal? uri (string->uri "https://case-1.client-authorization.scm/.well-known/openid-configuration")) - (values - (build-response #:headers `((content-type application/json))) - (stubs:scm->json-string - `((jwks_uri . "https://case-1.client-authorization.scm/keys") - (authorization_endpoint . "https://case-1.client-authorization.scm/authorize") - (token_endpoint . "https://case-1.client-authorization.scm/token"))))) - ;; It’s not a webid - ((equal? uri (string->uri "https://case-1.client-authorization.scm")) - (values - (build-response #:code 404 #:reason-phrase "Not Found") - #f)) - ;; 2. We first dereference the webid - ((equal? uri (string->uri "https://case-2.client-authorization.scm/profile/card#me")) - (values - (build-response #:headers `((content-type text/turtle))) - "<#me> <http://www.w3.org/ns/solid/terms#oidcIssuer> <https://one.identity.provider>, <https://another.identity.provider> .")) - ;; and we get the config of all IPs - ((equal? uri (string->uri "https://case-2.client-authorization.scm/.well-known/openid-configuration")) - (values - (build-response #:headers `((content-type application/json))) - (stubs:scm->json-string - `((jwks_uri . "https://case-2.client-authorization.scm/keys") - (authorization_endpoint . "https://case-2.client-authorization.scm/authorize") - (token_endpoint . "https://case-2.client-authorization.scm/token"))))) - ((equal? uri (string->uri "https://one.identity.provider/.well-known/openid-configuration")) - (values - (build-response #:headers `((content-type application/json))) - (stubs:scm->json-string - `((jwks_uri . "https://one.identity.provider/keys") - (authorization_endpoint . "https://one.identity.provider/authorize") - (token_endpoint . "https://one.identity.provider/token"))))) - ((equal? uri (string->uri "https://another.identity.provider/.well-known/openid-configuration")) - (values - (build-response #:headers `((content-type application/json))) - (stubs:scm->json-string - `((jwks_uri . "https://another.identity.provider/keys") - (authorization_endpoint . "https://another.identity.provider/authorize") - (token_endpoint . "https://another.identity.provider/token"))))) - ;; 3. The webid has no IPs. - ((equal? uri (string->uri "https://case-3.client-authorization.scm/profile/card#me")) - (values - (build-response #:headers `((content-type text/turtle))) - "")) - ;; so we query the host of the webid. - ((equal? uri (string->uri "https://case-3.client-authorization.scm/.well-known/openid-configuration")) - (values - (build-response #:headers `((content-type application/json))) - (stubs:scm->json-string - `((jwks_uri . "https://case-3.client-authorization.scm/keys") - (authorization_endpoint . "https://case-3.client-authorization.scm/authorize") - (token_endpoint . "https://case-3.client-authorization.scm/token"))))) - (else - (format (current-error-port) "Unexpected GET query of URI ~a.\n" (uri->string uri)) - (exit 1)))) - (let ((case-1 (authorize "case-1.client-authorization.scm" - #:client-id "https://app.client-authorization.scm" - #:redirect-uri "https://app.client-authorization.scm/redirected" - #:state "integrity&check" - #:http-get http-get)) - (case-2 (authorize "https://case-2.client-authorization.scm/profile/card#me" - #:client-id "https://app.client-authorization.scm" - #:redirect-uri "https://app.client-authorization.scm/redirected" - #:state "integrity&check" - #:http-get http-get)) - (case-3 (authorize "https://case-3.client-authorization.scm/profile/card#me" - #:client-id "https://app.client-authorization.scm" - #:redirect-uri "https://app.client-authorization.scm/redirected" - #:state "integrity&check" - #:http-get http-get)) - (expected-1 - `(("https://case-1.client-authorization.scm" - . ,(string->uri "https://case-1.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check")))) - (expected-2 - `(("https://case-2.client-authorization.scm" - . ,(string->uri "https://case-2.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check")) - ("https://one.identity.provider" - . ,(string->uri "https://one.identity.provider/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check")) - ("https://another.identity.provider" - . ,(string->uri "https://another.identity.provider/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check")))) - (expected-3 - `(("https://case-3.client-authorization.scm" - . ,(string->uri "https://case-3.client-authorization.scm/authorize?client_id=https%3A%2F%2Fapp.client-authorization.scm&redirect_uri=https%3A%2F%2Fapp.client-authorization.scm%2Fredirected&state=integrity%26check"))))) - (unless (equal? case-1 expected-1) - (format (current-error-port) "Case 1 failed:\n~s\n~s\n\n" - case-1 expected-1) - (exit 2)) - (unless (equal? (hash-map->list cons (alist->hash-table case-2)) - (hash-map->list cons (alist->hash-table expected-2))) - (format (current-error-port) "Case 2 failed:\n~s\n~s\n\n" - case-2 expected-2) - (exit 3)) - (unless (equal? case-3 expected-3) - (format (current-error-port) "Case 3 failed:\n~s\n~s\n\n" - case-3 expected-3) - (exit 4))))) diff --git a/tests/client-token.scm b/tests/client-token.scm deleted file mode 100644 index 576019a..0000000 --- a/tests/client-token.scm +++ /dev/null @@ -1,137 +0,0 @@ -;; webid-oidc, implementation of the Solid specification -;; Copyright (C) 2020, 2021 Vivien Kraus - -;; This program is free software: you can redistribute it and/or modify -;; it under the terms of the GNU Affero General Public License as -;; published by the Free Software Foundation, either version 3 of the -;; License, or (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU Affero General Public License for more details. - -;; You should have received a copy of the GNU Affero General Public License -;; along with this program. If not, see <https://www.gnu.org/licenses/>. - -(use-modules (webid-oidc client) - (webid-oidc testing) - (webid-oidc token-endpoint) - (webid-oidc jwk) - (webid-oidc authorization-code) - (webid-oidc oidc-configuration) - (webid-oidc jws) - (webid-oidc oidc-id-token) - ((webid-oidc parameters) #:prefix p:) - (web uri) - (web request) - (web response) - (srfi srfi-19) - (ice-9 optargs) - (ice-9 receive) - (ice-9 hash-table)) - -(with-test-environment - "client-token" - (lambda () - (define the-current-time 0) - (parameterize ((p:current-date (lambda () the-current-time))) - (define issuer-key (generate-key #:n-size 2048)) - (define issuer-configuration - (make-oidc-configuration - "https://issuer.client-token.scm/keys" - "https://issuer.client-token.scm/authorize" - "https://issuer.client-token.scm/token")) - (define token-endpoint (make-token-endpoint - (string->uri "https://issuer.client-token.scm/token") - (string->uri "https://issuer.client-token.scm") - 'RS256 - issuer-key - 3600)) - (define client-key (generate-key #:n-size 2048)) - (define authorization-code - (issue-authorization-code 'RS256 issuer-key 120 - (string->uri "https://client-token.scm/profile/card#me") - (string->uri "https://app.client-token.scm/app#id"))) - (define* (http-get uri #:key (headers '())) - (cond - ((equal? uri (string->uri "https://issuer.client-token.scm/.well-known/openid-configuration")) - (serve-oidc-configuration - (time-utc->date (make-time time-utc 0 (+ the-current-time 3600))) - issuer-configuration)) - ((equal? uri (string->uri "https://issuer.client-token.scm/keys")) - (serve-jwks - (time-utc->date (make-time time-utc 0 (+ the-current-time 3600))) - (make-jwks (list issuer-key)))) - (else - (format (current-error-port) "GET request to ~a: error.\n" (uri->string uri)) - (exit 1)))) - (define* (http-post uri #:key (body #f) (headers '())) - (unless (equal? uri (oidc-configuration-token-endpoint issuer-configuration)) - (format (current-error-port) - "Wrong URI for token negociation: ~a (expected ~a).\n" - (uri->string uri) - (uri->string - (oidc-configuration-token-endpoint - issuer-configuration))) - (exit 2)) - (unless (equal? body (format #f "grant_type=authorization_code&code=~a" - authorization-code)) - (format (current-error-port) - "Wrong body: ~s\n" body) - (exit 3)) - (unless (equal? - (assoc-ref headers 'content-type) - '(application/x-www-form-urlencoded)) - (format (current-error-port) - "Wrong content type: ~s\n" (assoc-ref headers 'content-type)) - (exit 4)) - (let ((request - (build-request uri - #:method 'POST - #:headers headers - #:port (open-input-string body))) - (request-body body)) - (receive (response response-body user error) - (token-endpoint request request-body) - (values response response-body)))) - (let ((response - (token "https://issuer.client-token.scm" - client-key - #:authorization-code authorization-code - #:http-get http-get - #:http-post http-post))) - (let ((id-token (assq-ref response 'id_token)) - (access-token (assq-ref response 'access_token)) - (token-type (assq-ref response 'token_type)) - (token-expiration (assq-ref response 'expires_in)) - (refresh-token (assq-ref response 'refresh_token))) - (let ((id-token-dec (id-token-decode id-token #:http-get http-get)) - (access-token-dec (jws-decode access-token (lambda (jws) issuer-key)))) - (unless id-token-dec - (format (current-error-port) "Could not decode the ID token from ~s (~s)" - id-token response) - (exit 5)) - (unless access-token-dec - (format (current-error-port) "Could not decode the access token from ~s (~s)" - access-token response) - (exit 6)) - (unless refresh-token - (format (current-error-port) "There does not seem to be a refresh token in ~s" - response) - (exit 6)) - (unless (equal? (id-token-webid id-token-dec) - (string->uri "https://client-token.scm/profile/card#me")) - (exit 7)) - (unless (equal? (id-token-iss id-token-dec) - (string->uri "https://issuer.client-token.scm")) - (exit 8)) - (unless (equal? (id-token-aud id-token-dec) - (string->uri "https://app.client-token.scm/app#id")) - (exit 9)) - ;; It’s not the job of the client to check that the access - ;; token is correct; TODO: add a check with a resource - ;; server. - - ;; TODO: try to negociate a refresh token. - )))))) diff --git a/tests/client-workflow.scm b/tests/client-workflow.scm new file mode 100644 index 0000000..04a4455 --- /dev/null +++ b/tests/client-workflow.scm @@ -0,0 +1,140 @@ +;; webid-oidc, implementation of the Solid specification +;; Copyright (C) 2021 Vivien Kraus + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU Affero General Public License as +;; published by the Free Software Foundation, either version 3 of the +;; License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU Affero General Public License for more details. + +;; You should have received a copy of the GNU Affero General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +(use-modules ((webid-oidc client) #:prefix client:) + ((webid-oidc client accounts) #:prefix client:) + ((webid-oidc jwk) #:prefix jwk:) + (webid-oidc testing) + ((webid-oidc stubs) #:prefix stubs:) + ((webid-oidc refresh-token) #:prefix refresh:) + ((webid-oidc simulation) #:prefix sim:) + ((webid-oidc parameters) #:prefix p:) + (web uri) + (web request) + (web response) + (srfi srfi-19) + (srfi srfi-26) + (ice-9 optargs) + (ice-9 receive) + (ice-9 hash-table) + (ice-9 match)) + +;; In this example, a user firsts requests an account, then logs in +;; with a refresh token, then logs out, but we can still revive per +;; account, then the refresh token gets banned. + +(define (display-log simulation) + (format (current-error-port) "Log:\n") + (for-each + (match-lambda + ((request request-body response response-body) + (format (current-error-port) "~s ~s (~s): ~s ~s\n" + (request-method request) + (uri->string (request-uri request)) + request-body + (response-code response) + (response-reason-phrase response)))) + (sim:simulation-scroll-log! simulation)) + (exit 42)) + +(with-test-environment + "client-workflow" + (lambda () + (let ((simulation (sim:make-simulation))) + (sim:add-server! simulation + (string->uri "https://server@client-workflow.scm") + (string->uri "https://server@client-workflow.scm/alice#me")) + (sim:add-client! simulation + (string->uri "https://client@client-workflow.scm") + (string->uri "https://client@client-workflow.scm/id") + (string->uri "https://client@client-workflow.scm/authorized") + "Client workflow test" + (string->uri "https://client@client-workflow.scm/about")) + (let ((client (client:make-client + (string->uri "https://client@client-workflow.scm/id") + (jwk:generate-key #:n-size 2048) + (string->uri "https://client@client-workflow.scm/authorized")))) + (parameterize ((p:current-date 0) + (client:authorization-process + (lambda* (uri #:key issuer) + (sim:grant-authorization simulation uri)))) + (receive (response response-body) + (let ((handler + (client:request client #f + (string->uri "https://server@client-workflow.scm") + #:http-request (cute sim:request simulation <...>)))) + (handler (build-request (string->uri "https://server@client-workflow.scm/")) + #f)) + (unless (eqv? (response-code response) 200) + ;; Only Alice can read that resource. + (exit 3))) + (match (sim:simulation-scroll-log! simulation) + ;; 1. The client gets the oidc configuration of the + ;; server. + + ;; 2. The browser gets redirected to the authorization + ;; URI and POSTs the authorization form. The server makes + ;; a request to the client ID, which replies first. + + ;; 3. The authorization request completes. + + ;; 4. The client exchanges the authorization code for a + ;; refresh token. + + ;; 5. and 6. The client decodes the ID token and requests + ;; the server keys. + + ;; 7. and 8. While the client is waiting for the final response to + ;; complete, the server checks the access token validity by + ;; querying the identity provider for its key. + + ;; 9. The client sends the signed request to the / URI of + ;; the server. + (((get-oidc-config-request _ get-oidc-config-response _) + (get-client-id-request _ get-client-id-response _) + (authorization-request _ authorization-response _) + (token-request _ token-response _) + _ _ ;; the client gets the key + _ _ ;; the resource server gets the key + (final-request _ final-response _)) + (unless + (and + ;; 1. Get the authorization endpoint. + (equal? (request-uri get-oidc-config-request) + (string->uri "https://server@client-workflow.scm/.well-known/openid-configuration")) + (eqv? (response-code get-oidc-config-response) 200) + ;; 2. The server checks the client ID. + (equal? (request-uri get-client-id-request) + (string->uri "https://client@client-workflow.scm/id")) + (eqv? (response-code get-client-id-response) 200) + ;; 3. The authorization request completes. + (string-prefix? + "https://server@client-workflow.scm/authorize?" + (uri->string (request-uri authorization-request))) + (eq? (request-method authorization-request) 'POST) + (eqv? (response-code authorization-response) 302) + (string-prefix? + "https://client@client-workflow.scm/authorized?" + (uri->string (response-location authorization-response))) + ;; 4. Token negociation. + (equal? (request-uri token-request) + (string->uri "https://server@client-workflow.scm/token")) + (eqv? (response-code token-response) 200) + ;; 5. The final request. + (equal? (request-uri final-request) + (string->uri "https://server@client-workflow.scm/")) + (eqv? (response-code final-response) 200)) + (exit 4))))))))) |