Merge pull request #11893 from kobergj/MultiInstanceOcisPartII

[OCISDEV-456] Cross Instance Sharing
This commit is contained in:
kobergj
2026-01-16 13:27:14 +01:00
committed by GitHub
25 changed files with 1645 additions and 712 deletions

View File

@@ -0,0 +1,5 @@
Enhancement: Allow sharing between instances
In Multi-Instance ocis it is now possible to share between instances.
https://github.com/owncloud/ocis/pull/11893

View File

@@ -9,10 +9,13 @@ Demo User have different roles on different instances
| User | ocis.owncloud.test | ocis.ocm.owncloud.test |
| --- | --- | --- |
| admin | admin | admin |
| einstein | user-light | user-light |
| einstein | user | |
| katherine | space-admin | |
| marie | user-light | user |
| moss | | admin | |
| richard | user | user-light |
| marie | | user |
| moss | | admin |
| richard | | |
Users can be invited to instances they are not member of by using their exact email address in space membership or share dialog.

View File

@@ -0,0 +1,20 @@
# groupOfNames requires at least one member to be present
# The refint will use this dn if the last member of the group
# has been removed
dn: cn=nobody,dc=owncloud,dc=com
objectClass: top
objectClass: organizationalRole
description: to be used for refint in empty groups
cn: nobody
dn: cn=ec730a6c-1b63-4b45-b83b-9e2311afdf85,dc=owncloud,dc=com
objectClass: top
objectClass: organizationalRole
description: ocis
cn: ec730a6c-1b63-4b45-b83b-9e2311afdf85
dn: cn=8d24cb5f-6ee6-4b98-86df-c4c268dddb46,dc=owncloud,dc=com
objectClass: top
objectClass: organizationalRole
description: ocis.ocm
cn: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46

View File

@@ -0,0 +1,14 @@
# configure memberof overlay to use groupOfNames and member attributes
dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcMemberOfGroupOC
olcMemberOfGroupOC: groupOfNames
-
replace: olcMemberOfMemberAD
olcMemberOfMemberAD: member
# configure refint overlay to use nobody if no member is present
dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRefintNothing
olcRefintNothing: cn=nobody,dc=owncloud,dc=com

View File

@@ -0,0 +1,14 @@
# the owncloud organization is already setup by osixia configuration
#dn: dc=owncloud,dc=com
#objectClass: organization
#objectClass: dcObject
#dc: owncloud
#o: ownCloud
dn: ou=users,dc=owncloud,dc=com
objectClass: organizationalUnit
ou: users
dn: ou=groups,dc=owncloud,dc=com
objectClass: organizationalUnit
ou: groups

View File

@@ -0,0 +1,132 @@
# Start dn with uid (user identifier / login), not cn (Firstname + Surname)
dn: uid=einstein,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: einstein
givenName: Albert
sn: Einstein
cn: einstein
displayName: Albert Einstein
description: A German-born theoretical physicist who developed the theory of relativity, one of the two pillars of modern physics (alongside quantum mechanics).
mail: einstein@example.org
uidNumber: 20000
gidNumber: 30000
homeDirectory: /home/einstein
ownCloudUUID: 4c510ada-c86b-4815-8820-42cdf82c3d51
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudRole: ocisUser
userPassword:: e1NTSEF9TXJEcXpFNGdKbXZxbVRVTGhvWEZ1VzJBbkV3NWFLK3J3WTIvbHc9PQ==
dn: uid=marie,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: marie
givenName: Marie
sn: Curie
cn: marie
displayName: Marie Skłodowska Curie
description: A Polish and naturalized-French physicist and chemist who conducted pioneering research on radioactivity.
mail: marie@example.org
uidNumber: 20001
gidNumber: 30000
homeDirectory: /home/marie
ownCloudUUID: f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudRole: ocisUser
userPassword:: e1NTSEF9UmFvQWs3TU9jRHBIUWY3bXN3MGhHNnVraFZQWnRIRlhOSUNNZEE9PQ==
dn: uid=richard,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: richard
givenName: Richard
sn: Feynman
cn: richard
displayName: Richard Phillips Feynman
description: An American theoretical physicist, known for his work in the path integral formulation of quantum mechanics, the theory of quantum electrodynamics, the physics of the superfluidity of supercooled liquid helium, as well as his work in particle physics for which he proposed the parton model.
mail: richard@example.org
uidNumber: 20002
gidNumber: 30000
homeDirectory: /home/richard
ownCloudUUID: 932b4540-8d16-481e-8ef4-588e4b6b151c
ownCloudRole: ocisUser
userPassword:: e1NTSEF9Z05LZTRreHdmOGRUREY5eHlhSmpySTZ3MGxSVUM1d1RGcWROTVE9PQ==
dn: uid=katherine,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: katherine
givenName: Katherine
sn: Johnson
cn: johnson
displayName: Creola Katherine Johnson
description: An American mathematician whose precise orbital calculations were critical to NASAs early spaceflights, including the Mercury and Apollo missions.
mail: katherine@example.org
uidNumber: 20002
gidNumber: 30000
homeDirectory: /home/katherine
ownCloudUUID: 534bb038-6f9d-4093-946f-133be61fa4e7
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudRole: ocisSpaceAdmin
userPassword:: e1NTSEF9Z05LZTRreHdmOGRUREY5eHlhSmpySTZ3MGxSVUM1d1RGcWROTVE9PQ==
dn: uid=moss,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: moss
givenName: Maurice
sn: Moss
cn: moss
displayName: Maurice Moss
description: A worker in the IT Department of Reynholm Industries. Of all the working staff in the IT Department, he is the most hard-working, the most experienced, and the most capable of doing his job well. He puts a lot of effort into his work, however he does not get the credit he deserves.
mail: moss@example.org
uidNumber: 20003
gidNumber: 30000
homeDirectory: /home/moss
ownCloudUUID: 058bff95-6708-4fe5-91e4-9ea3d377588b
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudRole: ocisAdmin
userPassword:: e1NTSEF9N0hEdTRoMkFDVExFWWt4U0RtSDZVQjhmUlpKRExDZDc=
dn: uid=admin,ou=users,dc=owncloud,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: ownCloudUser
objectClass: person
objectClass: posixAccount
objectClass: top
uid: admin
givenName: Admin
sn: Admin
cn: admin
displayName: Admin
description: An admin for this oCIS instance.
mail: admin@example.org
uidNumber: 20004
gidNumber: 30000
homeDirectory: /home/admin
ownCloudUUID: ddc2004c-0977-11eb-9d3f-a793888cd0f8
owncloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
owncloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudRole: ocisAdmin
userPassword:: e1NTSEF9UWhmaFB3dERydTUydURoWFFObDRMbzVIckI3TkI5Nmo=

View File

@@ -0,0 +1,95 @@
dn: cn=users,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: users
description: Users
ownCloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: 509a9dcd-bb37-4f4f-a01a-19dca27d9cfa
member: uid=einstein,ou=users,dc=owncloud,dc=com
member: uid=marie,ou=users,dc=owncloud,dc=com
member: uid=richard,ou=users,dc=owncloud,dc=com
member: uid=moss,ou=users,dc=owncloud,dc=com
member: uid=admin,ou=users,dc=owncloud,dc=com
dn: cn=sailing-lovers,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: sailing-lovers
description: Sailing lovers
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: 6040aa17-9c64-4fef-9bd0-77234d71bad0
member: uid=einstein,ou=users,dc=owncloud,dc=com
dn: cn=violin-haters,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: violin-haters
description: Violin haters
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: dd58e5ec-842e-498b-8800-61f2ec6f911f
member: uid=einstein,ou=users,dc=owncloud,dc=com
dn: cn=radium-lovers,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: radium-lovers
description: Radium lovers
ownCloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudUUID: 7b87fd49-286e-4a5f-bafd-c535d5dd997a
member: uid=marie,ou=users,dc=owncloud,dc=com
dn: cn=polonium-lovers,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: polonium-lovers
description: Polonium lovers
ownCloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudUUID: cedc21aa-4072-4614-8676-fa9165f598ff
member: uid=marie,ou=users,dc=owncloud,dc=com
dn: cn=quantum-lovers,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: quantum-lovers
description: Quantum lovers
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: a1726108-01f8-4c30-88df-2b1a9d1cba1a
member: uid=richard,ou=users,dc=owncloud,dc=com
dn: cn=philosophy-haters,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: philosophy-haters
description: Philosophy haters
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: 167cbee2-0518-455a-bfb2-031fe0621e5d
member: uid=richard,ou=users,dc=owncloud,dc=com
dn: cn=physics-lovers,ou=groups,dc=owncloud,dc=com
objectClass: groupOfNames
objectClass: ownCloud
objectClass: top
objectClass: ownCloudGroup
cn: physics-lovers
description: Physics lovers
ownCloudMemberOf: ec730a6c-1b63-4b45-b83b-9e2311afdf85
ownCloudMemberOf: 8d24cb5f-6ee6-4b98-86df-c4c268dddb46
ownCloudUUID: 262982c1-2362-4afa-bfdf-8cbfef64a06e
member: uid=einstein,ou=users,dc=owncloud,dc=com
member: uid=marie,ou=users,dc=owncloud,dc=com
member: uid=richard,ou=users,dc=owncloud,dc=com

