# Groupware Developer Guide
## Introduction
The Groupware component of OpenCloud is implemented as a (micro)service within the OpenCloud framework (see `./services/groupware/`).
It is essentially providing a REST API to the OpenCloud UI clients (web, mobile) that is high-level and adapted to the needs of the UIs.
The implementation of that REST API turns those high-level APIs into lower-level [JMAP](https://jmap.io/) API calls to [Stalwart, the JMAP mail server](https://stalw.art/), using our own JMAP client library in `./pkg/jmap/` with a couple of additional RFCs used by JMAP in `./pkg/jscalendar` and `./pkg/jscontact`.
## Prerequisites
* `git`: (mandatory) to check out source code of OpenCloud and companion applications
* `openssl`: (optional) to test IMAPS with Stalwart, and optionally to create certificates
* `curl`: (recommended) to test the Groupware API, and to perform a few checks and tests
* `xh` or `httpie`: (optional) to test the Groupware API in a more convenient way
* `docker` (mandatory, including Docker Compose support)
* `go`: (mandatory) to build applications
* `node` + `pnpm`: (mandatory) to build the built-in IDM frontend
## Repository
The code lives in the same tree as the other OpenCloud backend services, albeit currently in the `groupware` branch, that gets rebased on `main` on a regular basis (at least once per week.)
Use [the `groupware` branch](https://github.com/opencloud-eu/opencloud/tree/groupware)
```bash
cd ~/src/opencloud/
OCDIR="$PWD"
git clone --branch groupware git@github.com:opencloud-eu/opencloud.git
```
Note that setting the variable `OCDIR` is merely going to help us with keeping the instructions below as generic as possible, it is not an environment variable that is used by OpenCloud, or required for OpenCloud to function in any way.
### Tools Repository
Also, you might want to check out these [helper scripts in opencloud-tools](https://github.com/pbleser-oc/opencloud-tools) somewhere and put that directory into your `PATH`, as it contains scripts to test and build the OpenCloud Groupware:
```bash
cd "$OCDIR/"
git clone git@github.com:pbleser-oc/opencloud-tools.git ./bin
echo "export PATH=\"\$PATH:$OCDIR/bin\"" >> ~/.bashrc
```
#### Tools Prerequisites
Those scripts have the following prerequisites:
* the [`jq`](https://github.com/jqlang/jq) JSON query command-line tool to extract access tokens,
* either the [httpie](https://httpie.io/cli) (`pipx install httpie`) or [`xh`](https://github.com/ducaale/xh) (`cargo install xh --locked`) command-line HTTP clients, just out of convenience as their output is much nicer than curl's
* `curl` as well, to retrieve the access tokens from Keycloak (no need for nice output there)
## Configuration
Since we require having a Stalwart container running at the very least, the preferred way of running OpenCloud and its adjacent services for developing the Groupware component is by using the `opencloud_full` Docker Compose setup in `$OCDIR/opencloud/devtools/deployments/opencloud_full/`.
This section will explain how to configure that Docker Compose setup for the needs of the Groupware backend.
### Hosts
The default hostname domain for the containers is `.opencloud.test`
Make sure to have the following entries in your `/etc/hosts`:
```ruby
127.0.0.1 cloud.opencloud.test
127.0.0.1 keycloak.opencloud.test
127.0.0.1 wopiserver.opencloud.test
127.0.0.1 mail.opencloud.test
127.0.0.1 collabora.opencloud.test
127.0.0.1 traefik.opencloud.test
127.0.0.1 stalwart.opencloud.test
```
Alternatively, use the following shell snippet to extract it in a more automated fashion:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
perl -ne 'if (/^([A-Z][A-Z0-9]+)_DOMAIN=(.*)$/) { print length($2) < 1 ? lc($1).".opencloud.test" : $2,"\n"}' <.env\
|sort|while read n; do \
grep -w -q "$n" /etc/hosts && echo -e "\e[32;4mexists :\e[0m $n: \e[32m$(grep -w $n /etc/hosts)\e[0m">&2 ||\
{ echo -e "\e[33;4mmissing:\e[0m ${n}" >&2; echo -e "127.0.0.1\t${n}";};\
done \
| sudo tee -a /etc/hosts
```
### Compose
There are four options, either
1. running the Groupware backend with OpenLDAP and Keycloak containers, along with master authentication between the Groupware backend and Stalwart, more akin to a production setup;
2. running the Groupware backend with OpenLDAP and Keycloak containers, along with OIDC Bearer token authentication between the Groupware backend and Stalwart, even more akin to a production setup;
3. running the Groupware backend using the built-in LDAP and OIDC services, along with master authentication between the Groupware backend and Stalwart, for a minimalistic setup that uses less resources and is more likely to be found in a home lab setup;
4. running the Groupware backend using the built-in LDAP and OIDC services, along with OIDC Bearer token authentication between the Groupware backend and Stalwart.
> [!NOTE]
> Note that option 2 is currently not implemented yet.
>
> Furthermore, at this time of writing, options 1 and 4 are not properly documented yet due to changes in how Stalwart 0.16 and newer
> handle their configuration, for which instructions and configuration files still need to be updated.
>
> For the time being, use the ["built-in LDAP with master authentication"](#homelab-setup-master) option instead.
In either case, the Docker Compose configuration in `$OCDIR/opencloud/devtools/deployments/opencloud_full/` needs to be modified.
#### Production Setup with Master Authentication
```mermaid
---
title: Production Setup
---
flowchart LR
oc["`opencloud`"]
c["client"]
kc["`keycloak`"]
ol["`ldap-server`"]
st["`stalwart`"]
c -- http --> oc
oc -- jmap --> st
oc --> ol
st --> ol
kc --> ol
c --> kc
```
##### Production Setup Instructions
> [!NOTE]
> The setup instructions are currently outdated, due to Stalwart 0.16 or higher having completely overhauled
> the way they are configured, and will be caught up with and updated in due time, when needed.
>
> For now, use the ["built-in LDAP with master authentication"](#homelab-setup-master) option instead.
Edit `$OCDIR/opencloud/devtools/deployments/opencloud_full/.env`, making the following changes (make sure to check out [the shell command-line that automates all of that, below](#automate-env-setup-prod-master)):
* change the container image to `opencloudeu/opencloud:dev`:
```diff
-OC_DOCKER_IMAGE=opencloudeu/opencloud-rolling
+OC_DOCKER_IMAGE=opencloudeu/opencloud
-OC_DOCKER_TAG=
+OC_DOCKER_TAG=dev
```
* add the `groupware` service to `START_ADDITIONAL_SERVICES`:
```diff
-START_ADDITIONAL_SERVICES="notifications"
+START_ADDITIONAL_SERVICES="notifications,groupware"
```
* enable the OpenLDAP container:
```diff
-#LDAP=:ldap.yml
+LDAP=:ldap.yml
```
* enable the Keycloak container:
```diff
-#KEYCLOAK=:keycloak.yml
+KEYCLOAK=:keycloak.yml
```
* enable the Stalwart container:
```diff
-#STALWART=:stalwart.yml
+STALWART=:stalwart.yml
```
* optionally disable the Collabora container
```diff
-COLLABORA=:collabora.yml
+#COLLABORA=:collabora.yml
```
* optionally disable UI containers
```diff
-UNZIP=:web_extensions/unzip.yml
-DRAWIO=:web_extensions/drawio.yml
-JSONVIEWER=:web_extensions/jsonviewer.yml
-PROGRESSBARS=:web_extensions/progressbars.yml
-EXTERNALSITES=:web_extensions/externalsites.yml
+#UNZIP=:web_extensions/unzip.yml
+#DRAWIO=:web_extensions/drawio.yml
+#JSONVIEWER=:web_extensions/jsonviewer.yml
+#PROGRESSBARS=:web_extensions/progressbars.yml
+#EXTERNALSITES=:web_extensions/externalsites.yml
```
##### Production Setup Script
All those changes above can be automated with the following script:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
perl -pi -e '
s|^(OC_DOCKER_IMAGE)=.*$|$1=opencloudeu/opencloud|;
s|^(OC_DOCKER_TAG)=.*$|$1=dev|;
s|^(START_ADDITIONAL_SERVICES=".*(?
```mermaid
---
title: Homelab Setup with Impersonation
---
flowchart LR
oc["`opencloud`"]
c["client"]
st["`stalwart`"]
c -- http --> oc
oc -- jmap --> st
st -- ldap --> oc
```
"Master Authentication" actually refers to [impersonation](https://stalw.art/docs/auth/authorization/administrator/#impersonation), which works as follows:
* the JMAP username (that is being impersonated) is suffixed with the single character `%` and then the username of the "master account" (the one that impersonates)
* so for authenticating as `alan@example.org` using the impersonating/master account `admin@example.org`, the username must be `alan@example.org%admin@example.org`
* the password is obviously the one of the impersonating/master account (so the one of `admin@example.org` in our case)
Since the OpenCloud Groupware service does not have access to the clear text password of the user, typically because the authentication in a production environment will go through OIDC, where only the IdP (such as Keycloak) will see the clear text password, impersonation is required to authenticate between the OpenCloud Groupware service and Stalwart.
Alternatively, OIDC authentication can be used as well, which requires a different configuration setup in Stalwart.
In Stalwart, impersonation requires having a user that has the `impersonate` permission. When using the `opencloud_full` setup, the Stalwart configuration that is imported (using the `stalwart-import` container) adds that permission to the user `admin@example.org` which is one of the pre-provisionined users in the OpenCloud LDAP.
##### Homelab Setup Instructions
Edit `$OCDIR/opencloud/devtools/deployments/opencloud_full/.env`, making the following changes (make sure to check out [the shell command-line that automates all of that, below](#automate-env-setup-homelab-master)):
* change the container image to `opencloudeu/opencloud:dev`:
```diff
-OC_DOCKER_IMAGE=opencloudeu/opencloud-rolling
+OC_DOCKER_IMAGE=opencloudeu/opencloud
-OC_DOCKER_TAG=
+OC_DOCKER_TAG=dev
```
* enable the creation of demo users:
```diff
-DEMO_USERS=
+DEMO_USERS=true
```
* add the `groupware` service to `START_ADDITIONAL_SERVICES`:
```diff
-START_ADDITIONAL_SERVICES="notifications"
+START_ADDITIONAL_SERVICES="notifications,groupware"
```
* enable the Stalwart container:
```diff
-#STALWART=:stalwart.yml
+STALWART=:stalwart.yml
```
* while not required, it is recommended to enable basic authentication support which, while less secure, allows for easier tooling when developing and testing HTTP APIs, by adding `PROXY_ENABLE_BASIC_AUTH=true` somewhere before the last line of the `.env` file:
```diff
# Domain of Stalwart
# Defaults to "stalwart.opencloud.test"
STALWART_DOMAIN=
+# Enable basic authentication to facilitate HTTP API testing
+# Do not do this in production.
+PROXY_ENABLE_BASIC_AUTH=true
+
## IMPORTANT ##
```
* optionally disable the Collabora container
```diff
-COLLABORA=:collabora.yml
+#COLLABORA=:collabora.yml
```
* optionally disable UI containers
```diff
-UNZIP=:web_extensions/unzip.yml
-DRAWIO=:web_extensions/drawio.yml
-JSONVIEWER=:web_extensions/jsonviewer.yml
-PROGRESSBARS=:web_extensions/progressbars.yml
-EXTERNALSITES=:web_extensions/externalsites.yml
+#UNZIP=:web_extensions/unzip.yml
+#DRAWIO=:web_extensions/drawio.yml
+#JSONVIEWER=:web_extensions/jsonviewer.yml
+#PROGRESSBARS=:web_extensions/progressbars.yml
+#EXTERNALSITES=:web_extensions/externalsites.yml
```
##### Homelab Setup Script
All those changes above can be automated with the following script:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
perl -pi -e '
BEGIN{$basic_auth=0}
s|^(OC_DOCKER_IMAGE)=.*$|$1=opencloudeu/opencloud|;
s|^(OC_DOCKER_TAG)=.*$|$1=dev|;
s|^(START_ADDITIONAL_SERVICES=".*(?
We also need to create a private key and a certificate in order to be able to expose LDAP over SSL in the built-in IDM in the `opencloud` container, which can be achieved as follows:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
docker compose run --rm opencloud-certs
```
Alternatively, like this:
```bash
openssl req -subj '/CN=opencloud.test' -x509 -newkey rsa:4096 -sha256 -days 365 -batch -nodes \
-keyout ./config/opencloud/certs/ldaps.key \
-out ./config/opencloud/certs/ldaps.crt
chmod 666 ./config/opencloud/certs/ldaps.*
```
Note that this is only required once, as the certificate only expires after 10 years, and is stored under `./config/opencloud/certs/`.
#### Homelab Setup with OIDC Authentication
```mermaid
---
title: Homelab Setup with OIDC
---
flowchart LR
oc["`opencloud`"]
c["client"]
st["`stalwart`"]
c -- http --> oc
oc -- jmap --> st
st -- userinfo --> oc
```
> [!NOTE]
> The setup instructions are currently outdated, due to Stalwart 0.16 or higher having completely overhauled
> the way they are configured, and will be caught up with and updated in due time, when needed.
>
> For now, use the ["built-in LDAP with master authentication"](#homelab-setup-master) option instead.
With this setup, the authentication flow is as follows:
1. the client authenticates against an Identity Provider (IdP) to obtain a JWT, typically by submitting username and password credentials
1. the client uses this JWT to authenticate against OpenCloud
1. OpenCloud swaps that IdP issued JWT against an internal one, that it mints on its own
1. the OpenCloud Groupware service uses that internal JWT in JMAP requests that it sends to Stalwart, using bearer authentication
1. Stalwart is configured to verify that internal token by submitting it to a Token Introspection Endpoint which is running as an OpenCloud service, namely `auth-api`, which also needs to be enabled explicitly in the configuration
##### Homelab Setup with OIDC Setup Instructions
Edit `$OCDIR/opencloud/devtools/deployments/opencloud_full/.env`, making the following changes (make sure to check out [the shell command-line that automates all of that, below](#automate-env-setup-homelab-oidc)):
* change the container image to `opencloudeu/opencloud:dev`:
```diff
-OC_DOCKER_IMAGE=opencloudeu/opencloud-rolling
+OC_DOCKER_IMAGE=opencloudeu/opencloud
-OC_DOCKER_TAG=
+OC_DOCKER_TAG=dev
```
* enable the creation of demo users:
```diff
-DEMO_USERS=
+DEMO_USERS=true
```
* add the `groupware` and the `auth-api` services to `START_ADDITIONAL_SERVICES`:
```diff
-START_ADDITIONAL_SERVICES="notifications"
+START_ADDITIONAL_SERVICES="notifications,groupware,auth-api"
```
* enable the Stalwart container:
```diff
-#STALWART=:stalwart.yml
+STALWART=:stalwart.yml
```
* change the authentication directory configuration for Stalwart to `idmoidc` in the `.env` file, using the variable `STALWART_AUTH_DIRECTORY`:
```diff
# Domain of Stalwart
# Defaults to "stalwart.opencloud.test"
STALWART_DOMAIN=
# LDAP configuration to use for Stalwart:
# Can either be either
# - idmldap: for the built-in IDP/IDM, using Master Authentication between Groupware and Stalwart, and LDAP in Stalwart
# - idmoidc: built-in IDP/IDM, using OIDC Userinfo between Groupware and Stalwart
# - ldap: when using KeyCloak and OpenLDAP, with Master Authentication between Groupware and Stalwart, and LDAP in Stalwart
-STALWART_AUTH_DIRECTORY=idmldap
+STALWART_AUTH_DIRECTORY=idmoidc
```
* while not required, it is recommended to enable basic authentication support which, while less secure, allows for easier tooling when developing and testing HTTP APIs, by adding `PROXY_ENABLE_BASIC_AUTH=true` somewhere before the last line of the `.env` file:
```diff
+# Enable basic authentication to facilitate HTTP API testing
+# Do not do this in production.
+PROXY_ENABLE_BASIC_AUTH=true
+
## IMPORTANT ##
```
* optionally disable the Collabora container
```diff
-COLLABORA=:collabora.yml
+#COLLABORA=:collabora.yml
```
* optionally disable UI containers
```diff
-UNZIP=:web_extensions/unzip.yml
-DRAWIO=:web_extensions/drawio.yml
-JSONVIEWER=:web_extensions/jsonviewer.yml
-PROGRESSBARS=:web_extensions/progressbars.yml
-EXTERNALSITES=:web_extensions/externalsites.yml
+#UNZIP=:web_extensions/unzip.yml
+#DRAWIO=:web_extensions/drawio.yml
+#JSONVIEWER=:web_extensions/jsonviewer.yml
+#PROGRESSBARS=:web_extensions/progressbars.yml
+#EXTERNALSITES=:web_extensions/externalsites.yml
```
##### Homelab Setup with OIDC Setup Script
All those changes above can be automated with the following script:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
perl -pi -e '
BEGIN{$basic_auth=0}
s|^(OC_DOCKER_IMAGE)=.*$|$1=opencloudeu/opencloud|;
s|^(OC_DOCKER_TAG)=.*$|$1=dev|;
s|^(START_ADDITIONAL_SERVICES=".*(? [!NOTE]
> Unfortunately, as of now, the hostname or IP address of the host that runs the Groupware backend (or the OpenCloud single binary) needs to be configured manually
> in `devtools/deployments/opencloud_full/config/stalwart/idmoidc.toml`, specifically in the variable `directory.oidc.endpoint.url` since it highly depends on whether
> you are running it on the host (typically from an IDE)
> or as another container in the Docker Compose project.
> In the former case, it also depends on the operating system.
> It is currently hard-wired to be `http://172.17.0.1:10000/auth/...`, which only works
>
> * on Linux, where `172.17.0.1` _tends_ to be the gateway host IP address, for running the OpenCloud Groupware backend on the host
> * when the environment variable `AUTHAPI_HTTP_ADDR` is set to `0.0.0.0:10000`, allowing for HTTP access to the auth-api backend, instead of limiting it to HTTPS through the proxy, which opens a whole can of worms with making Stalwart accept self-signed certificates
## Building
Build the `opencloudeu/opencloud:dev` image first:
```bash
cd "$OCDIR/opencloud/"
make -C ./services/idp/ generate
make -C ./opencloud/ clean build dev-docker
```
## Running
And then run everything from the Docker Compose `opencloud_full` setup:
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
docker compose up -d
```
Stalwart >= 0.16 requires its configuration to be loaded into its data storage, which means that we also need to run an import of that configuration once.
It initially starts up in "recovery mode", and waits for a lockfile `.initialize` to exist on its storage volume (`opencloud_full_stalwart-data`).
To import the initial configuration and create that lockfile, run this from the same directory:
```bash
docker compose run --rm stalwart-import
```
The `stalwart` container will detect the existence of the lockfile that is created after successfully importing the configuration, and will then restart its process in regular mode.
This is only required the first time, or whenever one deleted the storage volume `opencloud_full_stalwart-data`.
### Running in an IDE in Production Setup
If you plan to make changes to the backend code base, it might be more convenient to do so from within VSCode, in which case you should run all the services from the Docker Compose setup as above, but stop the `opencloud` service container (as that one will be running from within your IDE instead):
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full/"
docker compose stop opencloud
```
and then use the Launcher named "`OpenCloud server with external services`" in VSCode.
Do not do this if you plan to use the built-in IDM for OIDC and/or LDAP though, as that requires having an `opencloud` container running that is reachable from Stalwart, which would not be the case if it was solely running in an IDE on the host.
### Running in an IDE in Homelab Setup
Or if you want to do so but using the [“homelab” setup](#homelab-setup), then the `opencloud` container needs to be kept running, as it also provides LDAP and OIDC services, as the `stalwart` container cannot access those services on the `opencloud` process that is running on the host (in the IDE.)
In VSCode, use the Launcher `OpenCloud server with Groupware` instead, and keep the `opencloud` container running in the `opencloud_full` compose project.
It needs the following environment variables to be set:
* `OC_INSECURE`: `true`
* `PROXY_ENABLE_BASIC_AUTH`: `true`
* `IDM_CREATE_DEMO_USERS`: `true`
* `OC_ADMIN_USER_ID`: `some-admin-user-id-0000-000000000000`
* `IDM_ADMIN_PASSWORD`: `admin`
* `OC_SYSTEM_USER_ID`: `some-system-user-id-000-000000000000`
* `OC_SYSTEM_USER_API_KEY`: `some-system-user-machine-auth-api-key`
* `OC_JWT_SECRET`: `some-opencloud-jwt-secret`
* `OC_MACHINE_AUTH_API_KEY`: `some-opencloud-machine-auth-api-key`
* `OC_TRANSFER_SECRET`: `some-opencloud-transfer-secret`
* `COLLABORATION_WOPIAPP_SECRET`: `some-wopi-secret`
* `IDM_SVC_PASSWORD`: `some-ldap-idm-password`
* `GRAPH_LDAP_BIND_PASSWORD`: `some-ldap-idm-password`
* `IDM_REVASVC_PASSWORD`: `some-ldap-reva-password`
* `GROUPS_LDAP_BIND_PASSWORD`: `some-ldap-reva-password`
* `USERS_LDAP_BIND_PASSWORD`: `some-ldap-reva-password`
* `AUTH_BASIC_LDAP_BIND_PASSWORD`: `some-ldap-reva-password`
* `IDM_IDPSVC_PASSWORD`: `some-ldap-idp-password`
* `IDP_LDAP_BIND_PASSWORD`: `some-ldap-idp-password`
* `GATEWAY_STORAGE_USERS_MOUNT_ID`: `storage-users-1`
* `STORAGE_USERS_MOUNT_ID`: `storage-users-1`
* `GRAPH_APPLICATION_ID`: `application-1`
* `OC_SERVICE_ACCOUNT_ID`: `service-account-id`
* `OC_SERVICE_ACCOUNT_SECRET`: `service-account-secret`
* `OC_ADD_RUN_SERVICES`: `auth-api,groupware`
* `GROUPWARE_LOG_LEVEL`: `trace`
* `GROUPWARE_JMAP_MASTER_USERNAME`: `admin@example.org`
* `GROUPWARE_JMAP_MASTER_PASSWORD`: `admin`
* `GROUPWARE_SEND_DURATIONS_RESPONSE`: `true`
* `AUTHAPI_HTTP_ADDR`: `0.0.0.0:10000`
* `AUTHAPI_AUTH_REQUIRE_SHARED_SECRET`: `true`
* `AUTHAPI_AUTH_SHARED_SECRETS`: `stalwart=maethaR9eiXaiph8ahn8ohH6dahPiequ`
## Checking Services
To check whether the various services are running correctly:
### LDAP
#### Production Setup LDAP
When using the “production” setup (as depicted in section [Production Setup](#prod-setup-master) above), queries can be performed directly against the \
OpenLDAP container (`opencloud_full-openldap-1`) since its LDAP ports are mapped onto the host (to `:389` and `:636` for LDAP and LDAPS, respectively).
When using the OpenLDAP container, the necessary LDAP parameters are as follows:
* Bind DN: `cn=admin,dc=opencloud,dc=eu`
* Bind Password: `admin`
* Base DN: `ou=users,dc=opencloud,dc=eu`
* Host: `localhost`
* LDAP Port: `389`
* LDAPS Port: `636`
Run the following command on your host (requires the `ldap-tools` package with the `ldapsearch` CLI tool), which should output a list of DNs of demo users:
```bash
ldapsearch -H ldap://localhost -D 'cn=admin,dc=opencloud,dc=eu' \
-x -w 'admin' -b 'ou=users,dc=opencloud,dc=eu' -LLL \
'(objectClass=person)' dn
```
Sample output:
```ldif
dn: uid=alan,ou=users,dc=opencloud,dc=eu
dn: uid=lynn,ou=users,dc=opencloud,dc=eu
dn: uid=mary,ou=users,dc=opencloud,dc=eu
dn: uid=admin,ou=users,dc=opencloud,dc=eu
dn: uid=dennis,ou=users,dc=opencloud,dc=eu
dn: uid=margaret,ou=users,dc=opencloud,dc=eu
```
#### Homelab Setup LDAP
Instead, when using the “homelab” setup (as depicted in section [Homelab Setup](#homelab-setup) above), queries cannot be performed directly from the host but, instead, require spinning up another container in the same Docker network and do so from there.
The necessary LDAP parameters are as follows:
* Bind DN: `uid=libregraph,ou=sysusers,o=libregraph-idm`
* Bind Password: `admin` (or whichever password is set in the `IDM_REVASVC_PASSWORD` environment variable in `opencloud.yml`)
* Base DN: `o=libregraph-idm`
* Host: `localhost`
* LDAP Port: none, only supports LDAPS
* LDAPS Port: `9235`
To access the LDAP tree, spawn a new container in the same network, e.g. like this for a Debian 12 container:
```bash
docker run --network 'opencloud_full_opencloud-net' --rm \
--name "debian-${RANDOM}" -ti 'debian:12'
```
In that container, install the necessary packages to have the LDAP command-line tools:
```bash
apt-get update -y && apt-get install -y ca-certificates ldap-utils
```
Alternatively, the same can be achieved with an Alpine container:
```bash
docker run --network 'opencloud_full_opencloud-net' --rm \
--name "alpine-${RANDOM}" -ti 'alpine'
```
And running this command instead to install the LDAP command-line tools:
```bash
apk update && apk install openldap-clients
```
Run the following command in that container, which should output a list of DNs of demo users:
```bash
LDAPTLS_REQCERT=never ldapsearch -H ldaps://opencloud:9235 \
-D 'uid=reva,ou=sysusers,o=libregraph-idm' -x -w 'admin' \
-b 'o=libregraph-idm' -LLL \
'(objectClass=person)' dn
```
> [!NOTE]
> The `LDAPTLS_REQCERT` environment variable is set to `never` to prevent the `ldapsearch` application to validate the TLS certificate of the LDAP server, since we are using self-signed certificates for all those services in the devtools setups.
Sample output:
```ldif
dn: uid=admin,ou=users,o=libregraph-idm
dn: uid=alan,ou=users,o=libregraph-idm
dn: uid=lynn,ou=users,o=libregraph-idm
dn: uid=mary,ou=users,o=libregraph-idm
dn: uid=margaret,ou=users,o=libregraph-idm
dn: uid=dennis,ou=users,o=libregraph-idm
```
Alternatively, as a one-liner using an Alpine Docker image:
```bash
docker run --network 'opencloud_full_opencloud-net' --rm -ti alpine:3 \
/bin/sh -c "apk update && apk add openldap-clients && exec /bin/sh -il"
```
Or, to combine it directly with the `ldapsearch` command:
```bash
docker run --network 'opencloud_full_opencloud-net' --rm alpine \
/bin/sh -c "apk update -q && apk add -q openldap-clients && LDAPTLS_REQCERT=never ldapsearch -H 'ldaps://opencloud:9235' -D 'uid=reva,ou=sysusers,o=libregraph-idm' -x -w 'admin' -b 'o=libregraph-idm' -LLL '(objectClass=person)' dn"
```
### Testing Keycloak
> [!NOTE]
> Only available in the [“production” setup](#prod-setup-master)
To check whether it works correctly, the following `curl` command:
```bash
curl -ks -D- -X POST \
"https://keycloak.opencloud.test/realms/openCloud/protocol/openid-connect/token" \
-d username=alan -d password=demo -d grant_type=password \
-d client_id=web -d scope=openid
```
should provide you with a JSON response that contains an `access_token` property.
If it is not set up correctly, it should give you this instead:
```json
{"error":"invalid_client","error_description":"Invalid client or Invalid client credentials"}
```
### Testing Stalwart
To then test the IMAP authentication with Stalwart, run the following command on your host (requires the `openssl` CLI tool):
```bash
openssl s_client -crlf -connect localhost:993
```
When then greeted with the following prompt:
```java
* OK [CAPABILITY ...] Stalwart IMAP4rev2 at your service.
```
enter the following command:
```bash
A LOGIN alan@example.org demo
```
to which one should receive the following response:
```java
A OK [CAPABILITY IMAP4rev2 ...] Authentication successful
```
### Testing Stalwart JMAP Impersonation
If you are using a setup with impersonation instead of OIDC authentication: to test impersonation directly against Stalwart (the password of the `admin@example.org` user is `admin`, not `demo` as for the other users):
```bash
curl -fSsLk \
-u 'alan@example.org%admin@example.org:admin' \
https://stalwart.opencloud.test/.well-known/jmap
```
## Seeding with Data
Once a [Stalwart](https://stalw.art/) container is running (using the Docker Compose setup as explained above), use [`groupware-assistant`](https://github.com/opencloud-eu/groupware-assistant) to populate the inbox folder using JMAP:
```bash
cd "$OCDIR/"
git clone git@github.com:opencloud-eu/groupware-assistant.git
cd ./groupware-assistant/
go build .
./groupware-assistant email generate --count=50
```
> [!NOTE]
> Note that this operation does not use the Groupware APIs or any other OpenCloud backend services either,
> as it directly communicates with Stalwart via JMAP on `https://stalwart.opencloud.test` by default.
For more details on the usage of that little helper tool, consult its [`README.md`](https://github.com/opencloud-eu/groupware-assistant/blob/main/README.md), or consult its `--help` output.
> [!NOTE]
> This only needs to be done once, since the emails are stored in a volume used by the Stalwart container.
It also supports generating random
* contacts
* calendar events
* tasks
and can also be used to create, list and delete
* address books
* calendars
* mailboxes
as well as listing principals.
## Setting up Stalwart Principals
To make things more interesting, we might want to create some resources that are currently not captured by our LDAP structure and/or not part of our demo users, such as by
* adding quota to users, to have quota limits show up in the JMAP payloads;
* add groups, to have them listed as additional accounts for the users that are members of those groups;
* add mailing-lists
Those things can either be done using the Stalwart administration web UI, manually, or by using its [JMAP based management API](https://stalw.art/docs/ref/).
The latter can be somewhat more easily used via the Stalwart CLI, which can be installed from here:
For example like this from source:
```bash
cd "$OCDIR"
git clone https://github.com/stalwartlabs/cli.git
cd ./cli
cargo install --path .
```
## Setting Quota in Stalwart
Use the [Stalwart Management API](https://stalw.art/docs/category/management-api) to set the quota for a user if you want to test quota-related Groupware APIs.
Note that users that exist in OpenCloud (specifically in LDAP, be it OpenLDAP or the built-in IDM) are only visible in Stalwart after they have been authenticated successfully once, e.g. by retrieving a [JMAP Session](https://jmap.io/spec-core.html#the-jmap-session-resource), which can be performed using the helper script `oc-st-session` (which uses the environment variable `username` to determine the username), or using `curl` directly as follows:
```bash
curl -fSsLk -u alan@example.org:demo https://stalwart.opencloud.test/.well-known/jmap
```
Or use this snippet to do that for all the auto-provisioned demo users:
```bash
cd "$OCDIR/opencloud/"
for u in $(awk '($1=="uid:"){print $2}' [!TIP]
> Until everything is documented, the complete list of URI routes can be found in \
[`$OCDIR/opencloud/services/groupware/pkg/groupware/route.go`](./pkg/groupware/route.go)
## Services
### Stalwart
#### Docker Compose Configuration
Stalwart is configured to authenticate and look up users and groups from LDAP, but we have two different options for running an LDAP server in our Docker Compose configuration in `devtools/deployments/opencloud_full/`:
* either using the built-in "IDM" LDAP server
* or using the OpenLDAP container
In our Stalwart configuration, that choice is driven by the variable `STALWART_AUTH_DIRECTORY`, which can be set to either `idmldap` or `ldap`, accordingly, in `devtools/deployments/opencloud_full/.env`
> [!IMPORTANT]
> At the time of writing, only the IDM LDAP server option is supported and `STALWART_AUTH_DIRECTORY` must thus be set to `idmldap` for now.
#### Web UI
To access the Stalwart admin UI, open and use the following credentials to log in:
* username: `admin`
* password: `secret`
Those credentials are defined in the environment variable `STALWART_RECOVERY_ADMIN` for the `stalwart` container in `$OC_DIR/opencloud/devtools/deployments/opencloud_full/stalwart.yml`
#### Restart from Scratch
To start with a Stalwart container from scratch, removing all the data (including emails):
```bash
cd "$OCDIR/opencloud/devtools/deployments/opencloud_full"
docker compose rm stalwart --stop
docker volume rm opencloud_full_stalwart-data
docker compose up -d stalwart
```
And then run the following to import the initial configuration:
```bash
docker compose run --rm stalwart-import
```
#### Diagnostics
If anything goes wrong, the first thing to check is Stalwart's logs, that are configured on the most verbose level (trace) and should thus provide a lot of insight:
```bash
docker logs -f opencloud_full-stalwart-1
```