From aa27a940647e867746f955772b4601e6cfce9b10 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sun, 26 Apr 2020 13:27:50 +0300 Subject: Change: yuck.md to use more Subplot syntax instead of yarn --- Makefile | 2 + yuck.md | 332 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 322 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index bc28ba4..e4cdf8f 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,5 @@ htmls = $(srcs:.md=.html) sp-docgen $< -o $@ all: $(pdfs) $(htmls) + +yuck.html: yuck.yaml diff --git a/yuck.md b/yuck.md index b4154fd..162c585 100644 --- a/yuck.md +++ b/yuck.md @@ -1,5 +1,8 @@ # Overview +Yuck may some day become an OpenID Connect and OAuth server. This is +just a sketcth for what it might look like. + ## Concepts Some basic concepts in this document: @@ -9,19 +12,23 @@ Some basic concepts in this document: * authentication: proving your identity * authorization: giving permission to do something -## The protocols: OAuth 2.0 and OpenID Connect +## The protocols: OAuth and OpenID Connect + +[OpenID Connect]: https://openid.net/specs/openid-connect-core-1_0.html +[OAuth]: https://tools.ietf.org/html/rfc6749 -The OAuth 2.0 protocol is for authorization, not authentication, and +The [OAuth][] 2.0 protocol is for authorization, not authentication, and assumes an already existing way to authenticate users. It's mainly for giving a service or application permission to do something on your behalf. -The OpenID Connect (OIDC) protocol is for authenticating yourself to -one service or application by using a third party service. This allows -one authentication service (or identity provider) be used for any -number of other services or applications. Further, since the identity -provider can keep a login session open independently of the other -services and applications, this provides a single sign-on experience. +The [OpenID Connect][] 1.0 (OIDC) protocol is for authenticating yourself +to one service or application by using a third party service. This +allows one authentication service (or identity provider) be used for +any number of other services or applications. Further, since the +identity provider can keep a login session open independently of the +other services and applications, this provides a single sign-on +experience. ## Entities involved in the protocols @@ -38,7 +45,21 @@ The protocols involves the following entities: access to them via a web API * the **identity provider** (IDP), which authenticates the user -# OIDC protocol +# The protocols + + +Yuck only supports specific versions and subsets of the protocols it +implements, chosen based mainly from the point of security. FIXME: +expand this with explanations of why the specific choices have been +made and why other subsets (grants, flows) are not chosen. + +## The OAuth 2.0 protocol: client credentials grant + +See [RFC8252][] for a description of the client credentials grant. + +[RFC8252]: https://tools.ietf.org/html/rfc8252 + +## The OIDC 1.0 protocol: authorization code This augments the plain OIDC with cookies: @@ -49,7 +70,7 @@ This augments the plain OIDC with cookies: login session -## Successful resource access by a logged-out user +### Successful resource access by a logged-out user ~~~plantuml @startuml @@ -174,7 +195,7 @@ user <- browser : show what the user wanted @enduml ~~~ -## Successful resource access by a logged-in user +### Successful resource access by a logged-in user ~~~plantuml @startuml @@ -221,7 +242,7 @@ user <- browser : show what the user wanted ~~~ -## Successful request when an access cookie has expired +### Successful request when an access cookie has expired ~~~plantuml @startuml @@ -295,11 +316,298 @@ user <- browser : show what the user wanted ~~~ +### FIXME: unhappy paths +# Acceptance criteria + +This chapter captures the acceptance criteria for Yuck in the form of +*scenarios* using the Subplot language. Subplot allows Yuck to be +automatically verified against the criteria. + +## Server reports its version + +~~~scenario +given a running Yuck at https://auth.example.com +when I do GET /version without a token +then response status code is 200 +~~~ + +## OAuth2 client credentials grant + +An example request: + +~~~ +POST /token HTTP/1.1 +Authorization: Basic USERPASS +Content-Type: application/x-www-form-urlencoded + +grant_type=client_credentials&scope=foo+bar+foobar +~~~ + +The `USERPASS` has the client id and secret encoded as is usual for +[HTTP Basic authentication][]. + +[HTTP Basic authentication]: https://en.wikipedia.org/wiki/Basic_access_authentication + +Yuck checks the client id and secret against known, pre-registered +clients, and that the `grant_type` URL parameter is +`client_credential`, and returns a signed JWT access token, if all is +OK. + +~~~json +{ + "iss": "https://auth.example.com", + "sub": "", + "aud": "CLIENT_ID", + "exp": 123456, + "scope": "foo bar" +} +~~~ + +The fields are as follows: + +* `sub` is empty, because this interaction involves not end-user or + subject +* `iss` is the configured URL from the Yuck configuration +* `aud` is the requesting client id +* `exp` is the timestamp in Unix seconds when the token stops being + valid +* `scope` is the space-delimieted list of scopes the client is allowed + to have and has requested + +### Get token with OAuth using client credentials + +~~~scenario +given a running Yuck at https://auth.example.com +and I am a registered OAuth client +and I am authorized to have scope foo +when I do POST /token with grant_type=client_credential&scope=foo+bar +then response status code is 200 +and response has header content-type: application/json +and response body is a valid, signed JWT token +and token sub is empty +and token has iss: "https://auth.example.com" +and token has aud set to my client id +and token expires at least 60 seconds in the future +and token has scope: "foo" +~~~ + + +### End-user interactive login + +This is a bit long. + +~~~scenario +given a running Yuck at https://auth.example.com +and I am registered as tomjon with password hunter2 +and I am authorized to have scope foo +and the application is registered at https://app.example.com +when I go to the application login page +then response status code is 302 +and response has header Location: https://auth.example.com/auth?response_type=code&scope=openid+foo&client_id=facade&state=RANDOM&redirect_uri=https://app.example.com/callback + +when I follow the redirect +then response status code is 200 +and response has header content-type: text/html +and body HTML form has username +and body HTML form has password + +when I do POST /auth with username=tomjon&password=wrong +then response status code is 401 + +when I do POST /auth with username=tomjon&password=hunter2 +then response status code is 302 +and response has header Location: https://app.example.com/callback?... +and I remember the Location URL code parameter + +when I follow the redirect + +when the application requests a token using remembered code +then response status code is 200 +and response has header content-type: application/json +and body has field access_token +and body has field token_type: Bearer +and body has field expires_in + +and access_token is a valid, signed JWT +and access_token has scope: "foo" +and access_token has sub: "tomjon" +~~~ + + +## Manage clients, users, applications via API + +~~~yarn +SCENARIO manage clients, users, applications +GIVEN an RSA key pair for token signing +AND a Qvisqve configuration for "https://qvisqve.example.com" +AND Qvisqve configuration has a token lifetime of 3600 +AND a running Qvisqve instance +AND an access token for admin with scopes +... uapi_clients_post +... uapi_clients_get +... uapi_clients_id_get +... uapi_clients_id_put +... uapi_clients_id_secret_put +... uapi_clients_id_delete +... uapi_users_post +... uapi_users_get +... uapi_users_id_get +... uapi_users_id_put +... uapi_users_id_secret_put +... uapi_users_id_delete +... uapi_applications_post +... uapi_applications_get +... uapi_applications_id_get +... uapi_applications_id_put +... uapi_applications_id_delete +~~~ +First, manage clients. + +~~~yarn +WHEN client requests GET /clients using token +THEN HTTP status code is 200 OK +AND Content-Type is application/json +AND JSON body matches +... { +... "resources": [] +... } + +WHEN client requests POST /clients with token and body +... { +... "id": "james" +... } +THEN HTTP status code is 201 Created +AND Location is https://qvisqve.example.com/clients/james + +WHEN client requests PUT /clients/james/secret with token and body +... { "secret": "hunter2" } +THEN HTTP status code is 200 OK + +WHEN client requests GET /clients using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": ["james"] +... } + +WHEN client requests GET /clients/james using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "id": "james" +... } + +WHEN client requests DELETE /clients/james with token +THEN HTTP status code is 200 OK +WHEN client requests GET /clients/james using token +THEN HTTP status code is 404 Not Found +WHEN client requests GET /clients using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": [] +... } +~~~ + +Then, manage users. + +~~~yarn +WHEN client requests GET /users using token +THEN HTTP status code is 200 OK +AND Content-Type is application/json +AND JSON body matches +... { +... "resources": [] +... } + +WHEN client requests POST /users with token and body +... { +... "id": "sherlock" +... } +THEN HTTP status code is 201 Created +AND Location is https://qvisqve.example.com/users/sherlock + +WHEN client requests PUT /users/sherlock/secret with token and body +... { "secret": "hunter2" } +THEN HTTP status code is 200 OK + +WHEN client requests GET /users using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": ["sherlock"] +... } + +WHEN client requests GET /users/sherlock using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "id": "sherlock" +... } + +WHEN client requests DELETE /users/sherlock with token +THEN HTTP status code is 200 OK +WHEN client requests GET /users/sherlock using token +THEN HTTP status code is 404 Not Found +WHEN client requests GET /users using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": [] +... } +~~~ + +Then, manage applications. + +~~~yarn +WHEN client requests GET /applications using token +THEN HTTP status code is 200 OK +AND Content-Type is application/json +AND JSON body matches +... { +... "resources": [] +... } + +WHEN client requests POST /applications with token and body +... { +... "id": "MI6", +... "callbacks": ["https://mi6.example.com/callback"] +... } +THEN HTTP status code is 201 Created +AND Location is https://qvisqve.example.com/applications/MI6 + +WHEN client requests GET /applications using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": ["MI6"] +... } + +WHEN client requests GET /applications/MI6 using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "id": "MI6", +... "callbacks": ["https://mi6.example.com/callback"] +... } + +WHEN client requests DELETE /applications/MI6 with token +THEN HTTP status code is 200 OK +WHEN client requests GET /applications/MI6 using token +THEN HTTP status code is 404 Not Found +WHEN client requests GET /applications using token +THEN HTTP status code is 200 OK +AND JSON body matches +... { +... "resources": [] +... } +~~~ --- title: Yuck or OIDC +bindings: yuck.yaml ... -- cgit v1.2.1