View File

@@ -0,0 +1,57 @@
# This LDIF files describes the ownCloud schema
dn: cn=owncloud,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: owncloud
olcObjectIdentifier: ownCloudOid 1.3.6.1.4.1.39430
olcAttributeTypes: ( ownCloudOid:1.1.2 NAME 'ownCloudUUID'
DESC 'A non-reassignable and persistent account ID)'
EQUALITY uuidMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE )
olcAttributeTypes: ( ownCloudOid:1.1.3 NAME 'oCExternalIdentity'
DESC 'A triple separated by "$" representing the objectIdentity resource type of the Graph API ( signInType $ issuer $ issuerAssignedId )'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( ownCloudOid:1.1.4 NAME 'ownCloudUserEnabled'
DESC 'A boolean value indicating if ownCloudUser is enabled'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE)
olcAttributeTypes: ( ownCloudOid:1.1.5 NAME 'ownCloudUserType'
DESC 'User type (e.g. Member or Guest)'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE )
olcAttributeTypes: ( ownCloudOid:1.1.6 NAME 'ocLastSignInTimestamp'
DESC 'The timestamp of the last sign-in'
EQUALITY generalizedTimeMatch
ORDERING generalizedTimeOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE )
olcAttributeTypes: ( ownCloudOid:1.1.7 NAME 'ownCloudMemberOf'
DESC 'Instances the user is member of'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( ownCloudOid:1.1.8 NAME 'ownCloudGuestOf'
DESC 'Instances the user is guest of'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( ownCloudOid:1.1.9 NAME 'ownCloudRole'
DESC 'The role of the user on instances they are memberOf'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: ( ownCloudOid:1.2.1 NAME 'ownCloud'
DESC 'ownCloud LDAP Schema'
AUXILIARY
MAY ( ownCloudUUID ) )
olcObjectClasses: ( ownCloudOid:1.2.2 NAME 'ownCloudUser'
DESC 'ownCloud User LDAP Schema'
SUP ownCloud
AUXILIARY
MAY ( ocExternalIdentity $ ownCloudUserEnabled $ ownCloudUserType $ ocLastSignInTimestamp $ ownCloudMemberOf $ ownCloudGuestOf $ ownCloudRole) )
olcObjectClasses: ( ownCloudOid:1.2.3 NAME 'ownCloudGroup'
DESC 'ownCloud Group LDAP Schema'
SUP ownCloud
AUXILIARY
MAY ( ownCloudMemberOf ) )

View File

@@ -75,8 +75,7 @@ services:
PROXY_USER_CS3_CLAIM: "username"
# INSECURE: needed if oCIS / Traefik is using self generated certificates
OCIS_INSECURE: "${INSECURE:-true}"
OCIS_ADMIN_USER_ID: ""
OCIS_EXCLUDE_RUN_SERVICES: "idp"
OCIS_EXCLUDE_RUN_SERVICES: "idp,idm"
GRAPH_ASSIGN_DEFAULT_USER_ROLE: "false"
GRAPH_USERNAME_MATCH: "none"
# password policies
@@ -84,8 +83,41 @@ services:
PROXY_CSP_CONFIG_FILE_LOCATION: /etc/ocis/csp.yaml
OCIS_MFA_ENABLED: ${OCIS_MFA_ENABLED:-false}
WEB_OIDC_SCOPE: "openid profile email acr"
# LDAP
OCIS_LDAP_URI: ldap://ldap-server:389
OCIS_LDAP_INSECURE: "true"
OCIS_LDAP_BIND_DN: "cn=admin,dc=owncloud,dc=com"
OCIS_LDAP_BIND_PASSWORD: ${LDAP_ADMIN_PASSWORD:-admin}
OCIS_LDAP_GROUP_BASE_DN: "ou=groups,dc=owncloud,dc=com"
OCIS_LDAP_GROUP_OBJECTCLASS: "groupOfNames"
OCIS_LDAP_USER_BASE_DN: "ou=users,dc=owncloud,dc=com"
OCIS_LDAP_USER_OBJECTCLASS: "inetOrgPerson"
LDAP_LOGIN_ATTRIBUTES: "uid"
OCIS_ADMIN_USER_ID: "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
IDP_LDAP_LOGIN_ATTRIBUTE: "uid"
IDP_LDAP_UUID_ATTRIBUTE: "ownclouduuid"
IDP_LDAP_UUID_ATTRIBUTE_TYPE: binary
GRAPH_LDAP_SERVER_WRITE_ENABLED: "true" # assuming the external ldap is writable
GRAPH_LDAP_REFINT_ENABLED: "true" # osixia has refint enabled.
# Multi-Instance Configuration
OCIS_MULTI_INSTANCE_ENABLED: true
OCIS_MULTI_INSTANCE_INSTANCEID: "base"
OCIS_MULTI_INSTANCE_INSTANCEID: "ec730a6c-1b63-4b45-b83b-9e2311afdf85"
OCIS_LDAP_USER_FILTER: "(&(objectclass=owncloud)(|(ownCloudMemberOf=ec730a6c-1b63-4b45-b83b-9e2311afdf85)(ownCloudGuestOf=ec730a6c-1b63-4b45-b83b-9e2311afdf85)))"
OCIS_LDAP_GROUP_FILTER: "(&(objectclass=owncloud)(ownCloudMemberOf=ec730a6c-1b63-4b45-b83b-9e2311afdf85))"
OCIS_LDAP_USER_MEMBER_ATTRIBUTE: "owncloudMemberOf"
OCIS_LDAP_USER_GUEST_ATTRIBUTE: "ownCloudGuestOf"
OCIS_LDAP_PRECISE_SEARCH_ATTRIBUTE: "cn"
OCIS_LDAP_INSTANCE_MAPPER_ENABLED: true
OCIS_LDAP_INSTANCE_MAPPER_BASE_DN: "dc=owncloud,dc=com"
OCIS_LDAP_INSTANCE_MAPPER_NAME_ATTRIBUTE: "description"
OCIS_LDAP_INSTANCE_MAPPER_ID_ATTRIBUTE: "cn"
OCIS_MULTI_INSTANCE_QUERY_TEMPLATE: "([^@]+)@(.+).owncloud.test"
OCIS_MULTI_INSTANCE_MEMBER_CLAIM: "memberOf"
OCIS_MULTI_INSTANCE_GUEST_CLAIM: "guestOf"
OCIS_MULTI_INSTANCE_GUEST_ROLE: "user-light"
# Workaround needed to show external users - can be removed once fixed
OCIS_SHOW_USER_EMAIL_IN_RESULTS: true
PROXY_ROLE_ASSIGNMENT_OIDC_CLAIM: ownCloudRole
volumes:
- ./config/ocis/banned-password-list.txt:/etc/ocis/banned-password-list.txt
- ./config/ocis/csp.yaml:/etc/ocis/csp.yaml
@@ -130,17 +162,13 @@ services:
PROXY_USER_CS3_CLAIM: "username"
# ??
OCIS_INSECURE: "${INSECURE:-true}"
OCIS_ADMIN_USER_ID: ""
OCIS_EXCLUDE_RUN_SERVICES: "idp"
OCIS_EXCLUDE_RUN_SERVICES: "idp,idm"
GRAPH_ASSIGN_DEFAULT_USER_ROLE: "false"
GRAPH_USERNAME_MATCH: "none"
# CSP
PROXY_CSP_CONFIG_FILE_LOCATION: /etc/ocis/csp-ocm.yaml
OCIS_MFA_ENABLED: ${OCIS_MFA_ENABLED:-false}
WEB_OIDC_SCOPE: "openid profile email acr"
# Multi-Instance
OCIS_MULTI_INSTANCE_ENABLED: true
OCIS_MULTI_INSTANCE_INSTANCEID: "ocm"
# make the REVA gateway accessible to the app drivers
GATEWAY_GRPC_ADDR: 0.0.0.0:9142
# make the registry available to the app provider containers
@@ -149,6 +177,42 @@ services:
NATS_NATS_PORT: 9233
#keycloak
WEB_UI_CONFIG_FILE: /etc/ocis/ocis.ocm.web.config.json
# LDAP
OCIS_LDAP_URI: ldap://ldap-server:389
OCIS_LDAP_INSECURE: "true"
OCIS_LDAP_BIND_DN: "cn=admin,dc=owncloud,dc=com"
OCIS_LDAP_BIND_PASSWORD: ${LDAP_ADMIN_PASSWORD:-admin}
OCIS_LDAP_GROUP_BASE_DN: "ou=groups,dc=owncloud,dc=com"
OCIS_LDAP_GROUP_OBJECTCLASS: "groupOfNames"
OCIS_LDAP_USER_BASE_DN: "ou=users,dc=owncloud,dc=com"
OCIS_LDAP_USER_OBJECTCLASS: "inetOrgPerson"
LDAP_LOGIN_ATTRIBUTES: "uid"
OCIS_ADMIN_USER_ID: "ddc2004c-0977-11eb-9d3f-a793888cd0f8"
IDP_LDAP_LOGIN_ATTRIBUTE: "uid"
IDP_LDAP_UUID_ATTRIBUTE: "ownclouduuid"
IDP_LDAP_UUID_ATTRIBUTE_TYPE: binary
GRAPH_LDAP_SERVER_WRITE_ENABLED: "true" # assuming the external ldap is writable
GRAPH_LDAP_REFINT_ENABLED: "true" # osixia has refint enabled.
# Multi-Instance
OCIS_MULTI_INSTANCE_ENABLED: true
OCIS_MULTI_INSTANCE_INSTANCEID: "8d24cb5f-6ee6-4b98-86df-c4c268dddb46"
OCIS_LDAP_USER_FILTER: "(&(objectclass=owncloud)(|(ownCloudMemberOf=8d24cb5f-6ee6-4b98-86df-c4c268dddb46)(ownCloudGuestOf=8d24cb5f-6ee6-4b98-86df-c4c268dddb46)))"
OCIS_LDAP_GROUP_FILTER: "(&(objectclass=owncloud)(ownCloudMemberOf=8d24cb5f-6ee6-4b98-86df-c4c268dddb46))"
OCIS_LDAP_USER_MEMBER_ATTRIBUTE: "owncloudMemberOf"
OCIS_LDAP_USER_GUEST_ATTRIBUTE: "ownCloudGuestOf"
OCIS_LDAP_PRECISE_SEARCH_ATTRIBUTE: "cn"
OCIS_LDAP_INSTANCE_MAPPER_ENABLED: true
OCIS_LDAP_INSTANCE_MAPPER_BASE_DN: "dc=owncloud,dc=com"
OCIS_LDAP_INSTANCE_MAPPER_NAME_ATTRIBUTE: "description"
OCIS_LDAP_INSTANCE_MAPPER_ID_ATTRIBUTE: "cn"
OCIS_MULTI_INSTANCE_QUERY_TEMPLATE: "([^@]+)@(.+).owncloud.test"
OCIS_MULTI_INSTANCE_MEMBER_CLAIM: "memberOf"
OCIS_MULTI_INSTANCE_GUEST_CLAIM: "guestOf"
OCIS_MULTI_INSTANCE_GUEST_ROLE: "user-light"
# user filter required for multi-instance ocis
# Workaround needed to show external users - can be removed once fixed
OCIS_SHOW_USER_EMAIL_IN_RESULTS: true
PROXY_ROLE_ASSIGNMENT_OIDC_CLAIM: ownCloudRole
volumes:
- ./config/ocis/csp-ocm.yaml:/etc/ocis/csp-ocm.yaml
- ./config/ocis/ocis.ocm.web.config.json:/etc/ocis/ocis.ocm.web.config.json:ro
@@ -205,6 +269,7 @@ services:
# tracing
KC_TRACING_ENABLED: ${KEYCLOAK_TRACING:-false}
KC_TRACING_ENDPOINT: http://jaeger:4317
JAVA_OPTS: "-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true"
labels:
- "traefik.enable=true"
- "traefik.http.routers.keycloak.entrypoints=https"
@@ -218,6 +283,50 @@ services:
driver: ${LOG_DRIVER:-local}
restart: always
ldap-server:
image: osixia/openldap:1.5.0
networks:
ocis-net:
environment:
LDAP_TLS_VERIFY_CLIENT: never
LDAP_TLS: false
LDAP_ORGANISATION: owncloud
LDAP_DOMAIN: owncloud.com
LDAP_ROOT: "dc=owncloud,dc=com"
LDAP_ADMIN_PASSWORD: ${LDAP_ADMIN_PASSWORD:-admin}
LDAP_SEED_INTERNAL_LDIF_PATH: /ldifs
LDAP_SEED_INTERNAL_SCHEMA_PATH: /schemas
ports:
- "127.0.0.1:389:389"
- "127.0.0.1:636:636"
volumes:
- ./config/ldap/ldif:/ldifs
- ./config/ldap/schemas:/schemas
- ldap-certs:/container/service/slapd/assets/certs
- ldap-data:/var/lib/ldap
- ldap-config:/etc/ldap/slapd.d
logging:
driver: ${LOG_DRIVER:-local}
restart: always
ldap-manager:
image: osixia/phpldapadmin:latest
networks:
ocis-net:
environment:
PHPLDAPADMIN_LDAP_HOSTS: "#PYTHON2BASH:[{'ldap-server': [{'server': [{'port': 389}]}]}]"
PHPLDAPADMIN_HTTPS: "false"
labels:
- "traefik.enable=true"
- "traefik.http.routers.ldap-manager.entrypoints=https"
- "traefik.http.routers.ldap-manager.rule=Host(`${LDAP_MANAGER_DOMAIN:-ldap.owncloud.test}`)"
- "traefik.http.routers.ldap-manager.tls.certresolver=http"
- "traefik.http.routers.ldap-manager.service=ldap-manager"
- "traefik.http.services.ldap-manager.loadbalancer.server.port=80"
logging:
driver: ${LOG_DRIVER:-local}
restart: always
volumes:
certs:
ocis-config:
@@ -225,6 +334,9 @@ volumes:
keycloak_postgres_data:
ocis-ocm-config:
ocis-ocm-data:
ldap-data:
ldap-config:
ldap-certs:
networks:
ocis-net:

View File

@@ -627,13 +627,13 @@ type Bundle struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id"
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"` // @gotags: yaml:"name"
Type Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty" yaml:"type"` // @gotags: yaml:"type"
Extension string `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty" yaml:"extension"` // @gotags: yaml:"extension"
DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name"
Settings []*Setting `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty" yaml:"settings"` // @gotags: yaml:"settings"
Resource *Resource `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource"
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id"
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // @gotags: yaml:"name"
Type Bundle_Type `protobuf:"varint,3,opt,name=type,proto3,enum=ocis.messages.settings.v0.Bundle_Type" json:"type,omitempty"` // @gotags: yaml:"type"
Extension string `protobuf:"bytes,4,opt,name=extension,proto3" json:"extension,omitempty"` // @gotags: yaml:"extension"
DisplayName string `protobuf:"bytes,5,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name"
Settings []*Setting `protobuf:"bytes,6,rep,name=settings,proto3" json:"settings,omitempty"` // @gotags: yaml:"settings"
Resource *Resource `protobuf:"bytes,7,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource"
}
func (x *Bundle) Reset() {
@@ -722,10 +722,10 @@ type Setting struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id"
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty" yaml:"name"` // @gotags: yaml:"name"
DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty" yaml:"display_name"` // @gotags: yaml:"display_name"
Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` // @gotags: yaml:"description"
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id"
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // @gotags: yaml:"name"
DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // @gotags: yaml:"display_name"
Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` // @gotags: yaml:"description"
// Types that are assignable to Value:
//
// *Setting_IntValue
@@ -736,7 +736,7 @@ type Setting struct {
// *Setting_PermissionValue
// *Setting_MultiChoiceCollectionValue
Value isSetting_Value `protobuf_oneof:"value"`
Resource *Resource `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource"
Resource *Resource `protobuf:"bytes,11,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource"
}
func (x *Setting) Reset() {
@@ -867,31 +867,31 @@ type isSetting_Value interface {
}
type Setting_IntValue struct {
IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
IntValue *Int `protobuf:"bytes,5,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
}
type Setting_StringValue struct {
StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
StringValue *String `protobuf:"bytes,6,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
}
type Setting_BoolValue struct {
BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
BoolValue *Bool `protobuf:"bytes,7,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
}
type Setting_SingleChoiceValue struct {
SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof" yaml:"single_choice_value"` // @gotags: yaml:"single_choice_value"
SingleChoiceValue *SingleChoiceList `protobuf:"bytes,8,opt,name=single_choice_value,json=singleChoiceValue,proto3,oneof"` // @gotags: yaml:"single_choice_value"
}
type Setting_MultiChoiceValue struct {
MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof" yaml:"multi_choice_value"` // @gotags: yaml:"multi_choice_value"
MultiChoiceValue *MultiChoiceList `protobuf:"bytes,9,opt,name=multi_choice_value,json=multiChoiceValue,proto3,oneof"` // @gotags: yaml:"multi_choice_value"
}
type Setting_PermissionValue struct {
PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof" yaml:"permission_value"` // @gotags: yaml:"permission_value"
PermissionValue *Permission `protobuf:"bytes,10,opt,name=permission_value,json=permissionValue,proto3,oneof"` // @gotags: yaml:"permission_value"
}
type Setting_MultiChoiceCollectionValue struct {
MultiChoiceCollectionValue *MultiChoiceCollection `protobuf:"bytes,12,opt,name=multi_choice_collection_value,json=multiChoiceCollectionValue,proto3,oneof" yaml:"multi_choice_collection_value"` // @gotags: yaml:"multi_choice_collection_value"
MultiChoiceCollectionValue *MultiChoiceCollection `protobuf:"bytes,12,opt,name=multi_choice_collection_value,json=multiChoiceCollectionValue,proto3,oneof"` // @gotags: yaml:"multi_choice_collection_value"
}
func (*Setting_IntValue) isSetting_Value() {}
@@ -913,11 +913,11 @@ type Int struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Default int64 `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
Min int64 `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty" yaml:"min"` // @gotags: yaml:"min"
Max int64 `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty" yaml:"max"` // @gotags: yaml:"max"
Step int64 `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty" yaml:"step"` // @gotags: yaml:"step"
Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder"
Default int64 `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
Min int64 `protobuf:"varint,2,opt,name=min,proto3" json:"min,omitempty"` // @gotags: yaml:"min"
Max int64 `protobuf:"varint,3,opt,name=max,proto3" json:"max,omitempty"` // @gotags: yaml:"max"
Step int64 `protobuf:"varint,4,opt,name=step,proto3" json:"step,omitempty"` // @gotags: yaml:"step"
Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder"
}
func (x *Int) Reset() {
@@ -992,11 +992,11 @@ type String struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
Required bool `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty" yaml:"required"` // @gotags: yaml:"required"
MinLength int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty" yaml:"min_length"` // @gotags: yaml:"min_length"
MaxLength int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty" yaml:"max_length"` // @gotags: yaml:"max_length"
Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty" yaml:"placeholder"` // @gotags: yaml:"placeholder"
Default string `protobuf:"bytes,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
Required bool `protobuf:"varint,2,opt,name=required,proto3" json:"required,omitempty"` // @gotags: yaml:"required"
MinLength int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"` // @gotags: yaml:"min_length"
MaxLength int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"` // @gotags: yaml:"max_length"
Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"` // @gotags: yaml:"placeholder"
}
func (x *String) Reset() {
@@ -1071,8 +1071,8 @@ type Bool struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Default bool `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty" yaml:"label"` // @gotags: yaml:"label"
Default bool `protobuf:"varint,1,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"` // @gotags: yaml:"label"
}
func (x *Bool) Reset() {
@@ -1126,7 +1126,7 @@ type SingleChoiceList struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
}
func (x *SingleChoiceList) Reset() {
@@ -1173,7 +1173,7 @@ type MultiChoiceList struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
Options []*ListOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
}
func (x *MultiChoiceList) Reset() {
@@ -1220,9 +1220,9 @@ type ListOption struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"` // @gotags: yaml:"value"
Default bool `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty" yaml:"default"` // @gotags: yaml:"default"
DisplayValue string `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value"
Value *ListOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` // @gotags: yaml:"value"
Default bool `protobuf:"varint,2,opt,name=default,proto3" json:"default,omitempty"` // @gotags: yaml:"default"
DisplayValue string `protobuf:"bytes,3,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value"
}
func (x *ListOption) Reset() {
@@ -1283,7 +1283,7 @@ type MultiChoiceCollection struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Options []*MultiChoiceCollectionOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty" yaml:"options"` // @gotags: yaml:"options"
Options []*MultiChoiceCollectionOption `protobuf:"bytes,1,rep,name=options,proto3" json:"options,omitempty"` // @gotags: yaml:"options"
}
func (x *MultiChoiceCollection) Reset() {
@@ -1330,10 +1330,10 @@ type MultiChoiceCollectionOption struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value *MultiChoiceCollectionOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"` // @gotags: yaml:"value"
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty" yaml:"key"` // @gotags: yaml:"key"
Attribute string `protobuf:"bytes,3,opt,name=attribute,proto3" json:"attribute,omitempty" yaml:"attribute"` // @gotags: yaml:"attribute"
DisplayValue string `protobuf:"bytes,4,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty" yaml:"display_value"` // @gotags: yaml:"display_value"
Value *MultiChoiceCollectionOptionValue `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` // @gotags: yaml:"value"
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // @gotags: yaml:"key"
Attribute string `protobuf:"bytes,3,opt,name=attribute,proto3" json:"attribute,omitempty"` // @gotags: yaml:"attribute"
DisplayValue string `protobuf:"bytes,4,opt,name=display_value,json=displayValue,proto3" json:"display_value,omitempty"` // @gotags: yaml:"display_value"
}
func (x *MultiChoiceCollectionOption) Reset() {
@@ -1474,15 +1474,15 @@ type isMultiChoiceCollectionOptionValue_Option interface {
}
type MultiChoiceCollectionOptionValue_IntValue struct {
IntValue *Int `protobuf:"bytes,1,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
IntValue *Int `protobuf:"bytes,1,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
}
type MultiChoiceCollectionOptionValue_StringValue struct {
StringValue *String `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
StringValue *String `protobuf:"bytes,2,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
}
type MultiChoiceCollectionOptionValue_BoolValue struct {
BoolValue *Bool `protobuf:"bytes,3,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
BoolValue *Bool `protobuf:"bytes,3,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
}
func (*MultiChoiceCollectionOptionValue_IntValue) isMultiChoiceCollectionOptionValue_Option() {}
@@ -1496,8 +1496,8 @@ type Permission struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Operation Permission_Operation `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty" yaml:"operation"` // @gotags: yaml:"operation"
Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty" yaml:"constraint"` // @gotags: yaml:"constraint"
Operation Permission_Operation `protobuf:"varint,1,opt,name=operation,proto3,enum=ocis.messages.settings.v0.Permission_Operation" json:"operation,omitempty"` // @gotags: yaml:"operation"
Constraint Permission_Constraint `protobuf:"varint,2,opt,name=constraint,proto3,enum=ocis.messages.settings.v0.Permission_Constraint" json:"constraint,omitempty"` // @gotags: yaml:"constraint"
}
func (x *Permission) Reset() {
@@ -1552,12 +1552,12 @@ type Value struct {
unknownFields protoimpl.UnknownFields
// id is the id of the Value. It is generated on saving it.
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" yaml:"id"` // @gotags: yaml:"id"
BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty" yaml:"bundle_id"` // @gotags: yaml:"bundle_id"
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // @gotags: yaml:"id"
BundleId string `protobuf:"bytes,2,opt,name=bundle_id,json=bundleId,proto3" json:"bundle_id,omitempty"` // @gotags: yaml:"bundle_id"
// setting_id is the id of the setting from within its bundle.
SettingId string `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty" yaml:"setting_id"` // @gotags: yaml:"setting_id"
AccountUuid string `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty" yaml:"account_uuid"` // @gotags: yaml:"account_uuid"
Resource *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty" yaml:"resource"` // @gotags: yaml:"resource"
SettingId string `protobuf:"bytes,3,opt,name=setting_id,json=settingId,proto3" json:"setting_id,omitempty"` // @gotags: yaml:"setting_id"
AccountUuid string `protobuf:"bytes,4,opt,name=account_uuid,json=accountUuid,proto3" json:"account_uuid,omitempty"` // @gotags: yaml:"account_uuid"
Resource *Resource `protobuf:"bytes,5,opt,name=resource,proto3" json:"resource,omitempty"` // @gotags: yaml:"resource"
// Types that are assignable to Value:
//
// *Value_BoolValue
@@ -1682,23 +1682,23 @@ type isValue_Value interface {
}
type Value_BoolValue struct {
BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
BoolValue bool `protobuf:"varint,6,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
}
type Value_IntValue struct {
IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
IntValue int64 `protobuf:"varint,7,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
}
type Value_StringValue struct {
StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
StringValue string `protobuf:"bytes,8,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
}
type Value_ListValue struct {
ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof" yaml:"list_value"` // @gotags: yaml:"list_value"
ListValue *ListValue `protobuf:"bytes,9,opt,name=list_value,json=listValue,proto3,oneof"` // @gotags: yaml:"list_value"
}
type Value_CollectionValue struct {
CollectionValue *CollectionValue `protobuf:"bytes,10,opt,name=collection_value,json=collectionValue,proto3,oneof" yaml:"collection_value"` // @gotags: yaml:"collection_value"
CollectionValue *CollectionValue `protobuf:"bytes,10,opt,name=collection_value,json=collectionValue,proto3,oneof"` // @gotags: yaml:"collection_value"
}
func (*Value_BoolValue) isValue_Value() {}
@@ -1716,7 +1716,7 @@ type ListValue struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values"
Values []*ListOptionValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values"
}
func (x *ListValue) Reset() {
@@ -1836,15 +1836,15 @@ type isListOptionValue_Option interface {
}
type ListOptionValue_StringValue struct {
StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
StringValue string `protobuf:"bytes,1,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
}
type ListOptionValue_IntValue struct {
IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
}
type ListOptionValue_BoolValue struct {
BoolValue bool `protobuf:"varint,3,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
BoolValue bool `protobuf:"varint,3,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
}
func (*ListOptionValue_StringValue) isListOptionValue_Option() {}
@@ -1858,7 +1858,7 @@ type CollectionValue struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Values []*CollectionOption `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty" yaml:"values"` // @gotags: yaml:"values"
Values []*CollectionOption `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"` // @gotags: yaml:"values"
}
func (x *CollectionValue) Reset() {
@@ -1906,7 +1906,7 @@ type CollectionOption struct {
unknownFields protoimpl.UnknownFields
// required
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty" yaml:"key"` // @gotags: yaml:"key"
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` // @gotags: yaml:"key"
// Types that are assignable to Option:
//
// *CollectionOption_IntValue
@@ -1987,15 +1987,15 @@ type isCollectionOption_Option interface {
}
type CollectionOption_IntValue struct {
IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof" yaml:"int_value"` // @gotags: yaml:"int_value"
IntValue int64 `protobuf:"varint,2,opt,name=int_value,json=intValue,proto3,oneof"` // @gotags: yaml:"int_value"
}
type CollectionOption_StringValue struct {
StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,proto3,oneof" yaml:"string_value"` // @gotags: yaml:"string_value"
StringValue string `protobuf:"bytes,3,opt,name=string_value,json=stringValue,proto3,oneof"` // @gotags: yaml:"string_value"
}
type CollectionOption_BoolValue struct {
BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,proto3,oneof" yaml:"bool_value"` // @gotags: yaml:"bool_value"
BoolValue bool `protobuf:"varint,4,opt,name=bool_value,json=boolValue,proto3,oneof"` // @gotags: yaml:"bool_value"
}
func (*CollectionOption_IntValue) isCollectionOption_Option() {}

View File

@@ -33,8 +33,9 @@ type Config struct {
UnifiedRoles UnifiedRoles `yaml:"unified_roles"`
MaxConcurrency int `yaml:"max_concurrency" env:"OCIS_MAX_CONCURRENCY;GRAPH_MAX_CONCURRENCY" desc:"The maximum number of concurrent requests the service will handle." introductionVersion:"7.0.0"`
Keycloak Keycloak `yaml:"keycloak"`
ServiceAccount ServiceAccount `yaml:"service_account"`
Keycloak Keycloak `yaml:"keycloak"`
ServiceAccount ServiceAccount `yaml:"service_account"`
MultiInstance MultiInstanceConfig `yaml:"multi_instance"`
Validation Validation `yaml:"validation"`
@@ -92,6 +93,15 @@ type LDAP struct {
EducationResourcesEnabled bool `yaml:"education_resources_enabled" env:"GRAPH_LDAP_EDUCATION_RESOURCES_ENABLED" desc:"Enable LDAP support for managing education related resources." introductionVersion:"pre5.0"`
EducationConfig LDAPEducationConfig
RequireExternalID bool `yaml:"require_external_id" env:"GRAPH_LDAP_REQUIRE_EXTERNAL_ID" desc:"If enabled, the 'OCIS_LDAP_USER_SCHEMA_EXTERNAL_ID' is used as primary identifier for the provisioning API." introductionVersion:"8.0.0"`
// Multi-Instance Only
UserMemberAttribute string `yaml:"user_member_attribute" env:"OCIS_LDAP_USER_MEMBER_ATTRIBUTE" desc:"LDAP Attribute to signal the user is member of an instance. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
UserGuestAttribute string `yaml:"user_guest_attribute" env:"OCIS_LDAP_USER_GUEST_ATTRIBUTE" desc:"LDAP Attribute to signal the user is guest of an instance. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
PreciseSearchAttribute string `yaml:"precise_search_attribute" env:"OCIS_LDAP_PRECISE_SEARCH_ATTRIBUTE" desc:"LDAP Attribute to be used for searching users on other instances. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
InstanceMapperEnabled bool `yaml:"instance_mapper_enabled" env:"OCIS_LDAP_INSTANCE_MAPPER_ENABLED" desc:"The InstanceMapper allows mapping instance names (user readable) to instance IDs (machine readable) based on an LDAP query. See other _INSTANCE_MAPPER_ env vars. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
InstanceMapperBaseDN string `yaml:"instance_mapper_base_dn" env:"OCIS_LDAP_INSTANCE_MAPPER_BASE_DN" desc:"BaseDN of the 'instancename to instanceid' mapper in LDAP. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
InstanceMapperNameAttribute string `yaml:"instance_mapper_name_attribute" env:"OCIS_LDAP_INSTANCE_MAPPER_NAME_ATTRIBUTE" desc:"LDAP Attribute of the instance name. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
InstanceMapperIDAttribute string `yaml:"instance_mapper_id_attribute" env:"OCIS_LDAP_INSTANCE_MAPPER_ID_ATTRIBUTE" desc:"LDAP Attribute of the instance ID. Requires OCIS_MULTI_INSTANCE_ENABLED." introductionVersion:"Curie"`
}
// LDAPEducationConfig represents the LDAP configuration for education related resources
@@ -162,3 +172,10 @@ type ServiceAccount struct {
type Validation struct {
MaxTagLength int `yaml:"max_tag_length" env:"OCIS_MAX_TAG_LENGTH" desc:"Define the maximum tag length. Defaults to 100 if not set. Set to 0 to not limit the tag length. Changes only impact the validation of new tags." introductionVersion:"7.2.0"`
}
// MultiInstanceConfig holds configuration for multi-instance-ocis
type MultiInstanceConfig struct {
Enabled bool `yaml:"enabled" env:"OCIS_MULTI_INSTANCE_ENABLED" desc:"Enable multiple instances of Infinite Scale." introductionVersion:"Curie"`
InstanceID string `yaml:"instanceid" env:"OCIS_MULTI_INSTANCE_INSTANCEID" desc:"The unique ID of this instance." introductionVersion:"Curie"`
QueryRegexp string `yaml:"query_regexp" env:"OCIS_MULTI_INSTANCE_QUERY_TEMPLATE" desc:"The regular expression extracting username and instancename from a user provided search." introductionVersion:"Curie"`
}

View File

@@ -36,7 +36,11 @@ type Backend interface {
DeleteUser(ctx context.Context, nameOrID string) error
// UpdateUser applies changes to given user, identified by username or id
UpdateUser(ctx context.Context, nameOrID string, user libregraph.UserUpdate) (*libregraph.User, error)
// AddUser adds a user to the instance (multi-instance only)
AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) // FIXME
GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error)
GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error)
GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error)
// FilterUsers returns a list of users that match the filter
FilterUsers(ctx context.Context, oreq *godata.GoDataRequest, filter *godata.ParseNode) ([]*libregraph.User, error)

View File

@@ -42,6 +42,11 @@ func (i *CS3) UpdateUser(ctx context.Context, nameOrID string, user libregraph.U
return nil, errNotImplemented
}
// AddUser is not implemented
func (i *CS3) AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) {
return libregraph.User{}, errNotImplemented
}
// GetUser implements the Backend Interface.
func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataRequest) (*libregraph.User, error) {
logger := i.Logger.SubloggerWithRequestID(ctx)
@@ -71,6 +76,11 @@ func (i *CS3) GetUser(ctx context.Context, userID string, _ *godata.GoDataReques
return CreateUserModelFromCS3(res.GetUser()), nil
}
// GetPreciseUser is not implemented
func (i *CS3) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
return nil, errNotImplemented
}
// GetUsers implements the Backend Interface.
func (i *CS3) GetUsers(ctx context.Context, oreq *godata.GoDataRequest) ([]*libregraph.User, error) {
logger := i.Logger.SubloggerWithRequestID(ctx)

View File

@@ -76,6 +76,15 @@ type LDAP struct {
logger *log.Logger
conn ldap.Client
// multi instance only
userMemberAttribute string
userGuestAttribute string
preciseSearchAttribute string
instanceMapperEnabled bool
instanceMapperBaseDN string
instanceMapperNameAttribute string
instanceMapperIDAttribute string
}
type userAttributeMap struct {
@@ -109,7 +118,7 @@ func ParseDisableMechanismType(disableMechanism string) (DisableUserMechanismTyp
return t, nil
}
func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LDAP, error) {
func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger, instanceID string) (*LDAP, error) {
if config.UserDisplayNameAttribute == "" || config.UserIDAttribute == "" ||
config.UserEmailAttribute == "" || config.UserNameAttribute == "" {
return nil, errors.New("invalid user attribute mappings")
@@ -158,29 +167,36 @@ func NewLDAPBackend(lc ldap.Client, config config.LDAP, logger *log.Logger) (*LD
}
return &LDAP{
useServerUUID: config.UseServerUUID,
usePwModifyExOp: config.UsePasswordModExOp,
userBaseDN: config.UserBaseDN,
userFilter: config.UserFilter,
userObjectClass: config.UserObjectClass,
userIDisOctetString: config.UserIDIsOctetString,
userScope: userScope,
userAttributeMap: uam,
groupBaseDN: config.GroupBaseDN,
groupCreateBaseDN: config.GroupCreateBaseDN,
groupFilter: config.GroupFilter,
groupObjectClass: config.GroupObjectClass,
groupIDisOctetString: config.GroupIDIsOctetString,
groupScope: groupScope,
groupAttributeMap: gam,
educationConfig: educationConfig,
disableUserMechanism: disableMechanismType,
localUserDisableGroupDN: config.LdapDisabledUsersGroupDN,
logger: logger,
conn: lc,
writeEnabled: config.WriteEnabled,
refintEnabled: config.RefintEnabled,
useExternalID: config.RequireExternalID,
useServerUUID: config.UseServerUUID,
usePwModifyExOp: config.UsePasswordModExOp,
userBaseDN: config.UserBaseDN,
userFilter: config.UserFilter,
userObjectClass: config.UserObjectClass,
userIDisOctetString: config.UserIDIsOctetString,
userScope: userScope,
userAttributeMap: uam,
groupBaseDN: config.GroupBaseDN,
groupCreateBaseDN: config.GroupCreateBaseDN,
groupFilter: config.GroupFilter,
groupObjectClass: config.GroupObjectClass,
groupIDisOctetString: config.GroupIDIsOctetString,
groupScope: groupScope,
groupAttributeMap: gam,
educationConfig: educationConfig,
disableUserMechanism: disableMechanismType,
localUserDisableGroupDN: config.LdapDisabledUsersGroupDN,
logger: logger,
conn: lc,
writeEnabled: config.WriteEnabled,
refintEnabled: config.RefintEnabled,
useExternalID: config.RequireExternalID,
userMemberAttribute: config.UserMemberAttribute,
userGuestAttribute: config.UserGuestAttribute,
preciseSearchAttribute: config.PreciseSearchAttribute,
instanceMapperEnabled: config.InstanceMapperEnabled,
instanceMapperBaseDN: config.InstanceMapperBaseDN,
instanceMapperNameAttribute: config.InstanceMapperNameAttribute,
instanceMapperIDAttribute: config.InstanceMapperIDAttribute,
}, nil
}
@@ -411,6 +427,26 @@ func (i *LDAP) UpdateUser(ctx context.Context, nameOrID string, user libregraph.
return returnUser, nil
}
// AddUser adds a user to the instance by setting the corresponding guest attribute.
func (i *LDAP) AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) {
e, err := i.getPreciseLDAPUser(id, "")
if err != nil {
return libregraph.User{}, err
}
mr := ldap.ModifyRequest{DN: e.DN}
mr.Add(i.userGuestAttribute, []string{instanceID})
if err := i.conn.Modify(&mr); err != nil {
i.logger.Error().Err(err).Str("userid", id).Str("instanceid", instanceID).Msg("error adding user")
return libregraph.User{}, err
}
u, err := i.refineUser(e, nil)
if err != nil {
i.logger.Error().Err(err).Str("userid", id).Str("instanceid", instanceID).Interface("entry", e).Msg("error refining user")
return libregraph.User{}, err
}
return *u, nil
}
func (i *LDAP) getUserByDN(dn, searchTerm string) (*ldap.Entry, error) {
baseFilter := fmt.Sprintf("(objectClass=%s)", i.userObjectClass)
@@ -524,7 +560,7 @@ func (i *LDAP) getLDAPUserByID(id string) (*ldap.Entry, error) {
return nil, fmt.Errorf("invalid User id: %w", err)
}
filter := fmt.Sprintf("(%s=%s)", i.userAttributeMap.id, idString)
return i.getLDAPUserByFilter(filter)
return i.getLDAPUserByFilter(filter, i.userFilter)
}
func (i *LDAP) getLDAPUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
@@ -538,11 +574,20 @@ func (i *LDAP) getLDAPUserByNameOrID(nameOrID string) (*ldap.Entry, error) {
filter = fmt.Sprintf("(%s=%s)", i.userAttributeMap.userName, ldap.EscapeFilter(nameOrID))
}
return i.getLDAPUserByFilter(filter)
return i.getLDAPUserByFilter(filter, i.userFilter)
}
func (i *LDAP) getLDAPUserByFilter(filter string) (*ldap.Entry, error) {
filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", i.userFilter, i.userObjectClass, filter)
func (i *LDAP) getPreciseLDAPUser(uniqueID string, instanceID string) (*ldap.Entry, error) {
uid := ldap.EscapeFilter(uniqueID)
filter := fmt.Sprintf("(%s=%s)", i.userAttributeMap.id, uid)
if iid := ldap.EscapeFilter(instanceID); iid != "" {
filter = fmt.Sprintf("(&(%s=%s)(|(%s=%s)(%s=%s)))", i.preciseSearchAttribute, uid, i.userMemberAttribute, iid, i.userGuestAttribute, iid)
}
return i.getLDAPUserByFilter(filter, "") // no user filter for precise search
}
func (i *LDAP) getLDAPUserByFilter(filter string, userFilter string) (*ldap.Entry, error) {
filter = fmt.Sprintf("(&%s(objectClass=%s)%s)", userFilter, i.userObjectClass, filter)
return i.searchLDAPEntryByFilter(i.userBaseDN, i.getUserAttrTypesForSearch(), filter)
}
@@ -556,6 +601,38 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData
return nil, err
}
var q *godata.GoDataQuery
if oreq != nil {
q = oreq.Query
}
return i.refineUser(e, q)
}
// GetPreciseUser gets a user using its exact email address or id. The overall user filter will be ignored.
func (i *LDAP) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
logger := i.logger.SubloggerWithRequestID(ctx)
logger.Debug().Str("backend", "ldap").Msg("GetPreciseUser")
iid, err := i.getInstanceID(instancename)
if err != nil {
// error logged in getInstanceID
return nil, ErrNotFound
}
e, err := i.getPreciseLDAPUser(name, iid)
if err != nil {
i.logger.Error().Err(err).Str("username", name).Str("instancename", instancename).Str("instanceid", iid).Msg("error getting precise user")
return nil, ErrNotFound
}
var q *godata.GoDataQuery
if oreq != nil {
q = oreq.Query
}
return i.refineUser(e, q)
}
func (i *LDAP) refineUser(e *ldap.Entry, query *godata.GoDataQuery) (*libregraph.User, error) {
u := i.createUserModelFromLDAP(e)
if u == nil {
return nil, ErrNotFound
@@ -568,7 +645,7 @@ func (i *LDAP) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoData
}
}
exp, err := GetExpandValues(oreq.Query)
exp, err := GetExpandValues(query)
if err != nil {
return nil, err
}
@@ -1352,6 +1429,41 @@ func (i *LDAP) oDataFilterToLDAPFilter(filter *godata.ParseNode) (string, error)
return fmt.Sprintf("(%s<=%s)", i.userAttributeMap.lastSignIn, ldap.EscapeFilter(ldapDateTime)), nil
}
func (i *LDAP) getInstanceID(instancename string) (string, error) {
if instancename == "" || !i.instanceMapperEnabled {
return instancename, nil
}
searchRequest := ldap.NewSearchRequest(
i.instanceMapperBaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases, 1, 0, false,
fmt.Sprintf("(%s=%s)", i.instanceMapperNameAttribute, instancename),
[]string{i.instanceMapperIDAttribute},
nil,
)
res, err := i.conn.Search(searchRequest)
if err != nil {
i.logger.Error().Err(err).Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Str("attribute", i.instanceMapperIDAttribute).Msg("Search instance by name failed")
return "", errorcode.New(errorcode.ItemNotFound, "instanceid search failed")
}
if len(res.Entries) == 0 {
// expected behaviour - we log debug in case something is fishy. This log can be removed when the feature proves stable
i.logger.Debug().Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Interface("result", res).Msg("Search instance by name empty")
return "", ErrNotFound
}
if len(res.Entries) > 1 {
i.logger.Error().Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Interface("result", res).Msg("Search instance by name returned multiple responses.")
return "", errorcode.New(errorcode.ItemNotFound, "instanceid search returned multiple responses")
}
if len(res.Entries[0].Attributes) == 0 || len(res.Entries[0].Attributes[0].Values) == 0 {
i.logger.Error().Str("backend", "ldap").Str("dn", i.instanceMapperBaseDN).Str("instancename", instancename).Interface("result", res).Msg("Search instance by name returned malformed response")
return "", errorcode.New(errorcode.ItemNotFound, "instanceid search response malformed")
}
return res.Entries[0].Attributes[0].Values[0], nil
}
func isLastSuccessFullSignInDateTimeFilter(node *godata.ParseNode) bool {
if node.Token.Type != godata.ExpressionTokenNav {
return false

View File

@@ -18,7 +18,7 @@ import (
)
func getMockedBackend(l ldap.Client, lc config.LDAP, logger *log.Logger) (*LDAP, error) {
return NewLDAPBackend(l, lc, logger)
return NewLDAPBackend(l, lc, logger, "")
}
const (
@@ -80,29 +80,29 @@ func TestNewLDAPBackend(t *testing.T) {
tc := lconfig
tc.UserDisplayNameAttribute = ""
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
t.Error("Should fail with incomplete user attr config")
}
tc = lconfig
tc.GroupIDAttribute = ""
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
t.Errorf("Should fail with incomplete group config")
}
tc = lconfig
tc.UserSearchScope = ""
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
t.Errorf("Should fail with invalid user search scope")
}
tc = lconfig
tc.GroupSearchScope = ""
if _, err := NewLDAPBackend(l, tc, &logger); err == nil {
if _, err := NewLDAPBackend(l, tc, &logger, ""); err == nil {
t.Errorf("Should fail with invalid group search scope")
}
if _, err := NewLDAPBackend(l, lconfig, &logger); err != nil {
if _, err := NewLDAPBackend(l, lconfig, &logger, ""); err != nil {
t.Errorf("Should fail with invalid group search scope")
}
}
@@ -147,7 +147,7 @@ func TestCreateUser(t *testing.T) {
c := lconfig
c.UseServerUUID = true
b, _ := NewLDAPBackend(l, c, &logger)
b, _ := NewLDAPBackend(l, c, &logger, "")
newUser, err := b.CreateUser(context.Background(), *user)
assert.Nil(t, err)
@@ -164,7 +164,7 @@ func TestCreateUserModelFromLDAP(t *testing.T) {
l := &mocks.Client{}
logger := log.NewLogger(log.Level("debug"))
b, _ := NewLDAPBackend(l, lconfig, &logger)
b, _ := NewLDAPBackend(l, lconfig, &logger, "")
if user := b.createUserModelFromLDAP(nil); user != nil {
t.Errorf("createUserModelFromLDAP should return on nil Entry")
}

View File

@@ -77,6 +77,64 @@ func (_c *Backend_AddMembersToGroup_Call) RunAndReturn(run func(context.Context,
return _c
}
// AddUser provides a mock function with given fields: ctx, id, instanceID
func (_m *Backend) AddUser(ctx context.Context, id string, instanceID string) (libregraph.User, error) {
ret := _m.Called(ctx, id, instanceID)
if len(ret) == 0 {
panic("no return value specified for AddUser")
}
var r0 libregraph.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (libregraph.User, error)); ok {
return rf(ctx, id, instanceID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) libregraph.User); ok {
r0 = rf(ctx, id, instanceID)
} else {
r0 = ret.Get(0).(libregraph.User)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, id, instanceID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Backend_AddUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddUser'
type Backend_AddUser_Call struct {
*mock.Call
}
// AddUser is a helper method to define mock.On call
// - ctx context.Context
// - id string
// - instanceID string
func (_e *Backend_Expecter) AddUser(ctx interface{}, id interface{}, instanceID interface{}) *Backend_AddUser_Call {
return &Backend_AddUser_Call{Call: _e.mock.On("AddUser", ctx, id, instanceID)}
}
func (_c *Backend_AddUser_Call) Run(run func(ctx context.Context, id string, instanceID string)) *Backend_AddUser_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *Backend_AddUser_Call) Return(_a0 libregraph.User, _a1 error) *Backend_AddUser_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Backend_AddUser_Call) RunAndReturn(run func(context.Context, string, string) (libregraph.User, error)) *Backend_AddUser_Call {
_c.Call.Return(run)
return _c
}
// CreateGroup provides a mock function with given fields: ctx, group
func (_m *Backend) CreateGroup(ctx context.Context, group libregraph.Group) (*libregraph.Group, error) {
ret := _m.Called(ctx, group)
@@ -528,6 +586,67 @@ func (_c *Backend_GetGroups_Call) RunAndReturn(run func(context.Context, *godata
return _c
}
// GetPreciseUser provides a mock function with given fields: ctx, name, instancename, oreq
func (_m *Backend) GetPreciseUser(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
ret := _m.Called(ctx, name, instancename, oreq)
if len(ret) == 0 {
panic("no return value specified for GetPreciseUser")
}
var r0 *libregraph.User
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string, *godata.GoDataRequest) (*libregraph.User, error)); ok {
return rf(ctx, name, instancename, oreq)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string, *godata.GoDataRequest) *libregraph.User); ok {
r0 = rf(ctx, name, instancename, oreq)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*libregraph.User)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string, string, *godata.GoDataRequest) error); ok {
r1 = rf(ctx, name, instancename, oreq)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Backend_GetPreciseUser_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPreciseUser'
type Backend_GetPreciseUser_Call struct {
*mock.Call
}
// GetPreciseUser is a helper method to define mock.On call
// - ctx context.Context
// - name string
// - instancename string
// - oreq *godata.GoDataRequest
func (_e *Backend_Expecter) GetPreciseUser(ctx interface{}, name interface{}, instancename interface{}, oreq interface{}) *Backend_GetPreciseUser_Call {
return &Backend_GetPreciseUser_Call{Call: _e.mock.On("GetPreciseUser", ctx, name, instancename, oreq)}
}
func (_c *Backend_GetPreciseUser_Call) Run(run func(ctx context.Context, name string, instancename string, oreq *godata.GoDataRequest)) *Backend_GetPreciseUser_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*godata.GoDataRequest))
})
return _c
}
func (_c *Backend_GetPreciseUser_Call) Return(_a0 *libregraph.User, _a1 error) *Backend_GetPreciseUser_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Backend_GetPreciseUser_Call) RunAndReturn(run func(context.Context, string, string, *godata.GoDataRequest) (*libregraph.User, error)) *Backend_GetPreciseUser_Call {
_c.Call.Return(run)
return _c
}
// GetUser provides a mock function with given fields: ctx, nameOrID, oreq
func (_m *Backend) GetUser(ctx context.Context, nameOrID string, oreq *godata.GoDataRequest) (*libregraph.User, error) {
ret := _m.Called(ctx, nameOrID, oreq)

View File

@@ -69,7 +69,8 @@ type DriveItemPermissionsProvider interface {
// DriveItemPermissionsService contains the production business logic for everything that relates to permissions on drive items.
type DriveItemPermissionsService struct {
BaseGraphService
tp trace.TracerProvider
tp trace.TracerProvider
identityBackend identity.Backend
}
type permissionType int
@@ -83,7 +84,7 @@ const (
)
// NewDriveItemPermissionsService creates a new DriveItemPermissionsService
func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, config *config.Config, tp trace.TracerProvider) (DriveItemPermissionsService, error) {
func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], identityCache identity.IdentityCache, config *config.Config, tp trace.TracerProvider, be identity.Backend) (DriveItemPermissionsService, error) {
return DriveItemPermissionsService{
BaseGraphService: BaseGraphService{
logger: &log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemService").Logger()},
@@ -91,7 +92,8 @@ func NewDriveItemPermissionsService(logger log.Logger, gatewaySelector pool.Sele
identityCache: identityCache,
config: config,
},
tp: tp,
tp: tp,
identityBackend: be,
}, nil
}
@@ -190,12 +192,17 @@ func (s DriveItemPermissionsService) Invite(ctx context.Context, resourceId *sto
expiration = createShareResponse.GetShare().GetExpiration()
default:
user, err := s.identityCache.GetUser(ctx, objectID)
if errors.Is(err, identity.ErrNotFound) && s.config.IncludeOCMSharees {
user, err = s.identityCache.GetAcceptedUser(ctx, objectID)
if err == nil && IsSpaceRoot(statResponse.GetInfo().GetId()) {
err = errorcode.New(errorcode.InvalidRequest, "federated user can not become a space member")
s.logger.Error().Err(err).Str("resourceId", resourceId.GetStorageId()).Interface("userId", objectID).Msg(err.Error())
return libregraph.Permission{}, err
if errors.Is(err, identity.ErrNotFound) {
if s.config.MultiInstance.Enabled {
user, err = s.identityBackend.AddUser(ctx, objectID, s.config.MultiInstance.InstanceID)
}
if s.config.IncludeOCMSharees && err != nil {
user, err = s.identityCache.GetAcceptedUser(ctx, objectID)
if err == nil && IsSpaceRoot(statResponse.GetInfo().GetId()) {
err = errorcode.New(errorcode.InvalidRequest, "federated user can not become a space member")
s.logger.Error().Err(err).Str("resourceId", resourceId.GetStorageId()).Interface("userId", objectID).Msg(err.Error())
return libregraph.Permission{}, err
}
}
}
if err != nil {

View File

@@ -55,7 +55,7 @@ var _ = Describe("createLinkTests", func() {
cache := identity.NewIdentityCache(identity.IdentityCacheWithGatewaySelector(gatewaySelector))
cfg := defaults.FullDefaultConfig()
svc, err = service.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg, otel.GetTracerProvider())
svc, err = service.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg, otel.GetTracerProvider(), nil)
Expect(err).ToNot(HaveOccurred())
driveItemId = &provider.ResourceId{
StorageId: "1",

View File

@@ -80,7 +80,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
cfg.UnifiedRoles.AvailableRoles = slices.DeleteFunc(cfg.UnifiedRoles.AvailableRoles, func(s string) bool {
return s == unifiedrole.UnifiedRoleSecureViewerID
})
service, err := svc.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg, otel.GetTracerProvider())
service, err := svc.NewDriveItemPermissionsService(logger, gatewaySelector, cache, cfg, otel.GetTracerProvider(), nil)
Expect(err).ToNot(HaveOccurred())
driveItemPermissionsService = service
ctx = revactx.ContextSetUser(context.Background(), currentUser)
@@ -270,7 +270,7 @@ var _ = Describe("DriveItemPermissionsService", func() {
// remove SecureViewer from allowed roles for this unit test
return s == unifiedrole.UnifiedRoleSecureViewerID
})
service, err := svc.NewDriveItemPermissionsService(log.NewLogger(), gatewaySelector, cache, cfg, otel.GetTracerProvider())
service, err := svc.NewDriveItemPermissionsService(log.NewLogger(), gatewaySelector, cache, cfg, otel.GetTracerProvider(), nil)
Expect(err).ToNot(HaveOccurred())
driveItemInvite.Roles = []string{unifiedrole.UnifiedRoleViewerID, unifiedrole.UnifiedRoleSecureViewerID}

View File

@@ -75,7 +75,7 @@ var _ = Describe("Users changing their own password", func() {
GroupSearchScope: "sub",
}
loggger := log.NewLogger()
identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger)
identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger, "")
Expect(err).To(BeNil())
eventsPublisher = mocks.Publisher{}

View File

@@ -213,7 +213,7 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
return svc, err
}
driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config, options.TraceProvider)
driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config, options.TraceProvider, svc.identityBackend)
if err != nil {
return svc, err
}
@@ -473,7 +473,11 @@ func setIdentityBackends(options Options, svc *Graph) error {
TLSConfig: tlsConf,
},
)
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger)
var iid string
if options.Config.MultiInstance.Enabled {
iid = options.Config.MultiInstance.InstanceID
}
lb, err := identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger, iid)
if err != nil {
options.Logger.Error().Err(err).Msg("Error initializing LDAP Backend")
return err

View File

@@ -310,6 +310,7 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
hasMFA := mfa.Has(r.Context())
if !hasAcceptableSearch(odataReq.Query, g.config.API.IdentitySearchMinLength) {
if !ctxHasFullPerms {
// for regular user the search term must have a minimum length
@@ -357,10 +358,15 @@ func (g Graph) GetUsers(w http.ResponseWriter, r *http.Request) {
logger.Debug().Interface("query", r.URL.Query()).Msg("calling get users on backend")
var users []*libregraph.User
username, instancename := g.parseExternalSearch(odataReq)
if odataReq.Query.Filter != nil {
switch {
case username != "" && instancename != "":
users = make([]*libregraph.User, 1)
users[0], err = g.identityBackend.GetPreciseUser(r.Context(), username, instancename, odataReq)
case odataReq.Query.Filter != nil:
users, err = g.applyUserFilter(r.Context(), odataReq, nil)
} else {
default:
users, err = g.identityBackend.GetUsers(r.Context(), odataReq)
}
@@ -1170,3 +1176,21 @@ func (g Graph) searchOCMAcceptedUsers(ctx context.Context, odataReq *godata.GoDa
}
return users, nil
}
// with multi-instance enabled, one can share to another instance by entering `username@instancename`
func (g Graph) parseExternalSearch(req *godata.GoDataRequest) (string, string) {
if !g.config.MultiInstance.Enabled {
return "", ""
}
if req == nil || req.Query == nil || req.Query.Search == nil {
return "", ""
}
unquoted := strings.Trim(req.Query.Search.RawValue, "\"")
parts := regexp.MustCompile(g.config.MultiInstance.QueryRegexp).FindStringSubmatch(unquoted)
// parts[0] contains complete expression
if len(parts) != 3 {
return "", ""
}
return parts[1], parts[2]
}

View File

@@ -103,12 +103,6 @@ func DefaultConfig() *config.Config {
MultiFactorAuthentication: config.MFAConfig{
AuthLevelNames: []string{"advanced"},
},
MultiInstance: config.MultiInstanceConfig{
InstanceID: "c1f28cf2-d363-4671-a8fe-8d7a80b36b87",
MemberClaim: "memberOf",
GuestClaim: "guestOf",
GuestRole: "user-light",
},
}
}

View File

@@ -111,7 +111,7 @@ func (ra oidcRoleAssigner) UpdateUserRoleAssignment(ctx context.Context, user *c
if overwriteRole == "" {
claimRoles, err := extractRoles(ra.rolesClaim, claims)
if err != nil {
logger.Error().Err(err).Msg("Error mapping role names to role ids")
logger.Error().Err(err).Str("Claim", ra.rolesClaim).Interface("claims", claims).Msg("Error mapping role names to role ids")
return nil, err
}