diff --git a/.env b/.env
index 66183361..d48abfce 100644
--- a/.env
+++ b/.env
@@ -6,3 +6,6 @@ BE_VERSION=v4
## Enable v4 ELASTIC feature (disable if required or set in command line). To later disable, either unset or set to an empty value
# ELASTIC_ENABLED=true
+
+## Enable LDAP authentication backend (disable if required or set in command line). To later disable, either unset or set to an empty value
+# LDAP_ENABLED=true
diff --git a/.github/workflows/compose_test.yaml b/.github/workflows/compose_test.yaml
index d559755e..2054d3c4 100644
--- a/.github/workflows/compose_test.yaml
+++ b/.github/workflows/compose_test.yaml
@@ -54,12 +54,14 @@ jobs:
strategy:
matrix:
BE_VERSION: [v3, v4]
- JOBS_AND_ELASTIC_ENABLED: ['', true]
+ ELASTIC_OR_JOBS_ENABLED: ['', true]
+ LDAP_ENABLED: ['', true]
steps:
- uses: actions/checkout@v4
- name: Test compose.yaml
run: |-
- export JOBS_ENABLED=${{ matrix.JOBS_AND_ELASTIC_ENABLED }}
- export ELASTIC_ENABLED=${{ matrix.JOBS_AND_ELASTIC_ENABLED }}
+ export JOBS_ENABLED=${{ matrix.ELASTIC_OR_JOBS_ENABLED }}
+ export ELASTIC_ENABLED=${{ matrix.ELASTIC_OR_JOBS_ENABLED }}
+ export LDAP_ENABLED=${{ matrix.LDAP_ENABLED }}
export BE_VERSION=${{ matrix.BE_VERSION }}
docker compose --profile '*' up --wait --wait-timeout 300
diff --git a/README.md b/README.md
index 285e7d85..39cef84e 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ They are used when adding new services or grouping services together (and do not
| env | `BE_VERSION` |
`v3`: backend/v3`v4`: backend/v4 | `v4` | as set | Sets the be version to use in (2) of [default setup](#default-setup) to v3 | mongodb,frontend |
| env | `JOBS_ENABLED` | `true`: rabbitmq,archivemock,jobs feature | `''` | v3 | Creates a rabbitmq message broker which the be posts to and the archivemock listens to. It emulates the data long-term archive/retrieve workflow | |
| env | `ELASTIC_ENABLED` | `true`: elastic,elastic feature | `''` | v4 | Creates an elastic search service and sets the be to use it for full-text searches | |
+| env | `LDAP_ENABLED` | `true`: ldap auth | `''` | as set | Creates an LDAP service and sets the be to use it as authentication backend | |
After optionally setting any configuration option, one can still select the services to run as described [here](README.md#select-the-services).
diff --git a/services/backend/README.md b/services/backend/README.md
index b9c21663..c4f19e69 100644
--- a/services/backend/README.md
+++ b/services/backend/README.md
@@ -5,3 +5,12 @@ The SciCat backend HTTP service.
## Dependency on `BE_VERSION`
The `BE_VERSION` value controls which version of the backend should be started, either [v3](./services/v3) or [v4](./services/v4) (default).
+
+## Dependencies
+
+Here below we show the internal dependencies of the service, which are not already covered [here](../../../../README.md) (if `B` depends on `A`, then we visualize as `A --> B`). The same subdomain to service convention applies.
+
+```mermaid
+graph TD
+ ldap --> backend
+```
diff --git a/services/backend/compose.yaml b/services/backend/compose.yaml
index 9265b39b..fa7ae295 100644
--- a/services/backend/compose.yaml
+++ b/services/backend/compose.yaml
@@ -1,2 +1,4 @@
include:
- - ./services/${BE_VERSION:-v4}/compose.yaml
+ - path:
+ - ./services/${BE_VERSION:-v4}/compose.yaml
+ - ./services/ldap/.${LDAP_ENABLED:+./${BE_VERSION:-v4}/}compose${LDAP_ENABLED:+.ldap}.yaml
diff --git a/services/backend/services/ldap/.compose.yaml b/services/backend/services/ldap/.compose.yaml
new file mode 100644
index 00000000..1d826e5c
--- /dev/null
+++ b/services/backend/services/ldap/.compose.yaml
@@ -0,0 +1 @@
+## empty file used when override configs are not enabled
diff --git a/services/backend/services/ldap/README.md b/services/backend/services/ldap/README.md
new file mode 100644
index 00000000..2c7fdc24
--- /dev/null
+++ b/services/backend/services/ldap/README.md
@@ -0,0 +1,20 @@
+# LDAP (OpenLDAP)
+
+LDAP (Lightweight Directory Access Protocol) is a protocol used to access and manage directory information such as user credentials.
+SciCat can use LDAP as third-party authentication provider.
+
+## Configuration options
+
+The OpenLDAP configuration is set by the [.env file](./config/.env).
+
+For an extensive list of available options see [here](https://hub.docker.com/r/bitnami/openldap).
+
+You can add other users by editing the [ldif file](./config/ldifs/02-users.ldif).
+:warning: User creation is only done once, when the container is created.
+
+## Default configuration
+The default configuration [.env file](./config/.env) creates the `dc=facility` domain with the following user:
+
+| Username | Password |
+| --------- | -------- |
+| ldap-user | password |
diff --git a/services/backend/services/ldap/compose.yaml b/services/backend/services/ldap/compose.yaml
new file mode 100644
index 00000000..2e617840
--- /dev/null
+++ b/services/backend/services/ldap/compose.yaml
@@ -0,0 +1,13 @@
+services:
+ ldap:
+ image: bitnami/openldap:2.6
+ volumes:
+ - ./config/ldifs:/ldifs:ro
+ env_file:
+ - ./config/.env
+ healthcheck:
+ test: ldapwhoami -H ldap://ldap:389 -D 'cn=admin,dc=facility' -w 'admin'
+ start_period: 5s
+ interval: 10s
+ timeout: 10s
+ retries: 5
diff --git a/services/backend/services/ldap/config/.env b/services/backend/services/ldap/config/.env
new file mode 100644
index 00000000..85b5fce1
--- /dev/null
+++ b/services/backend/services/ldap/config/.env
@@ -0,0 +1,4 @@
+LDAP_ADMIN_USERNAME=admin
+LDAP_ADMIN_PASSWORD=admin
+LDAP_PORT_NUMBER=389
+LDAP_ROOT=dc=facility
diff --git a/services/backend/services/ldap/config/ldifs/01-bootstrap.ldif b/services/backend/services/ldap/config/ldifs/01-bootstrap.ldif
new file mode 100644
index 00000000..c728ee63
--- /dev/null
+++ b/services/backend/services/ldap/config/ldifs/01-bootstrap.ldif
@@ -0,0 +1,11 @@
+dn: dc=facility
+objectClass: top
+objectClass: dcObject
+objectClass: organization
+o: My Organization
+dc: facility
+
+dn: ou=users,dc=facility
+objectClass: top
+objectClass: organizationalUnit
+ou: users
diff --git a/services/backend/services/ldap/config/ldifs/02-users.ldif b/services/backend/services/ldap/config/ldifs/02-users.ldif
new file mode 100644
index 00000000..050ec8f1
--- /dev/null
+++ b/services/backend/services/ldap/config/ldifs/02-users.ldif
@@ -0,0 +1,12 @@
+dn: uid=ldap-user,ou=users,dc=facility
+objectClass: top
+objectClass: person
+objectClass: organizationalPerson
+objectClass: inetOrgPerson
+uid: ldap-user
+cn: LDAP user
+displayName: ldap-user
+sn: LDAP
+givenName: User
+mail: ldap-user@facility.com
+userPassword: password
diff --git a/services/backend/services/v3/README.md b/services/backend/services/v3/README.md
index 591fb88f..8fd6a101 100644
--- a/services/backend/services/v3/README.md
+++ b/services/backend/services/v3/README.md
@@ -69,12 +69,14 @@ In the default configuration folder [config](./config), the backend is set to us
Additionally, by setting the env variable [ENABLE_JOBS](../../.env#L5), the [archive mock](./services/archivemock/) and [rabbitmq](./services/rabbitmq/) services are started and the backend is configured to connect to them.
+If `LDAP_ENABLED` is toggled, you can use LDAP to log in with a [LDAP user](../ldap/README.md#default-configuration).
+
## Dependencies
-Here below we show the internal dependencies of the service, which are not already covered [here](../../../../README.md) (if `B` depends on `A`, then we visualize it as `A --> B`). The same subdomain to service convention applies.
+Here below we show the internal dependencies of the service, which are not already covered [here](../../../../README.md) and [here](../../README.md) (if `B` depends on `A`, then we visualize as `A --> B`). The same subdomain to service convention applies.
```mermaid
-graph TD
+graph TD
rabbitmq --> archivemock
rabbitmq --> backend
backend --> archivemock
diff --git a/services/backend/services/v3/compose.ldap.yaml b/services/backend/services/v3/compose.ldap.yaml
new file mode 100644
index 00000000..0e866aac
--- /dev/null
+++ b/services/backend/services/v3/compose.ldap.yaml
@@ -0,0 +1,10 @@
+include:
+ - ../ldap/compose.yaml
+
+services:
+ backend:
+ depends_on:
+ ldap:
+ condition: service_healthy
+ volumes:
+ - ./config/providers.ldap.json:/config/providers.ldap.json
diff --git a/services/backend/services/v3/config/providers.ldap.json b/services/backend/services/v3/config/providers.ldap.json
new file mode 100644
index 00000000..a0a1a4c6
--- /dev/null
+++ b/services/backend/services/v3/config/providers.ldap.json
@@ -0,0 +1,24 @@
+{
+ "ldap": {
+ "provider": "ldap",
+ "authScheme": "ldap",
+ "module": "passport-ldapauth",
+ "authPath": "/auth/msad",
+ "successRedirect": "/auth/account",
+ "failureRedirect": "/msad",
+ "session": true,
+ "json": true,
+ "failureFlash": true,
+ "profileAttributesFromLDAP": {
+ "displayName": "displayName",
+ "email": "mail"
+ },
+ "server": {
+ "url": "ldap://ldap:389",
+ "bindDn": "cn=admin,dc=facility",
+ "bindCredentials": "admin",
+ "searchBase": "ou=users,dc=facility",
+ "searchFilter": "(uid={{username}})"
+ }
+ }
+}
diff --git a/services/backend/services/v4/README.md b/services/backend/services/v4/README.md
index a80ae90a..fdc8a33a 100644
--- a/services/backend/services/v4/README.md
+++ b/services/backend/services/v4/README.md
@@ -23,11 +23,13 @@ In the default configuration folder [config](./config), the backend is set to us
## Enable additional features
-Additionally, by setting the env variable `ELASTIC_ENABLED`, the [elastic search](./services/elastic/) service is started and the backend is configured to connect to them.
+Additionally, by setting the env variable `ELASTIC_ENABLED`, the [elastic search](./services/elastic/) service is started and the backend is configured to connect to them.
+
+If `LDAP_ENABLED` is toggled, you can use LDAP to log in with a [LDAP user](../ldap/README.md#default-configuration).
## Dependencies
-Here below we show the internal dependencies of the service, which are not already covered [here](../../../../README.md) (if `B` depends on `A`, then we visualize as `A --> B`). The same subdomain to service convention applies.
+Here below we show the internal dependencies of the service, which are not already covered [here](../../../../README.md) and [here](../../README.md) (if `B` depends on `A`, then we visualize as `A --> B`). The same subdomain to service convention applies.
```mermaid
graph TD
diff --git a/services/backend/services/v4/compose.ldap.yaml b/services/backend/services/v4/compose.ldap.yaml
new file mode 100644
index 00000000..9b8fd1c3
--- /dev/null
+++ b/services/backend/services/v4/compose.ldap.yaml
@@ -0,0 +1,10 @@
+include:
+ - ../ldap/compose.yaml
+
+services:
+ backend:
+ depends_on:
+ ldap:
+ condition: service_healthy
+ env_file:
+ - ./config/.ldap.env
diff --git a/services/backend/services/v4/config/.ldap.env b/services/backend/services/v4/config/.ldap.env
new file mode 100644
index 00000000..2b7755a0
--- /dev/null
+++ b/services/backend/services/v4/config/.ldap.env
@@ -0,0 +1,3 @@
+LDAP_URL=ldap://ldap:389
+LDAP_SEARCH_BASE=ou=users,dc=facility
+LDAP_SEARCH_FILTER=(uid={{username}})
diff --git a/services/frontend/compose.yaml b/services/frontend/compose.yaml
index ea5f7ae2..d78bbabb 100644
--- a/services/frontend/compose.yaml
+++ b/services/frontend/compose.yaml
@@ -13,5 +13,6 @@ services:
- /config/init.sh && nginx -g "daemon off;"
environment:
BE_VERSION: ${BE_VERSION:-v4}
+ LDAP_ENABLED: ${LDAP_ENABLED:-}
labels:
- traefik.http.routers.frontend.rule=Host(`localhost`)
diff --git a/services/frontend/config/config.base.json b/services/frontend/config/config.base.json
index 143d9268..de384ae6 100644
--- a/services/frontend/config/config.base.json
+++ b/services/frontend/config/config.base.json
@@ -7,7 +7,7 @@
"editMetadataEnabled":true,
"editPublishedData":true,
"editSampleEnabled":true,
- "externalAuthEndpoint":"/auth/msad",
+ "externalAuthEndpoint":"/api/v3/auth/ldap",
"facility":"SAMPLE-SITE",
"fileColorEnabled":true,
"fileDownloadEnabled":true,
diff --git a/services/frontend/config/config.ldap.json b/services/frontend/config/config.ldap.json
new file mode 100644
index 00000000..d25fb2e7
--- /dev/null
+++ b/services/frontend/config/config.ldap.json
@@ -0,0 +1,4 @@
+{
+ "loginLdapEnabled": true,
+ "loginLdapLabel": "Connect with LDAP"
+}
diff --git a/services/frontend/config/config.v3.json b/services/frontend/config/config.v3.json
index b01c5d09..9322b40e 100644
--- a/services/frontend/config/config.v3.json
+++ b/services/frontend/config/config.v3.json
@@ -1,3 +1,4 @@
{
- "accessTokenPrefix": ""
+ "accessTokenPrefix": "",
+ "externalAuthEndpoint":"/auth/msad"
}
diff --git a/services/frontend/config/init.sh b/services/frontend/config/init.sh
index 4c986a8f..14c8a4d1 100755
--- a/services/frontend/config/init.sh
+++ b/services/frontend/config/init.sh
@@ -10,6 +10,7 @@ exclude_config () {
}
[ "$BE_VERSION" = "v4" ] && exclude_config "v3.json"
+[ -z "$LDAP_ENABLED" ] && exclude_config "ldap.json"
# shellcheck disable=SC2086
jq -s 'reduce .[] as $item ({}; . * $item)' $FILES > /usr/share/nginx/html/assets/config.json