From 4237caa755dd4bf1e2c9d02993ab42bb2bd55716 Mon Sep 17 00:00:00 2001
From: eikek <eike.kettner@posteo.de>
Date: Mon, 6 Sep 2021 11:41:40 +0200
Subject: [PATCH] Add some documentation for OIDC

---
 .../src/main/resources/docspell-openapi.yml   | 53 ++++++++++++
 .../src/main/resources/reference.conf         |  2 +-
 website/site/content/docs/api/intro.md        |  8 ++
 website/site/content/docs/configure/_index.md | 85 +++++++++++++++++++
 website/site/content/docs/features/_index.md  |  7 +-
 5 files changed, 152 insertions(+), 3 deletions(-)

diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml
index c6523a95..b6d5112c 100644
--- a/modules/restapi/src/main/resources/docspell-openapi.yml
+++ b/modules/restapi/src/main/resources/docspell-openapi.yml
@@ -42,6 +42,7 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/VersionInfo"
+
   /open/auth/login:
     post:
       operationId: "open-auth-login"
@@ -93,6 +94,51 @@ paths:
             application/json:
               schema:
                 $ref: "#/components/schemas/AuthResult"
+  /open/auth/openid/{providerId}:
+    get:
+      operationId: "open-auth-openid"
+      tags: [ Authentication ]
+      summary: Authenticates via OIDC at the external provider given by its id
+      description: |
+        Initiates the ["Authorization Code
+        Flow"](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)
+        as described in the OpenID Connect specification. This only is
+        enabled, if an external provider has been configured correctly
+        in the config file.
+
+        This will redirect to the external provider to authenticate
+        the user. Once authenticated, the user is redirected back to
+        the `/resume` endpoint.
+      parameters:
+        - $ref: "#/components/parameters/providerId"
+      responses:
+        302:
+          description: Found. Redirect to external authentication provider
+        200:
+          description: Not used, is only here because openid requires it
+  /open/auth/openid/{providerId}/resume:
+    get:
+      operationId: "open-auth-openid-resume"
+      tags: [ Authentication ]
+      summary: The callback URL for the authentication provider
+      description: |
+        This URL is used to redirect the user back to the application
+        by the authentication provider after login is completed.
+
+        This will then try to find (or create) the account at docspell
+        using information about the user provided by the
+        authentication provider. If the required information cannot be
+        found, the user cannot be logged into the application.
+
+        If the process completed successfully, this endpoint redirects
+        into the web application which will take over from here.
+      parameters:
+        - $ref: "#/components/parameters/providerId"
+      responses:
+        303:
+          description: See Other. Redirect to the webapp
+        200:
+          description: Not used, is only here because openid requires it
 
   /open/checkfile/{id}/{checksum}:
     get:
@@ -6269,3 +6315,10 @@ components:
         some identifier for a client application
       schema:
         type: string
+    providerId:
+      name: providerId
+      in: path
+      required: true
+      schema:
+        type: string
+        format: ident
diff --git a/modules/restserver/src/main/resources/reference.conf b/modules/restserver/src/main/resources/reference.conf
index be13766a..2929f68d 100644
--- a/modules/restserver/src/main/resources/reference.conf
+++ b/modules/restserver/src/main/resources/reference.conf
@@ -134,7 +134,7 @@ docspell.server {
           provider-id = "keycloak",
           client-id = "docspell",
           client-secret = "example-secret-439e-bf06-911e4cdd56a6",
-          scope = "docspell", # scope is required for OIDC
+          scope = "profile", # scope is required for OIDC
           authorize-url = "http://localhost:8080/auth/realms/home/protocol/openid-connect/auth",
           token-url = "http://localhost:8080/auth/realms/home/protocol/openid-connect/token",
           #User URL is not used when signature key is set.
diff --git a/website/site/content/docs/api/intro.md b/website/site/content/docs/api/intro.md
index c6359ab0..cb232f53 100644
--- a/website/site/content/docs/api/intro.md
+++ b/website/site/content/docs/api/intro.md
@@ -44,6 +44,14 @@ must be `docspell_auth` and a custom header must be named
 The admin route (see below) `/admin/user/resetPassword` can be used to
 reset a password of a user.
 
+### OpenID Connect
+
+Docspell can be configured to be a relying party for OpenID Connect.
+Please see [the config
+section](@/docs/configure/_index.md#openid-connect-oauth2) for
+details.
+
+
 ## Admin
 
 There are some endpoints available for adminstration tasks, for
diff --git a/website/site/content/docs/configure/_index.md b/website/site/content/docs/configure/_index.md
index 4a8f14ea..9651f81a 100644
--- a/website/site/content/docs/configure/_index.md
+++ b/website/site/content/docs/configure/_index.md
@@ -342,6 +342,91 @@ The `session-valid` determines how long a token is valid. This can be
 just some minutes, the web application obtains new ones
 periodically. So a rather short time is recommended.
 
+### OpenID Connect / OAuth2
+
+You can integrate Docspell into your SSO solution via [OpenID
+Connect](https://openid.net/connect/) (OIDC). This requires to set up
+an OpenID Provider (OP) somewhere and to configure Docspell
+accordingly to act as the relying party.
+
+You can define multiple OPs to use. For some examples, please see the
+default configuration file [below](#rest-server).
+
+The configuration of a provider highly depends on how it is setup.
+Here is an example for a setup using
+[keycloak](https://www.keycloak.org):
+
+``` conf
+provider = {
+  provider-id = "keycloak",
+  client-id = "docspell",
+  client-secret = "example-secret-439e-bf06-911e4cdd56a6",
+  scope = "profile", # scope is required for OIDC
+  authorize-url = "http://localhost:8080/auth/realms/home/protocol/openid-connect/auth",
+  token-url = "http://localhost:8080/auth/realms/home/protocol/openid-connect/token",
+  #User URL is not used when signature key is set.
+  #user-url = "http://localhost:8080/auth/realms/home/protocol/openid-connect/userinfo",
+  sign-key = "b64:MII…ZYL09vAwLn8EAcSkCAwEAAQ==",
+  sig-algo = "RS512"
+}
+```
+
+The `provider-id` is some identifier that is used in the URL to
+distinguish between possibly multiple providers. The `client-id` and
+`client-secret` define the two parameters required for a "confidential
+client". The different URLs are best explained at the [keycloak
+docs](https://www.keycloak.org/docs/latest/server_admin/#_oidc-endpoints).
+They are available for all OPs in some way. The `user-url` is not
+required, if the access token is already containing the necessary
+data. If not, then docspell performs another request to the
+`user-url`, which must be the user-info endpoint, to obtain the
+required user data.
+
+If the data is taken from the token directly and not via a request to
+the user-info endpoint, then the token must be validated using the
+given `sign-key` and `sig-algo`. These two values are then required to
+specify! However, if the user-info endpoint should be used, then leave
+the `sign-key` empty and specify the correct url in `user-url`. When
+specifying the `sign-key` use a prefix of `b64:` if it is Base64
+encoded or `hex:` if it is hex encoded. Otherwise the unicode bytes
+are used, which is most probably not wanted for this setting.
+
+Once the user is authenticated, docspell tries to setup an account and
+does some checks. For this it must get to the username and collective
+name somehow. How it does this, can be specified by the `user-key` and
+`collective-key` settings:
+
+``` conf
+# The collective of the user is given in the access token as
+# property `docspell_collective`.
+collective-key = "lookup:docspell_collective",
+# The username to use for the docspell account
+user-key = "preferred_username"
+```
+
+The `user-key` is some string that is used to search the JSON response
+from the OP for an object with that key. The search happens
+recursively, so the field can be in a nested object. The found value
+is used as the user name. Keycloak transmits the `preferred_username`
+when asking for the `profile` scope. This can be used as the user
+name.
+
+The collective name can be obtained by different ways. For example,
+you can instruct your OP (like keycloak) to provide a collective name
+in the token and/or user-info responses. If you do this, then use the
+`lookup:` prefix as in the example above. This instructs docspell to
+search for a value the same way as the `user-key`. You can also set a
+fixed collective, using `fixed:` prefix; in this case all users are in
+the same collective! A third option is to prefix it with `account:` -
+then the value that is looked up is interpreted as the full account
+name, like `collective/user` and the `user-key` setting is ignored. If
+you want to put each user in its own collective, you can just use the
+same value as in `user-key`, only prefixed with `lookup:`. In the
+example it would be `lookup:preferred_username`.
+
+If you find that these methods do not suffice for your case, please
+open an issue.
+
 
 ## File Processing
 
diff --git a/website/site/content/docs/features/_index.md b/website/site/content/docs/features/_index.md
index c1a6c1d0..029647c6 100644
--- a/website/site/content/docs/features/_index.md
+++ b/website/site/content/docs/features/_index.md
@@ -31,8 +31,11 @@ description = "A list of features and limitations."
   jobs, set priorities
 - Everything available via a [documented](https://www.openapis.org/)
   [REST Api](@/docs/api/_index.md); allows to [generate
-  clients](https://openapi-generator.tech/docs/generators) for
-  (almost) any language
+  clients](https://openapi-generator.tech/docs/generators) for many
+  languages
+- [OpenID Connect](@/docs/configure/_index.md#openid-connect-oauth2)
+  support allows Docspell to integrate into your SSO setup, for
+  example with keycloak.
 - mobile-friendly Web-UI with dark and light theme
 - [Create anonymous
   “upload-urls”](@/docs/webapp/uploading.md#anonymous-upload) to