multiTenancyTests (#1313)

* multiTenancyTests

* fix linter issues

* fix after review
This commit is contained in:
Viktor Scharf
2025-08-08 10:16:57 +02:00
committed by GitHub
parent b1968591b1
commit 62a7f79f51
10 changed files with 334 additions and 12 deletions

View File

@@ -33,6 +33,7 @@ PLUGINS_S3_CACHE = "plugins/s3-cache:1"
PLUGINS_SLACK = "plugins/slack:1"
REDIS = "redis:6-alpine"
READY_RELEASE_GO = "woodpeckerci/plugin-ready-release-go:latest"
OPENLDAP = "bitnami/openldap:2.6"
DEFAULT_PHP_VERSION = "8.2"
DEFAULT_NODEJS_VERSION = "20"
@@ -113,7 +114,7 @@ config = {
"skip": False,
"withRemotePhp": [True],
"emailNeeded": True,
"extraEnvironment": {
"extraTestEnvironment": {
"EMAIL_HOST": "email",
"EMAIL_PORT": "9000",
},
@@ -207,7 +208,7 @@ config = {
"skip": False,
"withRemotePhp": [True],
"emailNeeded": True,
"extraEnvironment": {
"extraTestEnvironment": {
"EMAIL_HOST": "email",
"EMAIL_PORT": "9000",
},
@@ -250,7 +251,7 @@ config = {
"withRemotePhp": [True],
"federationServer": True,
"emailNeeded": True,
"extraEnvironment": {
"extraTestEnvironment": {
"EMAIL_HOST": "email",
"EMAIL_PORT": "9000",
},
@@ -300,6 +301,36 @@ config = {
"STORAGE_USERS_DRIVER": "decomposed",
},
},
"multiTenancy": {
"suites": [
"apiTenancy",
],
"skip": False,
"withRemotePhp": [True],
"ldapNeeded": True,
"extraTestEnvironment": {
"USE_PREPARED_LDAP_USERS": True,
},
"extraServerEnvironment": {
"OC_LDAP_USER_SCHEMA_TENANT_ID": "departmentNumber",
"OC_LDAP_URI": "ldaps://ldap-server:1636",
"OC_LDAP_INSECURE": True,
"OC_LDAP_BIND_DN": "cn=admin,dc=opencloud,dc=eu",
"OC_LDAP_BIND_PASSWORD": "admin",
"OC_LDAP_GROUP_BASE_DN": "ou=groups,dc=opencloud,dc=eu",
"OC_LDAP_GROUP_SCHEMA_ID": "entryUUID",
"OC_LDAP_USER_BASE_DN": "ou=users,dc=opencloud,dc=eu",
"OC_LDAP_USER_FILTER": "(objectclass=inetOrgPerson)",
"OC_LDAP_USER_SCHEMA_ID": "entryUUID",
"OC_LDAP_DISABLE_USER_MECHANISM": "none",
"GRAPH_LDAP_SERVER_UUID": True,
"GRAPH_LDAP_GROUP_CREATE_BASE_DN": "ou=custom,ou=groups,dc=opencloud,dc=eu",
"GRAPH_LDAP_REFINT_ENABLED": True,
"FRONTEND_READONLY_USER_ATTRIBUTES": "user.onPremisesSamAccountName,user.displayName,user.mail,user.passwordProfile,user.accountEnabled,user.appRoleAssignments",
"OC_LDAP_SERVER_WRITE_ENABLED": False,
"OC_EXCLUDE_RUN_SERVICES": "idm",
},
},
},
"apiTests": {
"numberOfParts": 7,
@@ -913,7 +944,7 @@ def localApiTestPipeline(ctx):
defaults = {
"suites": {},
"skip": False,
"extraEnvironment": {},
"extraTestEnvironment": {},
"extraServerEnvironment": {},
"storages": storages,
"accounts_hash_difficulty": 4,
@@ -924,6 +955,7 @@ def localApiTestPipeline(ctx):
"collaborationServiceNeeded": False,
"extraCollaborationEnvironment": {},
"withRemotePhp": with_remote_php,
"ldapNeeded": False,
}
if "localApiTests" in config:
@@ -941,11 +973,13 @@ def localApiTestPipeline(ctx):
(waitForServices("online-offices", ["collabora:9980", "onlyoffice:443", "fakeoffice:8080"]) if params["collaborationServiceNeeded"] else []) +
(waitForClamavService() if params["antivirusNeeded"] else []) +
(waitForEmailService() if params["emailNeeded"] else []) +
(ldapService() if params["ldapNeeded"] else []) +
(waitForLdapService() if params["ldapNeeded"] else []) +
opencloudServer(storage, params["accounts_hash_difficulty"], extra_server_environment = params["extraServerEnvironment"], with_wrapper = True, tika_enabled = params["tikaNeeded"]) +
(opencloudServer(storage, params["accounts_hash_difficulty"], deploy_type = "federation", extra_server_environment = params["extraServerEnvironment"]) if params["federationServer"] else []) +
((wopiCollaborationService("fakeoffice") + wopiCollaborationService("collabora") + wopiCollaborationService("onlyoffice")) if params["collaborationServiceNeeded"] else []) +
(openCloudHealthCheck("wopi", ["wopi-collabora:9304", "wopi-onlyoffice:9304", "wopi-fakeoffice:9304"]) if params["collaborationServiceNeeded"] else []) +
localApiTests(name, params["suites"], storage, params["extraEnvironment"], run_with_remote_php) +
localApiTests(name, params["suites"], storage, params["extraTestEnvironment"], run_with_remote_php) +
logRequests(),
"services": (emailService() if params["emailNeeded"] else []) +
(clamavService() if params["antivirusNeeded"] else []) +
@@ -2823,6 +2857,49 @@ def waitForClamavService():
],
}]
def ldapService():
return [
{
"name": "ldap-server",
"image": OPENLDAP,
"detach": True,
"environment": {
"BITNAMI_DEBUG": "true",
"LDAP_TLS_VERIFY_CLIENT": "never",
"LDAP_ENABLE_TLS": "yes",
"LDAP_TLS_CA_FILE": "/opt/bitnami/openldap/share/openldap.crt",
"LDAP_TLS_CERT_FILE": "/opt/bitnami/openldap/share/openldap.crt",
"LDAP_TLS_KEY_FILE": "/opt/bitnami/openldap/share/openldap.key",
"LDAP_ROOT": "dc=opencloud,dc=eu",
"LDAP_ADMIN_PASSWORD": "admin",
},
"commands": [
"mkdir -p /opt/bitnami/openldap/share",
"mkdir -p /tmp/custom-scripts",
"mkdir -p /tmp/ldif-files",
"cp tests/config/woodpecker/ldap/*.ldif /tmp/ldif-files/",
"cp tests/config/woodpecker/ldap/docker-entrypoint-override.sh /tmp/custom-scripts/",
"chmod +x /tmp/custom-scripts/docker-entrypoint-override.sh",
"ls -la /tmp/ldif-files/",
"/tmp/custom-scripts/docker-entrypoint-override.sh /opt/bitnami/scripts/openldap/run.sh",
],
"backend_options": {
"docker": {
"user": "0:0",
},
},
},
]
def waitForLdapService():
return [{
"name": "wait-for-ldap",
"image": OC_CI_WAIT_FOR,
"commands": [
"wait-for -it ldap-server:1636 -t 600",
],
}]
def fakeOffice():
return [
{

View File

@@ -344,8 +344,8 @@ class GraphHelper {
/**
* @param string $baseUrl
* @param string $xRequestId
* @param string $adminUser
* @param string $adminPassword
* @param string $user
* @param string $password
* @param string $searchTerm
*
* @return ResponseInterface
@@ -353,16 +353,16 @@ class GraphHelper {
public static function searchUser(
string $baseUrl,
string $xRequestId,
string $adminUser,
string $adminPassword,
string $user,
string $password,
string $searchTerm
): ResponseInterface {
$url = self::getFullUrl($baseUrl, "users?\$search=$searchTerm");
return HttpRequestHelper::get(
$url,
$xRequestId,
$adminUser,
$adminPassword,
$user,
$password,
self::getRequestHeaders()
);
}

View File

@@ -89,6 +89,13 @@ class OcHelper {
return (\getenv("TEST_REVA") === "true");
}
/**
* @return bool
*/
public static function isUsingPreparedLdapUsers(): bool {
return (\getenv("USE_PREPARED_LDAP_USERS") === "true");
}
/**
* @return bool|string false if no command given or the command as string
*/

View File

@@ -450,7 +450,7 @@ class SpacesContext implements Context {
* @throws Exception|GuzzleException
*/
public function cleanDataAfterTests(): void {
if (OcHelper::isTestingOnReva()) {
if (OcHelper::isTestingOnReva() || OcHelper::isUsingPreparedLdapUsers()) {
return;
}
$this->deleteAllProjectSpaces();

View File

@@ -442,6 +442,13 @@ default:
- AuthAppContext:
- CliContext:
- OcConfigContext:
apiTenancy:
paths:
- "%paths.base%/../features/apiTenancy"
context: *common_ldap_suite_context
contexts:
- FeatureContext: *common_feature_context_params
cliCommands:
paths:

View File

@@ -0,0 +1,77 @@
Feature: Multi-tenancy
I want to make sure that users from different tenants are isolated from each other,
so that each tenant's data and users remain private and secure.
Note:
All users are managed via LDAP and are assumed to exist.
Tests will use existing users without creating or deleting them.
Prepared LDAP users:
| user | tenant | group |
|-------|----------|----------------------|
| alice | tenant-1 | new-features-lovers |
| brian | tenant-1 | - |
| carol | tenant-2 | - |
| david | tenant-2 | release-lover |
Scenario: users from the same tenant can see each other
When user "Brian" searches for user "ali" using Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": ["value"],
"properties": {
"value": {
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"type": "object",
"required": [
"displayName",
"id",
"onPremisesSamAccountName",
"userType"
],
"properties": {
"displayName": {
"const": "Alice Hansen"
},
"id": {
"type": "string",
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
},
"onPremisesSamAccountName": {
"const": ""
},
"userType": {
"const": "Member"
}
}
}
}
}
}
"""
Scenario: users from different tenants cannot see each other
When user "David" searches for user "brian" using Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": ["value"],
"properties": {
"value": {
"type": "array",
"minItems": 0,
"maxItems": 0
}
}
}
"""

View File

@@ -0,0 +1,25 @@
dn: dc=opencloud,dc=eu
objectClass: organization
objectClass: dcObject
dc: opencloud
o: openCloud
dn: ou=users,dc=opencloud,dc=eu
objectClass: organizationalUnit
ou: users
dn: cn=admin,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: person
cn: admin
sn: admin
uid: ldapadmin
departmentNumber: {{ .TenantID }}
dn: ou=groups,dc=opencloud,dc=eu
objectClass: organizationalUnit
ou: groups
dn: ou=custom,ou=groups,dc=opencloud,dc=eu
objectClass: organizationalUnit
ou: custom

View File

@@ -0,0 +1,74 @@
dn: uid=alice,ou=users,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: alice
givenName: Alice
sn: Hansen
cn: alice
displayName: Alice Hansen
description: Senior DevOps engineer responsible for cloud infrastructure automation.
mail: alice@example.org
departmentNumber: tenant-1
userPassword:: e1NTSEF9eGY5OXlPOXVxSVJ5eTFxdFhQYVYxTnd6WE4wUWRReVU=
dn: uid=brian,ou=users,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: brian
givenName: Brian
sn: Murphy
cn: brian
displayName: Brian Murphy
description: IT support specialist focused on end-user systems and service desk operations.
mail: brian@example.org
departmentNumber: tenant-1
userPassword:: e1NTSEF9Y0xpdnEzdUxzNzB3SjU0R1dmR0EybndxbUZoRmNoOXQ=
dn: uid=carol,ou=users,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: carol
givenName: Carol
sn: King
cn: carol
displayName: Carol King
description: Project manager leading enterprise IT solutions and digital transformation projects.
mail: carol@example.org
departmentNumber: tenant-2
userPassword:: e1NTSEF9bWFiT2FyNEE4UWlJUm1Pb2JhNm1pYm1QMjQraDkzSEw=
dn: uid=david,ou=users,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: david
givenName: David
sn: Lopez
cn: david
displayName: David Lopez
description: Systems architect working on scalable backend services and platform reliability.
mail: david@example.org
departmentNumber: tenant-2
userPassword:: e1NTSEF9MEh2a3J0UTVONmZNSUhyL1NlWVQvclBYTjg0bi9SYlc=
dn: uid=admin,ou=users,dc=opencloud,dc=eu
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: admin
givenName: Admin
sn: Admin
cn: Admin
displayName: Admin
description: System administrator
mail: admin@example.org
departmentNumber: tenant-1
userPassword:: e1NTSEF9bFU2dDRHSC9Cb28wV2lnM1A0SVAzQTIyWE9aL2pCa1M=

View File

@@ -0,0 +1,13 @@
dn: cn=new-features-lovers,ou=groups,dc=opencloud,dc=eu
objectClass: groupOfNames
objectClass: top
cn: new-features-lovers
description: New features lovers
member: uid=alice,ou=users,dc=opencloud,dc=eu
dn: cn=release-lovers,ou=groups,dc=opencloud,dc=eu
objectClass: groupOfNames
objectClass: top
cn: release-lovers
description: Release lovers
member: uid=david,ou=users,dc=opencloud,dc=eu

View File

@@ -0,0 +1,42 @@
#!/bin/bash
printenv
if [ ! -f /opt/bitnami/openldap/share/openldap.key ]
then
openssl req -x509 -newkey rsa:4096 -keyout /opt/bitnami/openldap/share/openldap.key -out /opt/bitnami/openldap/share/openldap.crt -sha256 -days 365 -batch -nodes
fi
mkdir -p /opt/bitnami/openldap/ldifs
if [ -d "/tmp/ldif-files" ]; then
cp /tmp/ldif-files/*.ldif /opt/bitnami/openldap/ldifs/
fi
/opt/bitnami/scripts/openldap/entrypoint.sh "$@" &
ENTRYPOINT_PID=$!
echo "Waiting for LDAP server to start..."
while ! ldapsearch -x -H ldap://localhost:1389 -D "cn=admin,dc=opencloud,dc=eu" -w admin -b "dc=opencloud,dc=eu" > /dev/null 2>&1; do
sleep 2
done
echo "LDAP server is running, importing LDIF files..."
if [ -f "/opt/bitnami/openldap/ldifs/10_base.ldif" ]; then
echo "Importing 10_base.ldif..."
ldapadd -x -H ldap://localhost:1389 -D "cn=admin,dc=opencloud,dc=eu" -w admin -f /opt/bitnami/openldap/ldifs/10_base.ldif
fi
if [ -f "/opt/bitnami/openldap/ldifs/20_users.ldif" ]; then
echo "Importing 20_users.ldif..."
ldapadd -x -H ldap://localhost:1389 -D "cn=admin,dc=opencloud,dc=eu" -w admin -f /opt/bitnami/openldap/ldifs/20_users.ldif
fi
if [ -f "/opt/bitnami/openldap/ldifs/30_groups.ldif" ]; then
echo "Importing 30_groups.ldif..."
ldapadd -x -H ldap://localhost:1389 -D "cn=admin,dc=opencloud,dc=eu" -w admin -f /opt/bitnami/openldap/ldifs/30_groups.ldif
fi
echo "LDIF import completed!"
wait $ENTRYPOINT_PID