Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117926249
D4130.1775450386.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
132 KB
Referenced Files
None
Subscribers
None
D4130.1775450386.diff
View Options
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,8 +3,7 @@
* Make sure you have docker and docker-compose available.
* Change to the base directory of this repository.
* Run 'HOST=kolab.local ADMIN_PASSWORD="simple123" bin/configure.sh config.prod' to configure this deployment.
-* Run 'bin/deploy.sh' to start the deployment.
-* Run 'docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:password admin@kolab.local simple123' to set your admin password
+* Run 'env ADMIN_PASSWORD="simple123" bin/deploy.sh' to start the deployment.
* Add an /etc/hosts entry "127.0.0.1 kolab.local"
* navigate to https://kolab.local
* login as "admin@kolab.local" with password "simple123" (or whatever you have set), and create your users.
diff --git a/ansible/setup.yml b/ansible/setup.yml
--- a/ansible/setup.yml
+++ b/ansible/setup.yml
@@ -58,6 +58,7 @@
FIREBASE_API_KEY: "{{ firebase_api_key }}"
PUBLIC_IP: "{{ public_ip }}"
ADMIN_PASSWORD: "{{ admin_password }}"
+ KOLAB_GIT_REF: "{{ git_branch }}"
- name: Permit receiving mail
@@ -122,15 +123,10 @@
ansible.builtin.command: bin/deploy.sh
args:
chdir: /home/kolab/kolab
+ environment:
+ ADMIN_PASSWORD: "{{ admin_password }}"
register: result
always:
- name: Print output from previous task with newlines
ansible.builtin.debug:
msg="{{result.stdout_lines}}"
-
- - name: Set admin password
- become: true
- become_user: kolab
- ansible.builtin.command: docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:password admin@{{ hostname }} {{ admin_password|quote }}
- args:
- chdir: /home/kolab/kolab
diff --git a/bin/configure.sh b/bin/configure.sh
--- a/bin/configure.sh
+++ b/bin/configure.sh
@@ -78,6 +78,10 @@
echo "PASSPORT_PUBLIC_KEY=\"${PASSPORT_PUBLIC_KEY}\"" >> src/.env
fi
+if ! grep -q "KOLAB_GIT_REF=" .env; then
+ echo "KOLAB_GIT_REF=${KOLAB_GIT_REF:-master}" >> src/.env
+fi
+
# Customize configuration
sed -i \
-e "s/{{ host }}/${HOST:-kolab.local}/g" \
diff --git a/bin/deploy.sh b/bin/deploy.sh
--- a/bin/deploy.sh
+++ b/bin/deploy.sh
@@ -1,2 +1,6 @@
#!/bin/bash
bin/quickstart.sh --nodev
+if [[ -n $ADMIN_PASSWORD ]]; then
+ DOMAIN=$(grep APP_DOMAIN .env | tail -n1 | sed "s/APP_DOMAIN=//")
+ docker exec -w /src/kolabsrc/ kolab-webapp ./artisan user:password "admin@$DOMAIN" "$ADMIN_PASSWORD"
+fi
diff --git a/bin/quickstart.sh b/bin/quickstart.sh
--- a/bin/quickstart.sh
+++ b/bin/quickstart.sh
@@ -20,7 +20,7 @@
export DOCKER_BUILDKIT=0
-docker-compose down --remove-orphans
+docker-compose down -t 1 --remove-orphans
docker volume rm kolab_mariadb || :
docker volume rm kolab_imap || :
docker volume rm kolab_ldap || :
@@ -33,8 +33,8 @@
pkill -9 -f swoole || :
bin/regen-certs
-docker-compose build coturn kolab mariadb meet pdns proxy redis haproxy roundcube
-docker-compose up -d coturn kolab mariadb meet pdns redis roundcube
+docker-compose build coturn ldap kolab mariadb meet pdns proxy redis haproxy roundcube
+docker-compose up -d coturn ldap kolab mariadb meet pdns redis roundcube
# Workaround until we have docker-compose --wait (https://github.com/docker/compose/pull/8777)
function wait_for_container {
diff --git a/bin/regen-certs b/bin/regen-certs
--- a/bin/regen-certs
+++ b/bin/regen-certs
@@ -30,7 +30,7 @@
exit 1
fi
-export $(cat ${base_dir}/src/.env | xargs) >/dev/null 2>&1
+APP_DOMAIN=$(grep APP_DOMAIN .env | tail -n1 | sed "s/APP_DOMAIN=//")
for name in kolab.mgmt.com kolab.hosted.com imap.hosted.com {{admin,meet}.,}${APP_DOMAIN}; do
openssl genrsa -out ${cert_dir}/${name}.key 4096
diff --git a/ci/Makefile b/ci/Makefile
--- a/ci/Makefile
+++ b/ci/Makefile
@@ -13,7 +13,7 @@
lint:
docker kill kolab-tests || : ; \
- kolab rm kolab-tests || : ; \
+ docker rm kolab-tests || : ; \
docker run -v ${PWD}/../:/src/kolab.orig -t kolab-tests /lint.sh
test:
diff --git a/config.demo/src/.env b/config.demo/src/.env
--- a/config.demo/src/.env
+++ b/config.demo/src/.env
@@ -18,7 +18,7 @@
APP_WITH_FILES=1
APP_LDAP=1
-APP_IMAP=0
+APP_IMAP=1
APP_HEADER_CSP="connect-src 'self'; child-src 'self'; font-src 'self'; form-action 'self' data:; frame-ancestors 'self'; img-src blob: data: 'self' *; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; default-src 'self';"
APP_HEADER_XFO=sameorigin
@@ -69,7 +69,7 @@
LDAP_BASE_DN="dc=mgmt,dc=com"
LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
-LDAP_HOSTS=kolab
+LDAP_HOSTS=ldap
LDAP_PORT=389
LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
LDAP_SERVICE_BIND_PW="Welcome2KolabSystems"
diff --git a/config.dev/docker-compose.override.yml b/config.dev/docker-compose.override.yml
--- a/config.dev/docker-compose.override.yml
+++ b/config.dev/docker-compose.override.yml
@@ -2,7 +2,6 @@
services:
kolab:
ports:
- - "389:389"
- "8880:8880"
- "8443:8443"
- "10143:10143"
@@ -13,6 +12,9 @@
mariadb:
ports:
- "3306:3306"
+ ldap:
+ ports:
+ - "389:389"
redis:
ports:
- "6379:6379"
diff --git a/config.prod/src/.env b/config.prod/src/.env
--- a/config.prod/src/.env
+++ b/config.prod/src/.env
@@ -68,7 +68,7 @@
LDAP_BASE_DN="dc=mgmt,dc=com"
LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
-LDAP_HOSTS=kolab
+LDAP_HOSTS=ldap
LDAP_PORT=389
LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
LDAP_SERVICE_BIND_PW="{{ admin_password }}"
diff --git a/docker-compose.build.yml b/docker-compose.build.yml
--- a/docker-compose.build.yml
+++ b/docker-compose.build.yml
@@ -4,7 +4,7 @@
build:
context: ./docker/swoole/
container_name: kolab-swoole
- image: apheleia/swoole:4.8.x
+ image: apheleia/swoole
tests:
build:
context: ./docker/tests/
diff --git a/docker-compose.yml b/docker-compose.yml
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -24,24 +24,32 @@
DB_KOLAB_DATABASE: kolab
DB_KOLAB_USERNAME: kolab
DB_KOLAB_PASSWORD: ${DB_PASSWORD:?"DB_PASSWORD is missing"}
+ LDAP_HOST: ldap
+ LDAP_ADMIN_BIND_DN: ${LDAP_ADMIN_BIND_DN}
+ LDAP_ADMIN_BIND_PW: ${LDAP_ADMIN_BIND_PW}
+ LDAP_SERVICE_BIND_PW: ${LDAP_SERVICE_BIND_PW}
+ IMAP_ADMIN_LOGIN: ${IMAP_ADMIN_LOGIN}
+ IMAP_ADMIN_PASSWORD: ${IMAP_ADMIN_PASSWORD}
container_name: kolab
privileged: true
restart: on-failure
+ tty: true
depends_on:
mariadb:
condition: service_healthy
pdns:
condition: service_healthy
+ ldap:
+ condition: service_healthy
extra_hosts:
- "kolab.mgmt.com:127.0.0.1"
- "services.${APP_DOMAIN}:172.18.0.4"
environment:
- APP_DOMAIN=${APP_DOMAIN}
- - LDAP_HOST=127.0.0.1
+ - LDAP_HOST=ldap
- LDAP_ADMIN_BIND_DN=${LDAP_ADMIN_BIND_DN}
- LDAP_ADMIN_BIND_PW=${LDAP_ADMIN_BIND_PW}
- LDAP_SERVICE_BIND_PW=${LDAP_SERVICE_BIND_PW}
- - LDAP_HOSTED_BIND_PW=${LDAP_HOSTED_BIND_PW}
- DB_HOST=mariadb
- DB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- DB_HKCCP_DATABASE=${DB_DATABASE}
@@ -91,11 +99,45 @@
- ./docker/kolab/utils:/root/utils:ro
- /sys/fs/cgroup:/sys/fs/cgroup:ro
- imap:/imapdata
+
+ ldap:
+ build:
+ context: ./docker/ldap/
+ container_name: kolab-ldap
+ restart: on-failure
+ tty: true
+ hostname: ldap
+ privileged: true
+ environment:
+ - APP_DOMAIN=${APP_DOMAIN}
+ - LDAP_ADMIN_ROOT_DN=${LDAP_ADMIN_ROOT_DN}
+ - LDAP_ADMIN_BIND_DN=${LDAP_ADMIN_BIND_DN}
+ - LDAP_ADMIN_BIND_PW=${LDAP_ADMIN_BIND_PW}
+ - LDAP_SERVICE_BIND_PW=${LDAP_SERVICE_BIND_PW}
+ - LDAP_HOSTED_BIND_PW=${LDAP_HOSTED_BIND_PW}
+ - IMAP_ADMIN_PASSWORD=${IMAP_ADMIN_PASSWORD}
+ healthcheck:
+ interval: 10s
+ test: "systemctl status dirsrv@kolab || exit 1"
+ timeout: 5s
+ retries: 30
+ start_period: 5m
+ image: kolab-ldap
+ networks:
+ kolab:
+ ipv4_address: 172.18.0.12
+ tmpfs:
+ - /run
+ - /tmp
+ - /var/run
+ - /var/tmp
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:ro
- ldap:/ldapdata
roundcube:
build:
context: ./docker/roundcube/
- container_name: roundcube
+ container_name: kolab-roundcube
hostname: roundcube.hosted.com
restart: on-failure
depends_on:
@@ -107,7 +149,7 @@
condition: service_healthy
environment:
- APP_DOMAIN=${APP_DOMAIN}
- - LDAP_HOST=kolab
+ - LDAP_HOST=ldap
- LDAP_ADMIN_BIND_DN=${LDAP_ADMIN_BIND_DN}
- LDAP_ADMIN_BIND_PW=${LDAP_ADMIN_BIND_PW}
- LDAP_SERVICE_BIND_PW=${LDAP_SERVICE_BIND_PW}
@@ -198,6 +240,7 @@
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD}
container_name: kolab-pdns
restart: on-failure
+ tty: true
hostname: pdns
depends_on:
mariadb:
@@ -237,6 +280,8 @@
webapp:
build:
context: ./docker/webapp/
+ args:
+ GIT_REF: ${KOLAB_GIT_REF:-master}
container_name: kolab-webapp
restart: on-failure
image: kolab-webapp
@@ -263,6 +308,8 @@
meet:
build:
context: ./docker/meet/
+ args:
+ GIT_REF: ${KOLAB_GIT_REF:-master}
container_name: kolab-meet
restart: on-failure
healthcheck:
diff --git a/docker/kolab/Dockerfile b/docker/kolab/Dockerfile
--- a/docker/kolab/Dockerfile
+++ b/docker/kolab/Dockerfile
@@ -54,10 +54,22 @@
ARG DB_KOLAB_DATABASE
ARG DB_KOLAB_USERNAME
ARG DB_KOLAB_PASSWORD
+ARG LDAP_HOST
+ARG LDAP_ADMIN_BIND_DN
+ARG LDAP_ADMIN_BIND_PW
+ARG LDAP_SERVICE_BIND_PW
+ARG IMAP_ADMIN_LOGIN
+ARG IMAP_ADMIN_PASSWORD
RUN sed -i -r \
-e "s|DB_KOLAB_DATABASE|$DB_KOLAB_DATABASE|g" \
-e "s|DB_KOLAB_USERNAME|$DB_KOLAB_USERNAME|g" \
-e "s|DB_KOLAB_PASSWORD|$DB_KOLAB_PASSWORD|g" \
+ -e "s|LDAP_HOST|$LDAP_HOST|g" \
+ -e "s|LDAP_ADMIN_BIND_DN|$LDAP_ADMIN_BIND_DN|g" \
+ -e "s|LDAP_ADMIN_BIND_PW|$LDAP_ADMIN_BIND_PW|g" \
+ -e "s|LDAP_SERVICE_BIND_PW|$LDAP_SERVICE_BIND_PW|g" \
+ -e "s|IMAP_ADMIN_LOGIN|$IMAP_ADMIN_LOGIN|g" \
+ -e "s|IMAP_ADMIN_PASSWORD|$IMAP_ADMIN_PASSWORD|g" \
/etc/kolab/kolab.conf
RUN mkdir -p /imapdata/{spool,lib} && \
@@ -66,15 +78,8 @@
chmod -R 777 /imapdata && \
chown cyrus:mail /var/spool/imap /var/lib/imap
-RUN mkdir -p /ldapdata/{config,ssca,run} /var/run/dirsrv && \
- ln -s /ldapdata/config /etc/dirsrv/slapd-kolab && \
- ln -s /ldapdata/ssca /etc/dirsrv/ssca && \
- ln -s /ldapdata/run /var/run/dirsrv && \
- chmod -R 777 /ldapdata /etc/dirsrv
-
VOLUME [ "/sys/fs/cgroup" ]
VOLUME [ "/imapdata" ]
-VOLUME [ "/ldapdata" ]
WORKDIR /root/
diff --git a/docker/kolab/imapd.conf b/docker/kolab/imapd.conf
--- a/docker/kolab/imapd.conf
+++ b/docker/kolab/imapd.conf
@@ -32,6 +32,7 @@
delete_mode: delayed
expunge_mode: delayed
postuser: shared
+sharedprefix: shared
# on systems with cyrus 3+ specify search engine
# search_engine: squat
chatty: 1
diff --git a/docker/kolab/kolab-init.sh b/docker/kolab/kolab-init.sh
--- a/docker/kolab/kolab-init.sh
+++ b/docker/kolab/kolab-init.sh
@@ -4,7 +4,6 @@
./01-reverse-etc-hosts.sh && echo "01 done"
./02-write-my.cnf.sh && echo "02 done"
-./03-setup-ldap.sh && echo "03 ldap done"
./03-setup-kolab.sh && echo "03 kolab done"
./04-reset-mysql-kolab-password.sh && echo "04 done"
./05-adjust-configs.sh && echo "05 done"
diff --git a/docker/kolab/kolab.conf b/docker/kolab/kolab.conf
--- a/docker/kolab/kolab.conf
+++ b/docker/kolab/kolab.conf
@@ -12,14 +12,14 @@
virtual_domains = userid
[ldap]
-ldap_uri = ldap://127.0.0.1:389
+ldap_uri = ldap://LDAP_HOST:389
timeout = 10
supported_controls = 0,2,3
base_dn = dc=mgmt,dc=com
-bind_dn = cn=Directory Manager
-bind_pw =
+bind_dn = LDAP_ADMIN_BIND_DN
+bind_pw = LDAP_ADMIN_BIND_PW
service_bind_dn = uid=kolab-service,ou=Special Users,dc=mgmt,dc=com
-service_bind_pw =
+service_bind_pw = LDAP_SERVICE_BIND_PW
user_base_dn = dc=hosted,dc=com
user_scope = sub
user_filter = (objectclass=inetorgperson)
@@ -66,8 +66,8 @@
[cyrus-imap]
uri = imaps://127.0.0.1:11993
-admin_login = cyrus-admin
-admin_password =
+admin_login = IMAP_ADMIN_LOGIN
+admin_password = IMAP_ADMIN_PASSWORD
[cyrus-sasl]
result_attribute = mail
diff --git a/docker/kolab/utils/03-setup-ldap.sh b/docker/kolab/utils/03-setup-ldap.sh
deleted file mode 100755
--- a/docker/kolab/utils/03-setup-ldap.sh
+++ /dev/null
@@ -1,260 +0,0 @@
-#!/bin/bash
-
-. ./settings.sh
-
-cp -av /bin/true /usr/sbin/ds_systemd_ask_password_acl
-
-if [ -f "/etc/dirsrv/slapd-kolab/dse.ldif" ]; then
- echo "LDAP directory exists, nothing to do"
-
- mkdir -p /var/log/dirsrv/slapd-kolab/
- chmod 777 /var/log/dirsrv/slapd-kolab/
- systemctl start dirsrv@kolab
- mkdir /run/dirsrv
- chmod 777 /run/dirsrv
- mkdir -p /run/lock/dirsrv/slapd-kolab/
- chown dirsrv:dirsrv /run/lock/dirsrv/slapd-kolab/
- chmod 777 /run/lock/dirsrv/slapd-kolab/
- mkdir -p /var/lib/dirsrv/slapd-kolab
- chown dirsrv:dirsrv /var/lib/dirsrv/slapd-kolab
-
- systemctl start dirsrv@kolab
-else
- sed -i -e 's/sys.exit/print("exit") #sys.exit/' /usr/lib/python3.6/site-packages/pykolab/setup/setup_ldap.py
-
- echo "LDAP directory does not exist, setting it up."
- CMD="$(which setup-kolab) ldap \
- --default ${LDAP_HOST} \
- --fqdn=kolab.${domain} \
- --directory-manager-pwd=${LDAP_ADMIN_BIND_PW}"
- ${CMD} 2>&1 | tee -a /root/setup-kolab.log
-
-
- # Create hosted kolab service
- (
- echo "dn: uid=hosted-kolab-service,ou=Special Users,${rootdn}"
- echo "objectclass: top"
- echo "objectclass: inetorgperson"
- echo "objectclass: person"
- echo "uid: hosted-kolab-service"
- echo "cn: Hosted Kolab Service Account"
- echo "sn: Service Account"
- echo "givenname: Hosted Kolab"
- echo "userpassword: ${hosted_kolab_service_pw}"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- # Create ou domain
- (
- echo "dn: ou=Domains,${rootdn}"
- echo "ou: Domains"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- # Create management domain
- (
- echo "dn: associateddomain=${domain},${domain_base_dn}"
- echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Rest\";deny (all)(userdn != \"ldap:///uid=kolab-service,ou=Special Users,${rootdn} || ldap:///${rootdn}??sub?(objectclass=*)\");)"
- echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Hosted Kolab\";deny (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
- echo "inetDomainStatus: active"
- echo "objectClass: top"
- echo "objectClass: domainrelatedobject"
- echo "objectClass: inetdomain"
- echo "associatedDomain: ${domain}"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
-
- # Create hosted domains
- (
- echo "dn: associateddomain=${hosted_domain},${domain_base_dn}"
- echo "objectclass: top"
- echo "objectclass: domainrelatedobject"
- echo "objectclass: inetdomain"
- echo "inetdomainstatus: active"
- echo "associateddomain: ${hosted_domain}"
- echo "inetdomainbasedn: ${hosted_domain_rootdn}"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- (
- echo "dn: cn=$(echo ${hosted_domain} | sed -e 's/\./_/g'),cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: extensibleobject"
- echo "objectClass: nsbackendinstance"
- echo "cn: $(echo ${hosted_domain} | sed -e 's/\./_/g')"
- echo "nsslapd-suffix: ${hosted_domain_rootdn}"
- echo "nsslapd-cachesize: -1"
- echo "nsslapd-cachememsize: 10485760"
- echo "nsslapd-readonly: off"
- echo "nsslapd-require-index: off"
- echo "nsslapd-directory: /var/lib/dirsrv/slapd-${DS_INSTANCE_NAME:-$(hostname -s)}/db/$(echo ${hosted_domain} | sed -e 's/\./_/g')"
- echo "nsslapd-dncachememsize: 10485760"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- (
- #On centos7
- #echo "dn: cn=$(echo ${hosted_domain_rootdn} | sed -e 's/=/\\3D/g' -e 's/,/\\2D/g'),cn=mapping tree,cn=config"
- #On centos8
- echo "dn: cn=\"${hosted_domain_rootdn}\",cn=mapping tree,cn=config"
- echo "objectClass: top"
- echo "objectClass: extensibleObject"
- echo "objectClass: nsMappingTree"
- echo "nsslapd-state: backend"
- echo "cn: ${hosted_domain_rootdn}"
- echo "nsslapd-backend: $(echo ${hosted_domain} | sed -e 's/\./_/g')"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- (
- echo "dn: ${hosted_domain_rootdn}"
- echo "aci: (targetattr=\"carLicense || description || displayName || facsimileTelephoneNumber || homePhone || homePostalAddress || initials || jpegPhoto || labeledURI || mobile || pager || photo || postOfficeBox || postalAddress || postalCode || preferredDeliveryMethod || preferredLanguage || registeredAddress || roomNumber || secretary || seeAlso || st || street || telephoneNumber || telexNumber || title || userCertificate || userPassword || userSMIMECertificate || x500UniqueIdentifier\")(version 3.0; acl \"Enable self write for common attributes\"; allow (write) userdn=\"ldap:///self\";)"
- echo "aci: (targetattr =\"*\")(version 3.0;acl \"Directory Administrators Group\";allow (all) (groupdn=\"ldap:///cn=Directory Administrators,${hosted_domain_rootdn}\" or roledn=\"ldap:///cn=kolab-admin,${hosted_domain_rootdn}\");)"
- echo "aci: (targetattr=\"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)"
- echo "aci: (targetattr=\"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)"
- echo "aci: (targetattr = \"*\")(version 3.0; acl \"SIE Group\"; allow (all) groupdn = \"ldap:///cn=slapd-$(hostname -s),cn=389 Directory Server,cn=Server Group,cn=$(hostname -f),ou=${domain},o=NetscapeRoot\";)"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Search Access\";allow (read,compare,search)(userdn = \"ldap:///${hosted_domain_rootdn}??sub?(objectclass=*)\");)"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Service Search Access\";allow (read,compare,search)(userdn = \"ldap:///uid=kolab-service,ou=Special Users,${rootdn}\");)"
- echo "objectClass: top"
- echo "objectClass: domain"
- echo "dc: $(echo ${hosted_domain} | cut -d'.' -f 1)"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
- (
- for role in "2fa-user" "activesync-user" "imap-user"; do
- echo "dn: cn=${role},${hosted_domain_rootdn}"
- echo "cn: ${role}"
- echo "description: ${role} role"
- echo "objectclass: top"
- echo "objectclass: ldapsubentry"
- echo "objectclass: nsmanagedroledefinition"
- echo "objectclass: nsroledefinition"
- echo "objectclass: nssimpleroledefinition"
- echo ""
- done
-
- echo "dn: ou=Groups,${hosted_domain_rootdn}"
- echo "ou: Groups"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
-
- echo "dn: ou=People,${hosted_domain_rootdn}"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Hosted Kolab Services\";allow (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
- echo "ou: People"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
-
- echo "dn: ou=Special Users,${hosted_domain_rootdn}"
- echo "ou: Special Users"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
-
- echo "dn: ou=Resources,${hosted_domain_rootdn}"
- echo "ou: Resources"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
-
- echo "dn: ou=Shared Folders,${hosted_domain_rootdn}"
- echo "ou: Shared Folders"
- echo "objectClass: top"
- echo "objectClass: organizationalunit"
- echo ""
-
- echo "dn: uid=cyrus-admin,ou=Special Users,${hosted_domain_rootdn}"
- echo "sn: Administrator"
- echo "uid: cyrus-admin"
- echo "objectClass: top"
- echo "objectClass: person"
- echo "objectClass: inetorgperson"
- echo "objectClass: organizationalperson"
- echo "givenName: Cyrus"
- echo "cn: Cyrus Administrator"
- echo ""
-
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
-
- # Remove cn kolab cn config
- (
- echo "associateddomain=${domain},cn=kolab,cn=config"
- echo "cn=kolab,cn=config"
- ) | ldapdelete -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-
- # Remove hosted service access from mgmt domain
- (
- echo "dn: associateddomain=${domain},ou=Domains,${rootdn}"
- echo "changetype: modify"
- echo "replace: aci"
- echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Rest\";deny (all)(userdn != \"ldap:///uid=kolab-service,ou=Special Users,${rootdn} || ldap:///${rootdn}??sub?(objectclass=*)\");)"
- echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Hosted Kolab\";deny (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
- echo ""
- ) | ldapmodify -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
-
-
- # Add alias attribute index
- #
- export index_attr=alias
-
- (
- echo "dn: cn=${index_attr},cn=index,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectclass: top"
- echo "objectclass: nsindex"
- echo "cn: ${index_attr}"
- echo "nsSystemIndex: false"
- echo "nsindextype: pres"
- echo "nsindextype: eq"
- echo "nsindextype: sub"
-
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-
- (
- echo "dn: cn=${hosted_domain_db} ${index_attr} index,cn=index,cn=tasks,cn=config"
- echo "objectclass: top"
- echo "objectclass: extensibleObject"
- echo "cn: ${hosted_domain_db} ${index_attr} index"
- echo "nsinstance: ${hosted_domain_db}"
- echo "nsIndexAttribute: ${index_attr}:pres"
- echo "nsIndexAttribute: ${index_attr}:eq"
- echo "nsIndexAttribute: ${index_attr}:sub"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
- ldap_complete=0
-
- while [ ${ldap_complete} -ne 1 ]; do
- result=$(
- ldapsearch \
- -x \
- -h "${ldap_host}" \
- -D "${ldap_binddn}" \
- -w "${ldap_bindpw}" \
- -c \
- -LLL \
- -b "cn=${hosted_domain_db} ${index_attr} index,cn=index,cn=tasks,cn=config" \
- '(!(nstaskexitcode=0))' \
- -s base 2>/dev/null
- )
- if [ -z "$result" ]; then
- ldap_complete=1
- echo ""
- else
- echo -n "."
- sleep 1
- fi
- done
-
- ./50-add-vlv-searches.sh
- ./51-add-vlv-indexes.sh
- ./52-run-vlv-index-tasks.sh
-fi
-
diff --git a/docker/kolab/utils/50-add-vlv-searches.sh b/docker/kolab/utils/50-add-vlv-searches.sh
deleted file mode 100755
--- a/docker/kolab/utils/50-add-vlv-searches.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-
- . ./settings.sh
-
-(
- echo "dn: cn=PVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvSearch"
- echo "cn: PVS"
- echo "vlvBase: ${hosted_domain_rootdn}"
- echo "vlvScope: 2"
- echo "vlvFilter: (objectclass=inetorgperson)"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-(
- echo "dn: cn=RVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvSearch"
- echo "cn: RVS"
- echo "vlvBase: ${hosted_domain_rootdn}"
- echo "vlvScope: 2"
- echo "vlvFilter: (|(&(objectclass=kolabsharedfolder)(kolabfoldertype=event)(mail=*))(objectclass=groupofuniquenames)(objectclass=groupofurls))"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-(
- echo "dn: cn=GVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvSearch"
- echo "cn: GVS"
- echo "vlvBase: ${hosted_domain_rootdn}"
- echo "vlvScope: 2"
- echo "vlvFilter: (|(objectclass=groupofuniquenames)(objectclass=groupofurls))"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
- (
- echo "dn: cn=DVS,cn=${domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvSearch"
- echo "cn: DVS"
- echo "vlvBase: ${domain_base_dn}"
- echo "vlvScope: 2"
- echo "vlvFilter: (objectclass=domainrelatedobject)"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-fi
diff --git a/docker/kolab/utils/51-add-vlv-indexes.sh b/docker/kolab/utils/51-add-vlv-indexes.sh
deleted file mode 100755
--- a/docker/kolab/utils/51-add-vlv-indexes.sh
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/bash
-
- . ./settings.sh
-
-(
- echo "dn: cn=PVI,cn=PVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvIndex"
- echo "cn: PVI"
- echo "vlvSort: displayname sn givenname cn"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-(
- echo "dn: cn=RVI,cn=RVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvIndex"
- echo "cn: RVI"
- echo "vlvSort: cn"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-(
- echo "dn: cn=GVI,cn=GVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvIndex"
- echo "cn: GVI"
- echo "vlvSort: cn"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
-) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
- (
- echo "dn: cn=DVI,cn=DVS,cn=${domain_db},cn=ldbm database,cn=plugins,cn=config"
- echo "objectClass: top"
- echo "objectClass: vlvIndex"
- echo "cn: DVI"
- echo "vlvSort: associatedDomain"
- echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
- echo ""
- ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-fi
diff --git a/docker/kolab/utils/52-run-vlv-index-tasks.sh b/docker/kolab/utils/52-run-vlv-index-tasks.sh
deleted file mode 100755
--- a/docker/kolab/utils/52-run-vlv-index-tasks.sh
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/bin/bash
-
- . ./settings.sh
-
-(
- echo "dn: cn=PVI,cn=index,cn=tasks,cn=config"
- echo "objectclass: top"
- echo "objectclass: extensibleObject"
- echo "cn: PVI"
- echo "nsinstance: ${hosted_domain_db}"
- echo "nsIndexVLVAttribute: PVI"
- echo ""
-) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-ldap_complete=0
-
-while [ ${ldap_complete} -ne 1 ]; do
- result=$(
- ldapsearch \
- -x \
- -h ${ldap_host} \
- -D "${ldap_binddn}" \
- -w "${ldap_bindpw}" \
- -c \
- -LLL \
- -b "cn=PVI,cn=index,cn=tasks,cn=config" \
- '(!(nstaskexitcode=0))' \
- -s base 2>/dev/null
- )
- if [ -z "$result" ]; then
- ldap_complete=1
- echo ""
- else
- echo -n "."
- sleep 1
- fi
-done
-
-(
- echo "dn: cn=RVI,cn=index,cn=tasks,cn=config"
- echo "objectclass: top"
- echo "objectclass: extensibleObject"
- echo "cn: RVI"
- echo "nsinstance: ${hosted_domain_db}"
- echo "nsIndexVLVAttribute: RVI"
- echo ""
-) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-ldap_complete=0
-
-while [ ${ldap_complete} -ne 1 ]; do
- result=$(
- ldapsearch \
- -x \
- -h ${ldap_host} \
- -D "${ldap_binddn}" \
- -w "${ldap_bindpw}" \
- -c \
- -LLL \
- -b "cn=RVI,cn=index,cn=tasks,cn=config" \
- '(!(nstaskexitcode=0))' \
- -s base 2>/dev/null
- )
- if [ -z "$result" ]; then
- ldap_complete=1
- echo ""
- else
- echo -n "."
- sleep 1
- fi
-done
-
-
-
-(
- echo "dn: cn=GVI,cn=index,cn=tasks,cn=config"
- echo "objectclass: top"
- echo "objectclass: extensibleObject"
- echo "cn: GVI"
- echo "nsinstance: ${hosted_domain_db}"
- echo "nsIndexVLVAttribute: GVI"
- echo ""
-) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
-ldap_complete=0
-
-while [ ${ldap_complete} -ne 1 ]; do
- result=$(
- ldapsearch \
- -x \
- -h ${ldap_host} \
- -D "${ldap_binddn}" \
- -w "${ldap_bindpw}" \
- -c \
- -LLL \
- -b "cn=GVI,cn=index,cn=tasks,cn=config" \
- '(!(nstaskexitcode=0))' \
- -s base 2>/dev/null
- )
- if [ -z "$result" ]; then
- ldap_complete=1
- echo ""
- else
- echo -n "."
- sleep 1
- fi
-done
-
-if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
- (
- echo "dn: cn=DVI,cn=index,cn=tasks,cn=config"
- echo "objectclass: top"
- echo "objectclass: extensibleObject"
- echo "cn: DVI"
- echo "nsinstance: ${domain_db}"
- echo "nsIndexVLVAttribute: DVI"
- echo ""
- ) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
-
- ldap_complete=0
-
- while [ ${ldap_complete} -ne 1 ]; do
- result=$(
- ldapsearch \
- -x \
- -h ${ldap_host} \
- -D "${ldap_binddn}" \
- -w "${ldap_bindpw}" \
- -c \
- -LLL \
- -b "cn=DVI,cn=index,cn=tasks,cn=config" \
- '(!(nstaskexitcode=0))' \
- -s base 2>/dev/null
- )
- if [ -z "$result" ]; then
- ldap_complete=1
- echo ""
- else
- echo -n "."
- sleep 1
- fi
- done
-fi
diff --git a/docker/kolab/utils/cyrusadmin.sh b/docker/kolab/utils/cyrusadmin.sh
new file mode 100755
--- /dev/null
+++ b/docker/kolab/utils/cyrusadmin.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+# We use port 12143 because it has plain auth enabled
+echo "$@" | cyradm --auth PLAIN -u cyrus-admin -w Welcome2KolabSystems --port 12143 localhost
diff --git a/docker/kolab/utils/settings.sh b/docker/kolab/utils/settings.sh
--- a/docker/kolab/utils/settings.sh
+++ b/docker/kolab/utils/settings.sh
@@ -11,7 +11,6 @@
export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD}
export kolab_service_pw=${LDAP_SERVICE_BIND_PW}
-export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW}
export hosted_domain=${HOSTED_DOMAIN:-"hosted.com"}
export hosted_domain_db=${HOSTED_DOMAIN_DB:-"hosted_com"}
diff --git a/docker/kolab/Dockerfile b/docker/ldap/Dockerfile
copy from docker/kolab/Dockerfile
copy to docker/ldap/Dockerfile
--- a/docker/kolab/Dockerfile
+++ b/docker/ldap/Dockerfile
@@ -24,48 +24,17 @@
rpm -Uvh https://mirror.apheleia-it.ch/repos/Kolab:/16/kolab-16-for-el8stream.rpm
RUN sed -i -e '/^ssl/d' /etc/yum.repos.d/kolab*.repo && \
dnf config-manager --enable kolab-16-testing &&\
- dnf -y --setopt tsflags= install kolab patch &&\
+ dnf -y --setopt tsflags= install kolab-schema 389-ds-base &&\
dnf clean all
+COPY init.sh /init.sh
COPY kolab-init.service /etc/systemd/system/kolab-init.service
COPY kolab-setenv.service /etc/systemd/system/kolab-setenv.service
-COPY utils /root/utils
-
-RUN rm -rf /etc/systemd/system/multi-user.target.wants/{avahi-daemon,sshd}.* && \
- ln -s /etc/systemd/system/kolab-init.service \
- /etc/systemd/system/multi-user.target.wants/kolab-init.service && \
- ln -s /etc/systemd/system/kolab-setenv.service \
- /etc/systemd/system/multi-user.target.wants/kolab-setenv.service
+RUN systemctl disable avahi-daemon sshd; systemctl enable kolab-setenv kolab-init
RUN sed -i -r -e 's/^SELINUX=.*$/SELINUX=permissive/g' /etc/selinux/config 2>/dev/null || :
-COPY /rootfs /
-
-COPY kolab-init.sh /usr/local/sbin/
-RUN chmod 750 /usr/local/sbin/kolab-init.sh
-
-COPY kolab.conf /etc/kolab/kolab.conf
-COPY cyrus.conf /etc/cyrus.conf
-COPY imapd.conf /etc/imapd.conf
-COPY imapd.annotations.conf /etc/imapd.annotations.conf
-COPY guam.conf /etc/guam/sys.config
-
-ARG DB_KOLAB_DATABASE
-ARG DB_KOLAB_USERNAME
-ARG DB_KOLAB_PASSWORD
-RUN sed -i -r \
- -e "s|DB_KOLAB_DATABASE|$DB_KOLAB_DATABASE|g" \
- -e "s|DB_KOLAB_USERNAME|$DB_KOLAB_USERNAME|g" \
- -e "s|DB_KOLAB_PASSWORD|$DB_KOLAB_PASSWORD|g" \
- /etc/kolab/kolab.conf
-
-RUN mkdir -p /imapdata/{spool,lib} && \
- rm -rf /var/spool/imap && ln -s /imapdata/spool /var/spool/imap && \
- mv /var/lib/imap /var/lib/imap-bak && ln -s /imapdata/lib /var/lib/imap && \
- chmod -R 777 /imapdata && \
- chown cyrus:mail /var/spool/imap /var/lib/imap
-
RUN mkdir -p /ldapdata/{config,ssca,run} /var/run/dirsrv && \
ln -s /ldapdata/config /etc/dirsrv/slapd-kolab && \
ln -s /ldapdata/ssca /etc/dirsrv/ssca && \
@@ -73,11 +42,10 @@
chmod -R 777 /ldapdata /etc/dirsrv
VOLUME [ "/sys/fs/cgroup" ]
-VOLUME [ "/imapdata" ]
VOLUME [ "/ldapdata" ]
WORKDIR /root/
CMD ["/lib/systemd/systemd"]
-EXPOSE 10143/tcp 10465/tcp 10587/tcp 11143/tcp 11993/tcp
+EXPOSE 389/tcp
diff --git a/docker/ldap/init.sh b/docker/ldap/init.sh
new file mode 100755
--- /dev/null
+++ b/docker/ldap/init.sh
@@ -0,0 +1,809 @@
+#!/bin/bash
+
+# Disable password checking
+cp -av /bin/true /usr/sbin/ds_systemd_ask_password_acl
+
+# Make sure all the relvant folders exist in /ldapdata
+mkdir -p /ldapdata/{config,ssca,run}
+chmod -R 777 /ldapdata
+
+mkdir -p /var/log/dirsrv/slapd-kolab/
+chmod 777 /var/log/dirsrv/slapd-kolab/
+
+mkdir -p /run/dirsrv
+chmod 777 /run/dirsrv
+
+mkdir -p /run/lock/dirsrv/slapd-kolab/
+chown dirsrv:dirsrv /run/lock/dirsrv/slapd-kolab/
+chmod 777 /run/lock/dirsrv/slapd-kolab/
+
+mkdir -p /var/lib/dirsrv/slapd-kolab
+chown dirsrv:dirsrv /var/lib/dirsrv/slapd-kolab
+
+
+if [ -f "/etc/dirsrv/slapd-kolab/dse.ldif" ]; then
+ echo "LDAP directory exists, nothing to do"
+
+ # mkdir -p /var/log/dirsrv/slapd-kolab/
+ # chmod 777 /var/log/dirsrv/slapd-kolab/
+ # systemctl start dirsrv@kolab
+ # mkdir /run/dirsrv
+ # chmod 777 /run/dirsrv
+ # mkdir -p /run/lock/dirsrv/slapd-kolab/
+ # chown dirsrv:dirsrv /run/lock/dirsrv/slapd-kolab/
+ # chmod 777 /run/lock/dirsrv/slapd-kolab/
+ # mkdir -p /var/lib/dirsrv/slapd-kolab
+ # chown dirsrv:dirsrv /var/lib/dirsrv/slapd-kolab
+
+ systemctl start dirsrv@kolab
+ exit 0
+fi
+
+# Used for the graphical console only.
+GRAPHICAL_ADMIN_PASSWORD="-22F_EjHut5JCcd"
+DS_INSTANCE_NAME="kolab"
+DOMAIN="mgmt.com"
+FQDN="ldap.mgmt.com"
+
+cat << EOF > /tmp/dscreateinput
+[general]
+FullMachineName = ldap.mgmt.com
+SuiteSpotUserID = dirsrv
+SuiteSpotGroup = dirsrv
+AdminDomain = mgmt.com
+ConfigDirectoryLdapURL = ldap://ldap.mgmt.com:389/o=NetscapeRoot
+ConfigDirectoryAdminID = admin
+ConfigDirectoryAdminPwd = $GRAPHICAL_ADMIN_PASSWORD
+full_machine_name = ldap.mgmt.com
+
+[slapd]
+SlapdConfigForMC = Yes
+UseExistingMC = 0
+ServerPort = 389
+ServerIdentifier = kolab
+Suffix = $LDAP_ADMIN_ROOT_DN
+RootDN = cn=Directory Manager
+RootDNPwd = $LDAP_ADMIN_BIND_PW
+ds_bename = mgmt_com
+AddSampleEntries = No
+instance_name = $DS_INSTANCE_NAME
+root_password = $LDAP_ADMIN_BIND_PW
+create_suffix_entry = True
+
+[backend-userroot]
+suffix = $LDAP_ADMIN_ROOT_DN
+create_suffix_entry = True
+
+[admin]
+Port = 9830
+ServerAdminID = admin
+ServerAdminPwd = $GRAPHICAL_ADMIN_PASSWORD
+
+EOF
+dscreate -v from-file /tmp/dscreateinput
+
+cp /usr/share/dirsrv/data/template.ldif /tmp/templatedata.ldif
+sed -i "s/%ds_suffix%/$LDAP_BASE_DN/" /tmp/templatedata.ldif
+sed -i "s/%rootdn%/cn=Directory Manager/" /tmp/templatedata.ldif
+ldapadd -x -H 'ldap://127.0.0.1:389/' -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/templatedata.ldif
+
+
+#FIXME in kolab container setup kolab.conf entries
+
+
+cp /usr/share/doc/kolab-schema/kolab3.ldif /etc/dirsrv/slapd-kolab/schema/99kolab3.ldif
+
+systemctl restart dirsrv.target
+systemctl restart dirsrv@kolab
+systemctl enable dirsrv.target
+systemctl enable dirsrv@kolab
+
+
+
+# I'm not sure why we need to create those manually
+cat << EOF > /tmp/ldapadd
+
+# Directory Administrators, mgmt.com
+dn: cn=Directory Administrators,dc=mgmt,dc=com
+objectClass: top
+objectClass: groupofuniquenames
+cn: Directory Administrators
+uniqueMember: cn=Directory Manager
+
+# Groups, mgmt.com
+dn: ou=Groups,dc=mgmt,dc=com
+objectClass: top
+objectClass: organizationalunit
+ou: Groups
+
+# People, mgmt.com
+dn: ou=People,dc=mgmt,dc=com
+objectClass: top
+objectClass: organizationalunit
+ou: People
+
+# Special Users, mgmt.com
+dn: ou=Special Users,dc=mgmt,dc=com
+objectClass: top
+objectClass: organizationalUnit
+ou: Special Users
+description: Special Administrative Accounts
+
+# Accounting Managers, Groups, mgmt.com
+dn: cn=Accounting Managers,ou=Groups,dc=mgmt,dc=com
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: Accounting Managers
+ou: groups
+description: People who can manage accounting entries
+uniqueMember: cn=Directory Manager
+
+# HR Managers, Groups, mgmt.com
+dn: cn=HR Managers,ou=Groups,dc=mgmt,dc=com
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: HR Managers
+ou: groups
+description: People who can manage HR entries
+uniqueMember: cn=Directory Manager
+
+# QA Managers, Groups, mgmt.com
+dn: cn=QA Managers,ou=Groups,dc=mgmt,dc=com
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: QA Managers
+ou: groups
+description: People who can manage QA entries
+uniqueMember: cn=Directory Manager
+
+# PD Managers, Groups, mgmt.com
+dn: cn=PD Managers,ou=Groups,dc=mgmt,dc=com
+objectClass: top
+objectClass: groupOfUniqueNames
+cn: PD Managers
+ou: groups
+description: People who can manage engineer entries
+uniqueMember: cn=Directory Manager
+
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+
+## =========== Start of pykolab changes
+# Work that pykolab used to do
+#
+cat << EOF > /tmp/ldapadd
+# cyrus-admin, Special Users, mgmt.com
+dn: uid=cyrus-admin,ou=Special Users,dc=mgmt,dc=com
+objectClass: top
+objectClass: person
+objectClass: inetorgperson
+objectClass: organizationalperson
+uid: cyrus-admin
+givenName: Cyrus
+sn: Administrator
+cn: Cyrus Administrator
+userPassword: ${IMAP_ADMIN_PW}
+
+# kolab-service, Special Users, mgmt.com
+dn: uid=kolab-service,ou=Special Users,dc=mgmt,dc=com
+objectClass: top
+objectClass: person
+objectClass: inetorgperson
+objectClass: organizationalperson
+uid: kolab-service
+givenName: Kolab
+sn: Service
+cn: Kolab Service
+userPassword: ${LDAP_SERVICE_BIND_PW}
+
+# Resources, mgmt.com
+dn: ou=Resources,dc=mgmt,dc=com
+objectClass: top
+objectClass: organizationalunit
+ou: Resources
+
+# Shared Folders, mgmt.com
+dn: ou=Shared Folders,dc=mgmt,dc=com
+objectClass: top
+objectClass: organizationalunit
+ou: Shared Folders
+
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+
+cat << EOF > /tmp/ldapadd
+dn: cn=kolab,cn=config
+cn: kolab
+aci: (targetattr = "*") (version 3.0;acl "Kolab Services";allow (read,compare,search)(userdn = "ldap:///uid=kolab-service,ou=Special Users,$LDAP_ADMIN_ROOT_DN");)
+objectClass: top
+objectClass: extensibleobject
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Adding domain $DOMAIN to list of domains for this deployment"
+cat << EOF > /tmp/ldapadd
+dn: associateddomain=$DOMAIN,cn=kolab,cn=config
+objectClass: top
+objectClass: domainrelatedobject
+associatedDomain: $DOMAIN, $FQDN, localhost.localdomain, localhost
+aci: (targetattr = "*") (version 3.0;acl "Read Access for $DOMAIN Users";allow (read,compare,search)(userdn = "ldap:///$LDAP_ADMIN_ROOT_DN??sub?(objectclass=*)");)
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+##TODO
+ ## Add inetdomainbasedn in case the configured root dn is not the same as the
+ ## standard root dn for the domain name configured
+ #if not _input['rootdn'] == utils.standard_root_dn(_input['domain']):
+ # attrs['objectclass'].append('inetdomain')
+ # attrs['inetdomainbasedn'] = _input['rootdn']
+
+echo "Disabling anonymous binds"
+cat << EOF > /tmp/ldapadd
+dn: cn=config
+changetype: modify
+replace: nsslapd-allow-anonymous-access
+nsslapd-allow-anonymous-access: off
+EOF
+ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+
+## TODO: Ensure the uid attribute is unique
+## TODO^2: Consider renaming the general "attribute uniqueness to "uid attribute uniqueness"
+echo "Enabling attribute uniqueness plugin"
+cat << EOF > /tmp/ldapadd
+dn: cn=attribute uniqueness,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-pluginEnabled
+nsslapd-pluginEnabled: on
+EOF
+ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Enabling referential integrity plugin"
+cat << EOF > /tmp/ldapadd
+dn: cn=referential integrity postoperation,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-pluginEnabled
+nsslapd-pluginEnabled: on
+EOF
+ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Enabling referential integrity plugin"
+cat << EOF > /tmp/ldapadd
+dn: cn=referential integrity postoperation,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-pluginEnabled
+nsslapd-pluginEnabled: on
+EOF
+ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Enabling and configuring account policy plugin"
+cat << EOF > /tmp/ldapadd
+dn: cn=Account Policy Plugin,cn=plugins,cn=config
+changetype: modify
+replace: nsslapd-pluginEnabled
+nsslapd-pluginEnabled: on
+
+dn: cn=config,cn=Account Policy Plugin,cn=plugins,cn=config
+changetype: modify
+replace: alwaysrecordlogin
+alwaysrecordlogin: yes
+-
+add: stateattrname
+stateattrname: lastLoginTime
+-
+add: altstateattrname
+altstateattrname: createTimestamp
+EOF
+ldapmodify -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Adding the kolab-admin role"
+cat << EOF > /tmp/ldapadd
+dn: cn=kolab-admin,$LDAP_ADMIN_ROOT_DN
+description: Kolab Administrator
+objectClass: top
+objectClass: ldapsubentry
+objectClass: nsroledefinition
+objectClass: nssimpleroledefinition
+objectClass: nsmanagedroledefinition
+cn = kolab-admin
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+echo "Setting access control to $LDAP_ADMIN_ROOT_DN"
+cat << EOF > /tmp/ldapadd
+dn: $LDAP_ADMIN_ROOT_DN
+changetype: modify
+replace: aci
+aci: (targetattr = "carLicense || description || displayName || facsimileTelephoneNumber || homePhone || homePostalAddress || initials || jpegPhoto || l || labeledURI || mobile || o || pager || photo || postOfficeBox || postalAddress || postalCode || preferredDeliveryMethod || preferredLanguage || registeredAddress || roomNumber || secretary || seeAlso || st || street || telephoneNumber || telexNumber || title || userCertificate || userPassword || userSMIMECertificate || x500UniqueIdentifier || kolabDelegate || kolabInvitationPolicy || kolabAllowSMTPSender")(version 3.0; acl "Enable self write for common attributes"; allow (read,compare,search,write)(userdn = "ldap:///self");)
+aci: (targetattr = "*")(version 3.0;acl "Directory Administrators Group";allow (all)(groupdn = "ldap:///cn=Directory Administrators,$LDAP_ADMIN_ROOT_DN" or roledn = "ldap:///cn=kolab-admin,$LDAP_ADMIN_ROOT_DN");)
+aci: (targetattr="*")(version 3.0; acl "Configuration Administrators Group"; allow (all) groupdn="ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot";)
+aci: (targetattr="*")(version 3.0; acl "Configuration Administrator"; allow (all) userdn="ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot";)
+aci: (targetattr = "*")(version 3.0; acl "SIE Group"; allow (all) groupdn = "ldap:///cn=slapd-$DS_INSTANCE_NAME,cn=389 Directory Server,cn=Server Group,cn=$FQDN,ou=$DOMAIN,o=NetscapeRoot";)
+aci: (targetattr != "userPassword") (version 3.0;acl "Search Access";allow (read,compare,search)(userdn = "ldap:///all");)')
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+## =========== End of pykolab code
+
+# Create hosted kolab service
+cat << EOF > /tmp/ldapadd
+dn: uid=hosted-kolab-service,ou=Special Users,${LDAP_ADMIN_ROOT_DN}
+objectclass: top
+objectclass: inetorgperson
+objectclass: person
+uid: hosted-kolab-service
+cn: Hosted Kolab Service Account
+sn: Service Account
+givenname: Hosted Kolab
+userpassword: ${LDAP_HOSTED_BIND_PW}
+
+EOF
+ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -w "$LDAP_ADMIN_BIND_PW" -f /tmp/ldapadd
+
+export rootdn=$LDAP_ADMIN_ROOT_DN
+export domain=$DOMAIN
+export domain_db="mgmt_com"
+export ldap_host=127.0.0.1
+export ldap_binddn=${LDAP_ADMIN_BIND_DN}
+export ldap_bindpw=${LDAP_ADMIN_BIND_PW}
+
+export cyrus_admin=${IMAP_ADMIN_LOGIN}
+export cyrus_admin_pw=${IMAP_ADMIN_PASSWORD}
+
+export kolab_service_pw=${LDAP_SERVICE_BIND_PW}
+export hosted_kolab_service_pw=${LDAP_HOSTED_BIND_PW}
+
+export hosted_domain=${HOSTED_DOMAIN:-"hosted.com"}
+export hosted_domain_db=${HOSTED_DOMAIN_DB:-"hosted_com"}
+export hosted_domain_rootdn=${LDAP_HOSTED_ROOT_DN:-"dc=hosted,dc=com"}
+
+export domain_base_dn=${LDAP_DOMAIN_BASE_DN:-"ou=Domains,dc=mgmt,dc=com"}
+
+
+# Create ou domain
+(
+ echo "dn: ou=Domains,${rootdn}"
+ echo "ou: Domains"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+# Create management domain
+(
+ echo "dn: associateddomain=${domain},${domain_base_dn}"
+ echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Rest\";deny (all)(userdn != \"ldap:///uid=kolab-service,ou=Special Users,${rootdn} || ldap:///${rootdn}??sub?(objectclass=*)\");)"
+ echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Hosted Kolab\";deny (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
+ echo "inetDomainStatus: active"
+ echo "objectClass: top"
+ echo "objectClass: domainrelatedobject"
+ echo "objectClass: inetdomain"
+ echo "associatedDomain: ${domain}"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+
+# Create hosted domains
+(
+ echo "dn: associateddomain=${hosted_domain},${domain_base_dn}"
+ echo "objectclass: top"
+ echo "objectclass: domainrelatedobject"
+ echo "objectclass: inetdomain"
+ echo "inetdomainstatus: active"
+ echo "associateddomain: ${hosted_domain}"
+ echo "inetdomainbasedn: ${hosted_domain_rootdn}"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+(
+ echo "dn: cn=$(echo ${hosted_domain} | sed -e 's/\./_/g'),cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: extensibleobject"
+ echo "objectClass: nsbackendinstance"
+ echo "cn: $(echo ${hosted_domain} | sed -e 's/\./_/g')"
+ echo "nsslapd-suffix: ${hosted_domain_rootdn}"
+ echo "nsslapd-cachesize: -1"
+ echo "nsslapd-cachememsize: 10485760"
+ echo "nsslapd-readonly: off"
+ echo "nsslapd-require-index: off"
+ echo "nsslapd-directory: /var/lib/dirsrv/slapd-${DS_INSTANCE_NAME}/db/$(echo ${hosted_domain} | sed -e 's/\./_/g')"
+ echo "nsslapd-dncachememsize: 10485760"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+(
+ #On centos7
+ #echo "dn: cn=$(echo ${hosted_domain_rootdn} | sed -e 's/=/\\3D/g' -e 's/,/\\2D/g'),cn=mapping tree,cn=config"
+ #On centos8
+ echo "dn: cn=\"${hosted_domain_rootdn}\",cn=mapping tree,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: extensibleObject"
+ echo "objectClass: nsMappingTree"
+ echo "nsslapd-state: backend"
+ echo "cn: ${hosted_domain_rootdn}"
+ echo "nsslapd-backend: $(echo ${hosted_domain} | sed -e 's/\./_/g')"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+(
+ echo "dn: ${hosted_domain_rootdn}"
+ echo "aci: (targetattr=\"carLicense || description || displayName || facsimileTelephoneNumber || homePhone || homePostalAddress || initials || jpegPhoto || labeledURI || mobile || pager || photo || postOfficeBox || postalAddress || postalCode || preferredDeliveryMethod || preferredLanguage || registeredAddress || roomNumber || secretary || seeAlso || st || street || telephoneNumber || telexNumber || title || userCertificate || userPassword || userSMIMECertificate || x500UniqueIdentifier\")(version 3.0; acl \"Enable self write for common attributes\"; allow (write) userdn=\"ldap:///self\";)"
+ echo "aci: (targetattr =\"*\")(version 3.0;acl \"Directory Administrators Group\";allow (all) (groupdn=\"ldap:///cn=Directory Administrators,${hosted_domain_rootdn}\" or roledn=\"ldap:///cn=kolab-admin,${hosted_domain_rootdn}\");)"
+ echo "aci: (targetattr=\"*\")(version 3.0; acl \"Configuration Administrators Group\"; allow (all) groupdn=\"ldap:///cn=Configuration Administrators,ou=Groups,ou=TopologyManagement,o=NetscapeRoot\";)"
+ echo "aci: (targetattr=\"*\")(version 3.0; acl \"Configuration Administrator\"; allow (all) userdn=\"ldap:///uid=admin,ou=Administrators,ou=TopologyManagement,o=NetscapeRoot\";)"
+ echo "aci: (targetattr = \"*\")(version 3.0; acl \"SIE Group\"; allow (all) groupdn = \"ldap:///cn=slapd-${DS_INSTANCE_NAME},cn=389 Directory Server,cn=Server Group,cn=$FQDN,ou=${domain},o=NetscapeRoot\";)"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Search Access\";allow (read,compare,search)(userdn = \"ldap:///${hosted_domain_rootdn}??sub?(objectclass=*)\");)"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Service Search Access\";allow (read,compare,search)(userdn = \"ldap:///uid=kolab-service,ou=Special Users,${rootdn}\");)"
+ echo "objectClass: top"
+ echo "objectClass: domain"
+ echo "dc: $(echo ${hosted_domain} | cut -d'.' -f 1)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+(
+ for role in "2fa-user" "activesync-user" "imap-user"; do
+ echo "dn: cn=${role},${hosted_domain_rootdn}"
+ echo "cn: ${role}"
+ echo "description: ${role} role"
+ echo "objectclass: top"
+ echo "objectclass: ldapsubentry"
+ echo "objectclass: nsmanagedroledefinition"
+ echo "objectclass: nsroledefinition"
+ echo "objectclass: nssimpleroledefinition"
+ echo ""
+ done
+
+ echo "dn: ou=Groups,${hosted_domain_rootdn}"
+ echo "ou: Groups"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+
+ echo "dn: ou=People,${hosted_domain_rootdn}"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Hosted Kolab Services\";allow (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
+ echo "ou: People"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+
+ echo "dn: ou=Special Users,${hosted_domain_rootdn}"
+ echo "ou: Special Users"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+
+ echo "dn: ou=Resources,${hosted_domain_rootdn}"
+ echo "ou: Resources"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+
+ echo "dn: ou=Shared Folders,${hosted_domain_rootdn}"
+ echo "ou: Shared Folders"
+ echo "objectClass: top"
+ echo "objectClass: organizationalunit"
+ echo ""
+
+ echo "dn: uid=cyrus-admin,ou=Special Users,${hosted_domain_rootdn}"
+ echo "sn: Administrator"
+ echo "uid: cyrus-admin"
+ echo "objectClass: top"
+ echo "objectClass: person"
+ echo "objectClass: inetorgperson"
+ echo "objectClass: organizationalperson"
+ echo "givenName: Cyrus"
+ echo "cn: Cyrus Administrator"
+ echo ""
+
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+
+# Remove cn kolab cn config
+(
+ echo "associateddomain=${domain},cn=kolab,cn=config"
+ echo "cn=kolab,cn=config"
+) | ldapdelete -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+
+# Remove hosted service access from mgmt domain
+(
+ echo "dn: associateddomain=${domain},ou=Domains,${rootdn}"
+ echo "changetype: modify"
+ echo "replace: aci"
+ echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Rest\";deny (all)(userdn != \"ldap:///uid=kolab-service,ou=Special Users,${rootdn} || ldap:///${rootdn}??sub?(objectclass=*)\");)"
+ echo "aci: (targetattr = \"*\")(version 3.0;acl \"Deny Hosted Kolab\";deny (all)(userdn = \"ldap:///uid=hosted-kolab-service,ou=Special Users,${rootdn}\");)"
+ echo ""
+) | ldapmodify -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}"
+
+
+# Add alias attribute index
+#
+export index_attr=alias
+
+(
+ echo "dn: cn=${index_attr},cn=index,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: nsindex"
+ echo "cn: ${index_attr}"
+ echo "nsSystemIndex: false"
+ echo "nsindextype: pres"
+ echo "nsindextype: eq"
+ echo "nsindextype: sub"
+
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+
+(
+ echo "dn: cn=${hosted_domain_db} ${index_attr} index,cn=index,cn=tasks,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: extensibleObject"
+ echo "cn: ${hosted_domain_db} ${index_attr} index"
+ echo "nsinstance: ${hosted_domain_db}"
+ echo "nsIndexAttribute: ${index_attr}:pres"
+ echo "nsIndexAttribute: ${index_attr}:eq"
+ echo "nsIndexAttribute: ${index_attr}:sub"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+ldap_complete=0
+
+while [ ${ldap_complete} -ne 1 ]; do
+ result=$(
+ ldapsearch \
+ -x \
+ -h "${ldap_host}" \
+ -D "${ldap_binddn}" \
+ -w "${ldap_bindpw}" \
+ -c \
+ -LLL \
+ -b "cn=${hosted_domain_db} ${index_attr} index,cn=index,cn=tasks,cn=config" \
+ '(!(nstaskexitcode=0))' \
+ -s base 2>/dev/null
+ )
+ if [ -z "$result" ]; then
+ ldap_complete=1
+ echo ""
+ else
+ echo -n "."
+ sleep 1
+ fi
+done
+
+
+# Add VLV searches
+(
+ echo "dn: cn=PVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvSearch"
+ echo "cn: PVS"
+ echo "vlvBase: ${hosted_domain_rootdn}"
+ echo "vlvScope: 2"
+ echo "vlvFilter: (objectclass=inetorgperson)"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+(
+ echo "dn: cn=RVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvSearch"
+ echo "cn: RVS"
+ echo "vlvBase: ${hosted_domain_rootdn}"
+ echo "vlvScope: 2"
+ echo "vlvFilter: (|(&(objectclass=kolabsharedfolder)(kolabfoldertype=event)(mail=*))(objectclass=groupofuniquenames)(objectclass=groupofurls))"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+(
+ echo "dn: cn=GVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvSearch"
+ echo "cn: GVS"
+ echo "vlvBase: ${hosted_domain_rootdn}"
+ echo "vlvScope: 2"
+ echo "vlvFilter: (|(objectclass=groupofuniquenames)(objectclass=groupofurls))"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
+ (
+ echo "dn: cn=DVS,cn=${domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvSearch"
+ echo "cn: DVS"
+ echo "vlvBase: ${domain_base_dn}"
+ echo "vlvScope: 2"
+ echo "vlvFilter: (objectclass=domainrelatedobject)"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+ ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+fi
+
+
+
+# Add vlv indexes
+(
+ echo "dn: cn=PVI,cn=PVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvIndex"
+ echo "cn: PVI"
+ echo "vlvSort: displayname sn givenname cn"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+(
+ echo "dn: cn=RVI,cn=RVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvIndex"
+ echo "cn: RVI"
+ echo "vlvSort: cn"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+(
+ echo "dn: cn=GVI,cn=GVS,cn=${hosted_domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvIndex"
+ echo "cn: GVI"
+ echo "vlvSort: cn"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
+ (
+ echo "dn: cn=DVI,cn=DVS,cn=${domain_db},cn=ldbm database,cn=plugins,cn=config"
+ echo "objectClass: top"
+ echo "objectClass: vlvIndex"
+ echo "cn: DVI"
+ echo "vlvSort: associatedDomain"
+ echo "aci: (targetattr = \"*\") (version 3.0;acl \"Read Access\";allow (read,compare,search)(userdn = \"ldap:///anyone\");)"
+ echo ""
+ ) | ldapadd -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+fi
+
+# Run vlv index tasks
+(
+ echo "dn: cn=PVI,cn=index,cn=tasks,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: extensibleObject"
+ echo "cn: PVI"
+ echo "nsinstance: ${hosted_domain_db}"
+ echo "nsIndexVLVAttribute: PVI"
+ echo ""
+) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+ldap_complete=0
+
+while [ ${ldap_complete} -ne 1 ]; do
+ result=$(
+ ldapsearch \
+ -x \
+ -h ${ldap_host} \
+ -D "${ldap_binddn}" \
+ -w "${ldap_bindpw}" \
+ -c \
+ -LLL \
+ -b "cn=PVI,cn=index,cn=tasks,cn=config" \
+ '(!(nstaskexitcode=0))' \
+ -s base 2>/dev/null
+ )
+ if [ -z "$result" ]; then
+ ldap_complete=1
+ echo ""
+ else
+ echo -n "."
+ sleep 1
+ fi
+done
+
+(
+ echo "dn: cn=RVI,cn=index,cn=tasks,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: extensibleObject"
+ echo "cn: RVI"
+ echo "nsinstance: ${hosted_domain_db}"
+ echo "nsIndexVLVAttribute: RVI"
+ echo ""
+) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+ldap_complete=0
+
+while [ ${ldap_complete} -ne 1 ]; do
+ result=$(
+ ldapsearch \
+ -x \
+ -h ${ldap_host} \
+ -D "${ldap_binddn}" \
+ -w "${ldap_bindpw}" \
+ -c \
+ -LLL \
+ -b "cn=RVI,cn=index,cn=tasks,cn=config" \
+ '(!(nstaskexitcode=0))' \
+ -s base 2>/dev/null
+ )
+ if [ -z "$result" ]; then
+ ldap_complete=1
+ echo ""
+ else
+ echo -n "."
+ sleep 1
+ fi
+done
+
+
+
+(
+ echo "dn: cn=GVI,cn=index,cn=tasks,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: extensibleObject"
+ echo "cn: GVI"
+ echo "nsinstance: ${hosted_domain_db}"
+ echo "nsIndexVLVAttribute: GVI"
+ echo ""
+) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+ldap_complete=0
+
+while [ ${ldap_complete} -ne 1 ]; do
+ result=$(
+ ldapsearch \
+ -x \
+ -h ${ldap_host} \
+ -D "${ldap_binddn}" \
+ -w "${ldap_bindpw}" \
+ -c \
+ -LLL \
+ -b "cn=GVI,cn=index,cn=tasks,cn=config" \
+ '(!(nstaskexitcode=0))' \
+ -s base 2>/dev/null
+ )
+ if [ -z "$result" ]; then
+ ldap_complete=1
+ echo ""
+ else
+ echo -n "."
+ sleep 1
+ fi
+done
+
+if [ "${domain_base_dn}" != "cn=kolab,cn=config" ]; then
+ (
+ echo "dn: cn=DVI,cn=index,cn=tasks,cn=config"
+ echo "objectclass: top"
+ echo "objectclass: extensibleObject"
+ echo "cn: DVI"
+ echo "nsinstance: ${domain_db}"
+ echo "nsIndexVLVAttribute: DVI"
+ echo ""
+ ) | ldapmodify -a -x -h ${ldap_host} -D "${ldap_binddn}" -w "${ldap_bindpw}" -c
+
+ ldap_complete=0
+
+ while [ ${ldap_complete} -ne 1 ]; do
+ result=$(
+ ldapsearch \
+ -x \
+ -h ${ldap_host} \
+ -D "${ldap_binddn}" \
+ -w "${ldap_bindpw}" \
+ -c \
+ -LLL \
+ -b "cn=DVI,cn=index,cn=tasks,cn=config" \
+ '(!(nstaskexitcode=0))' \
+ -s base 2>/dev/null
+ )
+ if [ -z "$result" ]; then
+ ldap_complete=1
+ echo ""
+ else
+ echo -n "."
+ sleep 1
+ fi
+ done
+fi
diff --git a/docker/ldap/kolab-init.service b/docker/ldap/kolab-init.service
new file mode 100644
--- /dev/null
+++ b/docker/ldap/kolab-init.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Kolab Setup Service
+Requires=kolab-setenv.service
+After=kolab-setenv.service ldapdata.mount
+
+[Service]
+Type=oneshot
+EnvironmentFile=/etc/openshift-environment
+ExecStart=/init.sh
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/docker/ldap/kolab-setenv.service b/docker/ldap/kolab-setenv.service
new file mode 100644
--- /dev/null
+++ b/docker/ldap/kolab-setenv.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Kolab Set Environment
+
+[Service]
+Type=oneshot
+ExecStart=/bin/bash -c "cat /proc/1/environ | tr '\0' '\n' > /etc/openshift-environment"
+
+[Install]
+WantedBy=multi-user.target
diff --git a/docker/meet/Dockerfile b/docker/meet/Dockerfile
--- a/docker/meet/Dockerfile
+++ b/docker/meet/Dockerfile
@@ -7,6 +7,7 @@
npm nodejs python3 python3-pip meson ninja-build make gcc g++ git && \
dnf clean all
+ARG GIT_REF=master
COPY build.sh /build.sh
RUN /build.sh
COPY init.sh /init.sh
diff --git a/docker/meet/build.sh b/docker/meet/build.sh
--- a/docker/meet/build.sh
+++ b/docker/meet/build.sh
@@ -2,7 +2,10 @@
set -e
mkdir /src/
cd /src/
-git clone https://git.kolab.org/source/kolab.git kolab
+git clone --branch $GIT_REF https://git.kolab.org/source/kolab.git kolab
+pushd kolab
+git reset --hard $GIT_REF
+popd
cp -R kolab/meet/server /src/meetsrc
rm -Rf /src/meetsrc/node_modules
cd /src/meetsrc
diff --git a/docker/swoole/Dockerfile b/docker/swoole/Dockerfile
--- a/docker/swoole/Dockerfile
+++ b/docker/swoole/Dockerfile
@@ -1,8 +1,8 @@
-FROM fedora:35
+FROM fedora:37
MAINTAINER Jeroen van Meeuwen <vanmeeuwen@apheleia-it.ch>
-ARG SWOOLE_VERSION=v4.11.1
+ARG SWOOLE_VERSION=v5.0.2
ENV HOME=/opt/app-root/src
LABEL io.k8s.description="Platform for serving PHP applications under Swoole" \
@@ -29,7 +29,7 @@
php-mysqlnd \
re2c \
wget && \
- git clone https://github.com/openswoole/swoole-src.git/ /swoole-src.git/ && \
+ git clone https://github.com/swoole/swoole-src.git/ /swoole-src.git/ && \
cd /swoole-src.git/ && \
git checkout -f ${SWOOLE_VERSION} && \
git clean -d -f -x && \
@@ -38,7 +38,6 @@
./configure \
--enable-sockets \
--disable-mysqlnd \
- --enable-http2 \
--enable-openssl && \
make -j4 && \
make install && \
@@ -52,8 +51,8 @@
php-devel \
re2c && \
dnf clean all && \
- echo "extension=openswoole.so" >> /etc/php.d/openswoole.ini && \
- php -m 2>&1 | grep -q openswoole
+ echo "extension=swoole.so" >> /etc/php.d/swoole.ini && \
+ php -m 2>&1 | grep -q swoole
RUN id default || (groupadd -g 1001 default && useradd -d /opt/app-root/ -u 1001 -g 1001 default)
diff --git a/docker/tests/Dockerfile b/docker/tests/Dockerfile
--- a/docker/tests/Dockerfile
+++ b/docker/tests/Dockerfile
@@ -1,4 +1,4 @@
-FROM apheleia/swoole:4.8.x
+FROM apheleia/swoole:latest
MAINTAINER Jeroen van Meeuwen <vanmeeuwen@apheleia-it.ch>
diff --git a/docker/webapp/Dockerfile b/docker/webapp/Dockerfile
--- a/docker/webapp/Dockerfile
+++ b/docker/webapp/Dockerfile
@@ -1,15 +1,17 @@
-FROM apheleia/swoole:4.8.x
+FROM apheleia/swoole:latest
MAINTAINER Jeroen van Meeuwen <vanmeeuwen@apheleia-it.ch>
USER root
-RUN dnf -y install findutils gnupg2 git rsync
+RUN dnf -y install findutils gnupg2 git rsync procps-ng
EXPOSE 8000
+ARG GIT_REF=master
COPY build.sh /build.sh
RUN /build.sh
COPY init.sh /init.sh
+COPY update.sh /update.sh
CMD [ "/init.sh" ]
diff --git a/docker/webapp/build.sh b/docker/webapp/build.sh
--- a/docker/webapp/build.sh
+++ b/docker/webapp/build.sh
@@ -5,7 +5,11 @@
mkdir /src
cd /src
-git clone https://git.kolab.org/source/kolab.git kolab
+git clone --branch $GIT_REF https://git.kolab.org/source/kolab.git kolab
+pushd kolab
+git reset --hard $GIT_REF
+popd
+
cp -a kolab/src /src/kolabsrc
cd /src/kolabsrc
diff --git a/docker/webapp/update.sh b/docker/webapp/update.sh
new file mode 100755
--- /dev/null
+++ b/docker/webapp/update.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+set -e
+set -x
+
+rsync -av \
+ --exclude=vendor \
+ --exclude=composer.lock \
+ --exclude=node_modules \
+ --exclude=package-lock.json \
+ --exclude=public \
+ --exclude=storage \
+ --exclude=resources/build \
+ --exclude=bootstrap \
+ --exclude=.gitignore \
+ /src/kolabsrc.orig/ /src/kolabsrc/ | tee /tmp/rsync.output
+
+cd /src/kolabsrc/
+# Only run npm if something relevant was updated
+if grep -e "package.json" -e "resources" /tmp/rsync.output; then
+ npm run dev
+fi
+./artisan octane:reload
diff --git a/src/app/Backends/DAV.php b/src/app/Backends/DAV.php
--- a/src/app/Backends/DAV.php
+++ b/src/app/Backends/DAV.php
@@ -184,7 +184,8 @@
$folder = DAV\Folder::fromDomElement($element);
// Note: Addressbooks don't have 'type' specified
- if (($component == self::TYPE_VCARD && in_array('addressbook', $folder->types))
+ if (
+ ($component == self::TYPE_VCARD && in_array('addressbook', $folder->types))
|| in_array($component, $folder->components)
) {
$folders[] = $folder;
@@ -303,7 +304,7 @@
// could fetch "an index" but also any of object's data.
$body = '<?xml version="1.0" encoding="utf-8"?>'
- .' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="' . self::NAMESPACES[$component] . '">'
+ . ' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="' . self::NAMESPACES[$component] . '">'
. '<d:prop>'
. '<d:getetag />'
. '</d:prop>'
@@ -359,7 +360,7 @@
];
$body = '<?xml version="1.0" encoding="utf-8"?>'
- .' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="' . self::NAMESPACES[$component] . '">'
+ . ' <c:' . $queries[$component] . ' xmlns:d="DAV:" xmlns:c="' . self::NAMESPACES[$component] . '">'
. '<d:prop>'
. '<d:getetag />'
. '<c:' . $types[$component] . ' />'
diff --git a/src/app/Backends/DAV/CommonObject.php b/src/app/Backends/DAV/CommonObject.php
--- a/src/app/Backends/DAV/CommonObject.php
+++ b/src/app/Backends/DAV/CommonObject.php
@@ -33,7 +33,7 @@
// Extract UID from the URL
$href_parts = explode('/', $object->href);
- $object->uid = preg_replace('/\.[a-z]+$/', '', $href_parts[count($href_parts)-1]);
+ $object->uid = preg_replace('/\.[a-z]+$/', '', $href_parts[count($href_parts) - 1]);
}
if ($etag = $element->getElementsByTagName('getetag')->item(0)) {
diff --git a/src/app/Backends/DAV/Vevent.php b/src/app/Backends/DAV/Vevent.php
--- a/src/app/Backends/DAV/Vevent.php
+++ b/src/app/Backends/DAV/Vevent.php
@@ -108,87 +108,85 @@
}
switch ($prop->name) {
- case 'DTSTART':
- case 'DTEND':
- case 'DUE':
- case 'CREATED':
- case 'LAST-MODIFIED':
- case 'DTSTAMP':
- $key = Str::camel(strtolower($prop->name));
- // These are of type Sabre\VObject\Property\ICalendar\DateTime
- $this->{$key} = $prop;
- break;
-
- case 'RRULE':
- $params = !empty($this->recurrence) ? $this->recurrence : [];
-
- foreach ($prop->getParts() as $k => $v) {
- $params[Str::camel(strtolower($k))] = is_array($v) ? implode(',', $v) : $v;
- }
+ case 'DTSTART':
+ case 'DTEND':
+ case 'DUE':
+ case 'CREATED':
+ case 'LAST-MODIFIED':
+ case 'DTSTAMP':
+ $key = Str::camel(strtolower($prop->name));
+ // These are of type Sabre\VObject\Property\ICalendar\DateTime
+ $this->{$key} = $prop;
+ break;
- if (!empty($params['until'])) {
- $params['until'] = new \DateTime($params['until']);
- }
+ case 'RRULE':
+ $params = !empty($this->recurrence) ? $this->recurrence : [];
- if (empty($params['interval'])) {
- $params['interval'] = 1;
- }
+ foreach ($prop->getParts() as $k => $v) {
+ $params[Str::camel(strtolower($k))] = is_array($v) ? implode(',', $v) : $v;
+ }
- $this->recurrence = array_filter($params);
- break;
+ if (!empty($params['until'])) {
+ $params['until'] = new \DateTime($params['until']);
+ }
- case 'EXDATE':
- case 'RDATE':
- $key = strtolower($prop->name);
- $dates = []; // TODO
+ if (empty($params['interval'])) {
+ $params['interval'] = 1;
+ }
- if (!empty($this->recurrence[$key])) {
- $this->recurrence[$key] = array_merge($this->recurrence[$key], $dates);
- }
- else {
- $this->recurrence[$key] = $dates;
- }
+ $this->recurrence = array_filter($params);
+ break;
- break;
-
- case 'ATTENDEE':
- case 'ORGANIZER':
- $attendee = [
- 'rsvp' => false,
- 'email' => preg_replace('!^mailto:!i', '', (string) $prop),
- ];
-
- $attendeeProps = ['CN', 'PARTSTAT', 'ROLE', 'CUTYPE', 'RSVP', 'DELEGATED-FROM', 'DELEGATED-TO',
- 'SCHEDULE-STATUS', 'SCHEDULE-AGENT', 'SENT-BY'];
-
- foreach ($prop->parameters() as $name => $value) {
- $key = Str::camel(strtolower($name));
- switch ($name) {
- case 'RSVP':
- $params[$key] = strtolower($value) == 'true';
- break;
- case 'CN':
- $params[$key] = str_replace('\,', ',', strval($value));
- break;
- default:
- if (in_array($name, $attendeeProps)) {
- $params[$key] = strval($value);
- }
- break;
+ case 'EXDATE':
+ case 'RDATE':
+ $key = strtolower($prop->name);
+ $dates = []; // TODO
+
+ if (!empty($this->recurrence[$key])) {
+ $this->recurrence[$key] = array_merge($this->recurrence[$key], $dates);
+ } else {
+ $this->recurrence[$key] = $dates;
}
- }
- if ($prop->name == 'ORGANIZER') {
- $attendee['role'] = 'ORGANIZER';
- $attendee['partstat'] = 'ACCEPTED';
+ break;
- $this->organizer = $attendee;
- }
- else if (empty($this->organizer) || $attendee['email'] != $this->organizer['email']) {
- $this->attendees[] = $attendee;
- }
+ case 'ATTENDEE':
+ case 'ORGANIZER':
+ $attendee = [
+ 'rsvp' => false,
+ 'email' => preg_replace('!^mailto:!i', '', (string) $prop),
+ ];
+
+ $attendeeProps = ['CN', 'PARTSTAT', 'ROLE', 'CUTYPE', 'RSVP', 'DELEGATED-FROM', 'DELEGATED-TO',
+ 'SCHEDULE-STATUS', 'SCHEDULE-AGENT', 'SENT-BY'];
+
+ foreach ($prop->parameters() as $name => $value) {
+ $key = Str::camel(strtolower($name));
+ switch ($name) {
+ case 'RSVP':
+ $params[$key] = strtolower($value) == 'true';
+ break;
+ case 'CN':
+ $params[$key] = str_replace('\,', ',', strval($value));
+ break;
+ default:
+ if (in_array($name, $attendeeProps)) {
+ $params[$key] = strval($value);
+ }
+ break;
+ }
+ }
+
+ if ($prop->name == 'ORGANIZER') {
+ $attendee['role'] = 'ORGANIZER';
+ $attendee['partstat'] = 'ACCEPTED';
+
+ $this->organizer = $attendee;
+ } elseif (empty($this->organizer) || $attendee['email'] != $this->organizer['email']) {
+ $this->attendees[] = $attendee;
+ }
- break;
+ break;
}
}
@@ -217,47 +215,46 @@
$value = strval($prop);
switch ($prop->name) {
- case 'TRIGGER':
- foreach ($prop->parameters as $param) {
- if ($param->name == 'VALUE' && $param->getValue() == 'DATE-TIME') {
- $trigger = '@' . $prop->getDateTime()->format('U');
- $alarm['trigger'] = $prop->getDateTime();
+ case 'TRIGGER':
+ foreach ($prop->parameters as $param) {
+ if ($param->name == 'VALUE' && $param->getValue() == 'DATE-TIME') {
+ $trigger = '@' . $prop->getDateTime()->format('U');
+ $alarm['trigger'] = $prop->getDateTime();
+ } elseif ($param->name == 'RELATED') {
+ $alarm['related'] = $param->getValue();
+ }
}
- else if ($param->name == 'RELATED') {
- $alarm['related'] = $param->getValue();
+ /*
+ if (!$trigger && ($values = libcalendaring::parse_alarm_value($value))) {
+ $trigger = $values[2];
}
- }
-/*
- if (!$trigger && ($values = libcalendaring::parse_alarm_value($value))) {
- $trigger = $values[2];
- }
-*/
- if (empty($alarm['trigger'])) {
- $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $value), 'T');
- // if all 0-values have been stripped, assume 'at time'
- if ($alarm['trigger'] == 'P') {
- $alarm['trigger'] = 'PT0S';
+ */
+ if (empty($alarm['trigger'])) {
+ $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $value), 'T');
+ // if all 0-values have been stripped, assume 'at time'
+ if ($alarm['trigger'] == 'P') {
+ $alarm['trigger'] = 'PT0S';
+ }
}
- }
- break;
+ break;
- case 'ACTION':
- $action = $alarm['action'] = strtoupper($value);
- break;
+ case 'ACTION':
+ $action = $alarm['action'] = strtoupper($value);
+ break;
- case 'SUMMARY':
- case 'DESCRIPTION':
- case 'DURATION':
- $alarm[strtolower($prop->name)] = $value;
- break;
+ case 'SUMMARY':
+ case 'DESCRIPTION':
+ case 'DURATION':
+ $alarm[strtolower($prop->name)] = $value;
+ break;
- case 'REPEAT':
- $alarm['repeat'] = (int) $value;
- break;
+ case 'REPEAT':
+ $alarm['repeat'] = (int) $value;
+ break;
- case 'ATTENDEE':
- $alarm['attendees'][] = preg_replace('!^mailto:!i', '', $value);
- break;
+ case 'ATTENDEE':
+ $alarm['attendees'][] = preg_replace('!^mailto:!i', '', $value);
+ break;
}
}
diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php
--- a/src/app/Backends/IMAP.php
+++ b/src/app/Backends/IMAP.php
@@ -2,7 +2,6 @@
namespace App\Backends;
-use App\Domain;
use App\Group;
use App\Resource;
use App\SharedFolder;
@@ -76,6 +75,7 @@
// Mailbox already exists
if (self::folderExists($imap, $mailbox)) {
$imap->closeConnection();
+ self::createDefaultFolders($user);
return true;
}
@@ -107,11 +107,36 @@
$imap->setQuota($mailbox, ['storage' => $quota]);
}
+ self::createDefaultFolders($user);
+
$imap->closeConnection();
return true;
}
+ /**
+ * Create default folders for the user.
+ *
+ * @param \App\User $user User
+ */
+ public static function createDefaultFolders(User $user): void
+ {
+ if ($defaultFolders = \config('imap.default_folders')) {
+ $config = self::getConfig();
+ // Log in as user to set private annotations and subscription state
+ $imap = self::initIMAP($config, $user->email);
+ foreach ($defaultFolders as $name => $folderconfig) {
+ try {
+ $mailbox = self::toUTF7($name);
+ self::createFolder($imap, $mailbox, true, $folderconfig['metadata']);
+ } catch (\Exception $e) {
+ \Log::warning("Failed to create the default folder" . $e->getMessage());
+ }
+ }
+ $imap->closeConnection();
+ }
+ }
+
/**
* Delete a mailbox.
*
@@ -184,28 +209,13 @@
$settings = $resource->getSettings(['invitation_policy', 'folder']);
$mailbox = self::toUTF7($settings['folder']);
- // Mailbox already exists
- if (self::folderExists($imap, $mailbox)) {
- $imap->closeConnection();
- return true;
- }
-
- // Create the shared folder
- if (!$imap->createFolder($mailbox)) {
- \Log::error("Failed to create mailbox {$mailbox}");
- $imap->closeConnection();
- return false;
- }
-
- // Set folder type
- $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => 'event']);
-
- // Set ACL
+ $acl = null;
if (!empty($settings['invitation_policy'])) {
if (preg_match('/^manual:(\S+@\S+)$/', $settings['invitation_policy'], $m)) {
- self::aclUpdate($imap, $mailbox, ["{$m[1]}, full"]);
+ $acl = ["{$m[1]}, full"];
}
}
+ self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => 'event'], $acl);
$imap->closeConnection();
@@ -299,24 +309,7 @@
$acl = !empty($settings['acl']) ? json_decode($settings['acl'], true) : null;
$mailbox = self::toUTF7($settings['folder']);
- // Mailbox already exists
- if (self::folderExists($imap, $mailbox)) {
- $imap->closeConnection();
- return true;
- }
-
- // Create the mailbox
- if (!$imap->createFolder($mailbox)) {
- \Log::error("Failed to create mailbox {$mailbox}");
- $imap->closeConnection();
- return false;
- }
-
- // Set folder type
- $imap->setMetadata($mailbox, ['/shared/vendor/kolab/folder-type' => $folder->type]);
-
- // Set ACL
- self::aclUpdate($imap, $mailbox, $acl);
+ self::createFolder($imap, $mailbox, false, ['/shared/vendor/kolab/folder-type' => $folder->type], $acl);
$imap->closeConnection();
@@ -459,6 +452,30 @@
return false;
}
+ /**
+ * Check if an account is set up
+ *
+ * @param string $username User login (email address)
+ *
+ * @return bool True if an account exists and is set up, False otherwise
+ */
+ public static function verifyDefaultFolders(string $username): bool
+ {
+ $config = self::getConfig();
+ $imap = self::initIMAP($config, $username);
+
+ foreach (\config('imap.default_folders') as $mb => $_metadata) {
+ $mailbox = self::toUTF7($mb);
+ if (!self::folderExists($imap, $mailbox)) {
+ $imap->closeConnection();
+ return false;
+ }
+ }
+
+ $imap->closeConnection();
+ return true;
+ }
+
/**
* Check if we can connect to the imap server
*
@@ -515,6 +532,43 @@
$imap->closeConnection();
}
+ /**
+ * Create a folder and set some default properties
+ *
+ * @param \rcube_imap_generic $imap The imap instance
+ * @param string $mailbox Mailbox name
+ * @param bool $subscribe Subscribe to the folder
+ * @param array $metadata Metadata to set on the folder
+ * @param array $acl Acl to set on the folder
+ *
+ * @return bool True when having a folder created, False if it already existed.
+ * @throws \Exception
+ */
+ private static function createFolder($imap, string $mailbox, $subscribe = false, $metadata = null, $acl = null)
+ {
+ if (self::folderExists($imap, $mailbox)) {
+ return false;
+ }
+
+ if (!$imap->createFolder($mailbox)) {
+ throw new \Exception("Failed to create mailbox {$mailbox}");
+ }
+
+ if ($acl) {
+ self::aclUpdate($imap, $mailbox, $acl, true);
+ }
+
+ if ($subscribe) {
+ $imap->subscribe($mailbox);
+ }
+
+ foreach ($metadata as $key => $value) {
+ $imap->setMetadata($mailbox, [$key => $value]);
+ }
+
+ return true;
+ }
+
/**
* Convert Kolab ACL into IMAP user->rights array
*/
diff --git a/src/app/Console/Commands/User/ResyncCommand.php b/src/app/Console/Commands/User/ResyncCommand.php
--- a/src/app/Console/Commands/User/ResyncCommand.php
+++ b/src/app/Console/Commands/User/ResyncCommand.php
@@ -45,8 +45,9 @@
if ($deleted_only) {
$users->whereNotNull('deleted_at')
- ->where(function($query) {
- $query->where('status', '&', User::STATUS_IMAP_READY)->orWhere('status', '&', User::STATUS_LDAP_READY);
+ ->where(function ($query) {
+ $query->where('status', '&', User::STATUS_IMAP_READY)
+ ->orWhere('status', '&', User::STATUS_LDAP_READY);
});
}
@@ -88,7 +89,7 @@
}
\App\Jobs\User\CreateJob::dispatch($user->id);
- } else if (!empty($req_user)) {
+ } elseif (!empty($req_user)) {
if ($dry_run) {
$this->info("{$user->email}: will be pushed");
continue;
diff --git a/src/app/Domain.php b/src/app/Domain.php
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -44,6 +44,15 @@
// domain has been created in LDAP
public const STATUS_LDAP_READY = 1 << 6;
+ /** @var int The allowed states for this object used in StatusPropertyTrait */
+ private int $allowed_states = self::STATUS_NEW |
+ self::STATUS_ACTIVE |
+ self::STATUS_SUSPENDED |
+ self::STATUS_DELETED |
+ self::STATUS_CONFIRMED |
+ self::STATUS_VERIFIED |
+ self::STATUS_LDAP_READY;
+
// open for public registration
public const TYPE_PUBLIC = 1 << 0;
// zone hosted with us
@@ -170,29 +179,13 @@
*/
public function setStatusAttribute($status)
{
- $new_status = 0;
-
- $allowed_values = [
- self::STATUS_NEW,
- self::STATUS_ACTIVE,
- self::STATUS_SUSPENDED,
- self::STATUS_DELETED,
- self::STATUS_CONFIRMED,
- self::STATUS_VERIFIED,
- self::STATUS_LDAP_READY,
- ];
-
- foreach ($allowed_values as $value) {
- if ($status & $value) {
- $new_status |= $value;
- $status ^= $value;
- }
- }
-
- if ($status > 0) {
+ // Detect invalid flags
+ if ($status & ~$this->allowed_states) {
throw new \Exception("Invalid domain status: {$status}");
}
+ $new_status = $status;
+
if ($this->isPublic()) {
$this->attributes['status'] = $new_status;
return;
diff --git a/src/app/Group.php b/src/app/Group.php
--- a/src/app/Group.php
+++ b/src/app/Group.php
@@ -44,6 +44,13 @@
// group has been created in LDAP
public const STATUS_LDAP_READY = 1 << 4;
+ /** @var int The allowed states for this object used in StatusPropertyTrait */
+ private int $allowed_states = self::STATUS_NEW |
+ self::STATUS_ACTIVE |
+ self::STATUS_SUSPENDED |
+ self::STATUS_DELETED |
+ self::STATUS_LDAP_READY;
+
/** @var array<string, string> The attributes that should be cast */
protected $casts = [
'created_at' => 'datetime:Y-m-d H:i:s',
diff --git a/src/app/Http/Controllers/API/V4/PaymentsController.php b/src/app/Http/Controllers/API/V4/PaymentsController.php
--- a/src/app/Http/Controllers/API/V4/PaymentsController.php
+++ b/src/app/Http/Controllers/API/V4/PaymentsController.php
@@ -376,6 +376,7 @@
$mandate['amount'] = $mandate['minAmount'] = (int) ceil(Payment::MIN_AMOUNT / 100);
$mandate['balance'] = 0;
$mandate['isDisabled'] = !empty($mandate['id']) && $settings['mandate_disabled'];
+ $mandate['isValid'] = !empty($mandate['isValid']);
foreach (['amount', 'balance'] as $key) {
if (($value = $settings["mandate_{$key}"]) !== null) {
@@ -523,16 +524,16 @@
$request['vat_rate_id'] = $rate->id;
switch (\config('app.vat.mode')) {
- case 1:
- // In this mode tax is added on top of the payment. The amount
- // to pay grows, but we keep wallet balance without tax.
- $request['amount'] = $request['amount'] + round($request['amount'] * $rate->rate / 100);
- break;
-
- default:
- // In this mode tax is "swallowed" by the vendor. The payment
- // amount does not change
- break;
+ case 1:
+ // In this mode tax is added on top of the payment. The amount
+ // to pay grows, but we keep wallet balance without tax.
+ $request['amount'] = $request['amount'] + round($request['amount'] * $rate->rate / 100);
+ break;
+
+ default:
+ // In this mode tax is "swallowed" by the vendor. The payment
+ // amount does not change
+ break;
}
}
}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -203,7 +203,7 @@
'enableUsers' => $isController,
'enableWallets' => $isController,
'enableWalletMandates' => $isController,
- 'enableWalletPayments' => !$plan || $plan->mode != 'mandate',
+ 'enableWalletPayments' => $isController && (!$plan || $plan->mode != 'mandate'),
'enableCompanionapps' => $hasBeta,
];
diff --git a/src/app/Http/Middleware/ContentSecurityPolicy.php b/src/app/Http/Middleware/ContentSecurityPolicy.php
--- a/src/app/Http/Middleware/ContentSecurityPolicy.php
+++ b/src/app/Http/Middleware/ContentSecurityPolicy.php
@@ -21,6 +21,11 @@
'xfo' => 'X-Frame-Options',
];
+ //Exclude horizon routes, per https://github.com/laravel/horizon/issues/576
+ if ($request->is('horizon*')) {
+ $headers = [];
+ }
+
$next = $next($request);
foreach ($headers as $opt => $header) {
diff --git a/src/app/Payment.php b/src/app/Payment.php
--- a/src/app/Payment.php
+++ b/src/app/Payment.php
@@ -106,7 +106,15 @@
*/
public function credit($method): void
{
- // TODO: Possibly we should sanity check that payment is paid, and not negative?
+ if (empty($this->wallet)) {
+ throw new \Exception("Cannot credit a payment not assigned to a wallet");
+ }
+
+ if ($this->credit_amount < 0) {
+ throw new \Exception("Cannot credit a payment with negative amount");
+ }
+
+ // TODO: Possibly we should sanity check that payment is paid?
// TODO: Localization?
$description = $this->type == self::TYPE_RECURRING ? 'Auto-payment' : 'Payment';
$description .= " transaction {$this->id} using {$method}";
diff --git a/src/app/Providers/AuthServiceProvider.php b/src/app/Providers/AuthServiceProvider.php
--- a/src/app/Providers/AuthServiceProvider.php
+++ b/src/app/Providers/AuthServiceProvider.php
@@ -2,10 +2,7 @@
namespace App\Providers;
-use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
-use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
@@ -25,17 +22,5 @@
public function boot()
{
$this->registerPolicies();
-
- Passport::tokensCan([
- 'api' => 'Access API',
- 'mfa' => 'Access MFA API',
- ]);
-
- Passport::tokensExpireIn(now()->addMinutes(\config('auth.token_expiry_minutes')));
- Passport::refreshTokensExpireIn(now()->addMinutes(\config('auth.refresh_token_expiry_minutes')));
- Passport::personalAccessTokensExpireIn(now()->addMonths(6));
-
- Passport::useClientModel(\App\Auth\PassportClient::class);
- Passport::tokenModel()::observe(\App\Observers\Passport\TokenObserver::class);
}
}
diff --git a/src/app/Providers/PassportServiceProvider.php b/src/app/Providers/PassportServiceProvider.php
--- a/src/app/Providers/PassportServiceProvider.php
+++ b/src/app/Providers/PassportServiceProvider.php
@@ -10,6 +10,26 @@
class PassportServiceProvider extends \Laravel\Passport\PassportServiceProvider
{
+ /**
+ * Register any authentication / authorization services.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ Passport::tokensCan([
+ 'api' => 'Access API',
+ 'mfa' => 'Access MFA API',
+ ]);
+
+ Passport::tokensExpireIn(now()->addMinutes(\config('auth.token_expiry_minutes')));
+ Passport::refreshTokensExpireIn(now()->addMinutes(\config('auth.refresh_token_expiry_minutes')));
+ Passport::personalAccessTokensExpireIn(now()->addMonths(6));
+
+ Passport::useClientModel(\App\Auth\PassportClient::class);
+ Passport::tokenModel()::observe(\App\Observers\Passport\TokenObserver::class);
+ }
+
/**
* Make the authorization service instance.
*
diff --git a/src/app/Resource.php b/src/app/Resource.php
--- a/src/app/Resource.php
+++ b/src/app/Resource.php
@@ -45,6 +45,13 @@
// resource has been created in IMAP
public const STATUS_IMAP_READY = 1 << 8;
+ /** @var int The allowed states for this object used in StatusPropertyTrait */
+ private int $allowed_states = self::STATUS_NEW |
+ self::STATUS_ACTIVE |
+ self::STATUS_DELETED |
+ self::STATUS_LDAP_READY |
+ self::STATUS_IMAP_READY;
+
// A template for the email attribute on a resource creation
public const EMAIL_TEMPLATE = 'resource-{id}@{domainName}';
diff --git a/src/app/SharedFolder.php b/src/app/SharedFolder.php
--- a/src/app/SharedFolder.php
+++ b/src/app/SharedFolder.php
@@ -48,6 +48,13 @@
// folder has been created in IMAP
public const STATUS_IMAP_READY = 1 << 8;
+ /** @var int The allowed states for this object used in StatusPropertyTrait */
+ private int $allowed_states = self::STATUS_NEW |
+ self::STATUS_ACTIVE |
+ self::STATUS_DELETED |
+ self::STATUS_LDAP_READY |
+ self::STATUS_IMAP_READY;
+
/** @const array Supported folder type labels */
public const SUPPORTED_TYPES = ['mail', 'event', 'contact', 'task', 'note', 'file'];
diff --git a/src/app/Traits/StatusPropertyTrait.php b/src/app/Traits/StatusPropertyTrait.php
--- a/src/app/Traits/StatusPropertyTrait.php
+++ b/src/app/Traits/StatusPropertyTrait.php
@@ -101,34 +101,10 @@
*/
public function setStatusAttribute($status)
{
- $new_status = 0;
-
- $allowed_states = [
- 'STATUS_NEW',
- 'STATUS_ACTIVE',
- 'STATUS_SUSPENDED',
- 'STATUS_DELETED',
- 'STATUS_LDAP_READY',
- 'STATUS_IMAP_READY',
- ];
-
- foreach ($allowed_states as $const) {
- if (!defined("static::$const")) {
- continue;
- }
-
- $value = constant("static::$const");
-
- if ($status & $value) {
- $new_status |= $value;
- $status ^= $value;
- }
- }
-
- if ($status > 0) {
+ if ($status & ~$this->allowed_states) {
throw new \Exception("Invalid status: {$status}");
}
- $this->attributes['status'] = $new_status;
+ $this->attributes['status'] = $status;
}
}
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -61,6 +61,16 @@
// a restricted user
public const STATUS_RESTRICTED = 1 << 7;
+ /** @var int The allowed states for this object used in StatusPropertyTrait */
+ private int $allowed_states = self::STATUS_NEW |
+ self::STATUS_ACTIVE |
+ self::STATUS_SUSPENDED |
+ self::STATUS_DELETED |
+ self::STATUS_LDAP_READY |
+ self::STATUS_IMAP_READY |
+ self::STATUS_DEGRADED |
+ self::STATUS_RESTRICTED;
+
/** @var array<int, string> The attributes that are mass assignable */
protected $fillable = [
'id',
@@ -547,7 +557,7 @@
/**
* Un-restrict this user.
*
- * @param bool $deep Unrestrinct also all users in the account
+ * @param bool $deep Unrestrict also all users in the account
*
* @return void
*/
@@ -632,40 +642,6 @@
$this->setPasswordAttribute($password);
}
- /**
- * User status mutator
- *
- * @throws \Exception
- */
- public function setStatusAttribute($status)
- {
- $new_status = 0;
-
- $allowed_values = [
- self::STATUS_NEW,
- self::STATUS_ACTIVE,
- self::STATUS_SUSPENDED,
- self::STATUS_DELETED,
- self::STATUS_LDAP_READY,
- self::STATUS_IMAP_READY,
- self::STATUS_DEGRADED,
- self::STATUS_RESTRICTED,
- ];
-
- foreach ($allowed_values as $value) {
- if ($status & $value) {
- $new_status |= $value;
- $status ^= $value;
- }
- }
-
- if ($status > 0) {
- throw new \Exception("Invalid user status: {$status}");
- }
-
- $this->attributes['status'] = $new_status;
- }
-
/**
* Validate the user credentials
*
diff --git a/src/config/imap.php b/src/config/imap.php
--- a/src/config/imap.php
+++ b/src/config/imap.php
@@ -9,4 +9,52 @@
'host' => env('IMAP_HOST', '172.18.0.5'),
'imap_port' => env('IMAP_PORT', 12143),
'guam_port' => env('IMAP_GUAM_PORT', 9143),
+ 'default_folders' => [
+ 'Drafts' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'mail.drafts',
+ ],
+ ],
+ 'Sent' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'mail.sentitems',
+ ],
+ ],
+ 'Trash' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'mail.wastebasket',
+ ],
+ ],
+ 'Spam' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'mail.junkemail',
+ ],
+ ],
+
+ 'Calendar' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'event.default'
+ ],
+ ],
+ 'Contacts' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'contact.default',
+ ],
+ ],
+ 'Tasks' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'task.default',
+ ],
+ ],
+ 'Notes' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'note.default',
+ ],
+ ],
+ 'Files' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'file.default',
+ ],
+ ],
+ ]
];
diff --git a/src/database/migrations/2023_02_17_100000_vat_rates_table.php b/src/database/migrations/2023_02_17_100000_vat_rates_table.php
--- a/src/database/migrations/2023_02_17_100000_vat_rates_table.php
+++ b/src/database/migrations/2023_02_17_100000_vat_rates_table.php
@@ -61,7 +61,7 @@
->from('user_settings')
->where('key', 'country')
->where('value', $country);
- });
+ });
})
->update(['vat_rate_id' => $vatRate->id]);
}
diff --git a/src/tests/Browser/Pages/PaymentMollie.php b/src/tests/Browser/Pages/PaymentMollie.php
--- a/src/tests/Browser/Pages/PaymentMollie.php
+++ b/src/tests/Browser/Pages/PaymentMollie.php
@@ -25,7 +25,7 @@
*/
public function assert($browser)
{
- $browser->waitFor('form#body table, form#body iframe');
+ $browser->waitFor('form#body table, form#body iframe', 10);
}
/**
@@ -64,7 +64,7 @@
$browser->type('#expiryDate', '12/' . (date('y') + 1));
})
->withinFrame('#cvv iframe', function ($browser) {
- $browser->type('#verificationCode', '123');
+ $browser->click('#verificationCode')->type('#verificationCode', '123');
})
->click('#submit-button');
}
diff --git a/src/tests/Browser/Pages/PaymentStripe.php b/src/tests/Browser/Pages/PaymentStripe.php
--- a/src/tests/Browser/Pages/PaymentStripe.php
+++ b/src/tests/Browser/Pages/PaymentStripe.php
@@ -40,7 +40,7 @@
'@title' => '.App-Overview .ProductSummary',
'@amount' => '#ProductSummary-totalAmount',
'@description' => '#ProductSummary-Description',
- '@email-input' => '.App-Payment #email',
+ '@email' => '.App-Payment .ReadOnlyFormField-email .ReadOnlyFormField-content',
'@cardnumber-input' => '.App-Payment #cardNumber',
'@cardexpiry-input' => '.App-Payment #cardExpiry',
'@cardcvc-input' => '.App-Payment #cardCvc',
diff --git a/src/tests/Browser/PaymentMollieTest.php b/src/tests/Browser/PaymentMollieTest.php
--- a/src/tests/Browser/PaymentMollieTest.php
+++ b/src/tests/Browser/PaymentMollieTest.php
@@ -108,6 +108,7 @@
->on(new WalletPage())
->assertMissing('@body #mandate-form .alert')
->click('@main #mandate-form button')
+/*
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
->waitFor('#payment-method-selection .link-creditcard svg')
@@ -115,8 +116,10 @@
->assertMissing('#payment-method-selection .link-banktransfer')
->click('#payment-method-selection .link-creditcard');
})
+*/
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
+ ->waitFor('@body #mandate_amount')
->assertSeeIn('@body label[for="mandate_amount"]', 'Fill up by')
->assertValue('@body #mandate_amount', Payment::MIN_AMOUNT / 100)
->assertSeeIn('@body label[for="mandate_balance"]', 'when account balance is below') // phpcs:ignore
@@ -229,13 +232,16 @@
$browser->on(new WalletPage())
->assertMissing('@body #mandate-form .alert')
->click('@main #mandate-form button')
+/*
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
->waitFor('#payment-method-selection .link-creditcard')
->click('#payment-method-selection .link-creditcard');
})
+*/
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
+ ->waitFor('@body #mandate_amount')
->assertSeeIn('@button-cancel', 'Cancel')
->assertSeeIn('@button-action', 'Continue')
// Submit valid data
@@ -259,13 +265,16 @@
// Create a new mandate
->click('@main #mandate-form button')
+/*
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
->waitFor('#payment-method-selection .link-creditcard')
->click('#payment-method-selection .link-creditcard');
})
+*/
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
+ ->waitFor('@body #mandate_amount')
->assertSeeIn('@button-cancel', 'Cancel')
->assertSeeIn('@button-action', 'Continue')
// Submit valid data
diff --git a/src/tests/Browser/PaymentStripeTest.php b/src/tests/Browser/PaymentStripeTest.php
--- a/src/tests/Browser/PaymentStripeTest.php
+++ b/src/tests/Browser/PaymentStripeTest.php
@@ -80,7 +80,7 @@
->on(new PaymentStripe())
->assertSeeIn('@title', $user->tenant->title . ' Payment')
->assertSeeIn('@amount', 'CHF 12.34')
- ->assertValue('@email-input', $user->email)
+ ->assertSeeIn('@email', $user->email)
->submitValidCreditCard();
// Now it should redirect back to wallet page and in background
@@ -115,6 +115,7 @@
->on(new WalletPage())
->assertMissing('@body #mandate-form .alert')
->click('@main #mandate-form button')
+/*
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
->waitFor('#payment-method-selection .link-creditcard')
@@ -122,8 +123,10 @@
->assertMissing('#payment-method-selection .link-banktransfer')
->click('#payment-method-selection .link-creditcard');
})
+*/
->with(new Dialog('@payment-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Set up auto-payment')
+ ->waitFor('@body #mandate_amount')
->assertSeeIn('@body label[for="mandate_amount"]', 'Fill up by')
->assertValue('@body #mandate_amount', Payment::MIN_AMOUNT / 100)
->assertSeeIn('@body label[for="mandate_balance"]', 'when account balance is below') // phpcs:ignore
@@ -157,7 +160,7 @@
->on(new PaymentStripe())
->assertMissing('@title')
->assertMissing('@amount')
- ->assertValue('@email-input', $user->email)
+ ->assertSeeIn('@email', $user->email)
->submitValidCreditCard()
->waitForLocation('/wallet', 30) // need more time than default 5 sec.
->visit('/wallet?paymentProvider=stripe')
diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php
--- a/src/tests/Browser/SignupTest.php
+++ b/src/tests/Browser/SignupTest.php
@@ -12,7 +12,9 @@
use Tests\Browser\Components\Menu;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Dashboard;
+use Tests\Browser\Pages\Home;
use Tests\Browser\Pages\Signup;
+use Tests\Browser\Pages\Wallet;
use Tests\TestCaseDusk;
use Illuminate\Foundation\Testing\DatabaseMigrations;
@@ -29,7 +31,7 @@
$this->deleteTestUser('admin@user-domain-signup.com');
$this->deleteTestDomain('user-domain-signup.com');
- Plan::where('mode', 'token')->update(['mode' => 'email']);
+ Plan::whereIn('mode', ['token', 'mandate'])->update(['mode' => 'email']);
}
/**
@@ -42,7 +44,7 @@
$this->deleteTestDomain('user-domain-signup.com');
SignupInvitation::truncate();
- Plan::where('mode', 'token')->update(['mode' => 'email']);
+ Plan::whereIn('mode', ['token', 'mandate'])->update(['mode' => 'email']);
@unlink(storage_path('signup-tokens.txt'));
@@ -518,6 +520,67 @@
});
}
+ /**
+ * Test signup with a mandate plan, also the wallet lock
+ */
+ public function testSignupMandate(): void
+ {
+ // Test the individual plan
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $plan->mode = 'mandate';
+ $plan->save();
+
+ $this->browse(function (Browser $browser) {
+ $browser->visit(new Signup())
+ ->waitFor('@step0 .plan-individual button')
+ ->click('@step0 .plan-individual button')
+ // Test Back button
+ ->whenAvailable('@step3', function ($browser) {
+ $browser->click('button[type=button]');
+ })
+ ->whenAvailable('@step0', function ($browser) {
+ $browser->click('.plan-individual button');
+ })
+ // Test submit
+ ->whenAvailable('@step3', function ($browser) {
+ $domains = Domain::getPublicDomains();
+ $domains_count = count($domains);
+
+ $browser->assertMissing('.card-title')
+ ->assertElementsCount('select#signup_domain option', $domains_count, false)
+ ->assertText('select#signup_domain option:nth-child(1)', $domains[0])
+ ->assertValue('select#signup_domain option:nth-child(1)', $domains[0])
+ ->type('#signup_login', 'signuptestdusk')
+ ->type('#signup_password', '12345678')
+ ->type('#signup_password_confirmation', '12345678')
+ ->click('[type=submit]');
+ })
+ ->waitUntilMissing('@step3')
+ ->on(new Wallet())
+ ->assertSeeIn('#lock-alert', "The account is locked")
+ ->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
+ });
+
+ $user = User::where('email', 'signuptestdusk@' . \config('app.domain'))->first();
+ $this->assertSame($plan->id, $user->getSetting('plan_id'));
+
+ // Login again and see that the account is still locked
+ $this->browse(function (Browser $browser) use ($user) {
+ $browser->on(new Home())
+ ->submitLogon($user->email, '12345678', false)
+ ->waitForLocation('/wallet')
+ ->on(new Wallet())
+ ->assertSeeIn('#lock-alert', "The account is locked")
+ ->within(new Menu(), function ($browser) {
+ $browser->clickMenuItem('logout');
+ });
+
+ // TODO: Test automatic UI unlock after creating a valid auto-payment mandate
+ });
+ }
+
/**
* Test signup with a token plan
*/
diff --git a/src/tests/Feature/Backends/IMAPTest.php b/src/tests/Feature/Backends/IMAPTest.php
--- a/src/tests/Feature/Backends/IMAPTest.php
+++ b/src/tests/Feature/Backends/IMAPTest.php
@@ -107,6 +107,7 @@
$result = IMAP::createUser($user);
$this->assertTrue($result);
$this->assertTrue(IMAP::verifyAccount($user->email));
+ $this->assertTrue(IMAP::verifyDefaultFolders($user->email));
$imap = $this->getImap();
$quota = $imap->getQuota('user/' . $user->email);
@@ -128,6 +129,7 @@
$result = IMAP::verifyAccount($user->email);
$this->assertFalse($result);
+ $this->assertFalse(IMAP::verifyDefaultFolders($user->email));
}
/**
@@ -150,7 +152,9 @@
$imap = $this->getImap();
$expectedAcl = ['john@kolab.org' => str_split('lrswipkxtecdn')];
- $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+ $acl = $imap->getACL(IMAP::toUTF7($imapFolder));
+ $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org']));
+ $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']);
// Update the resource (rename)
$resource->name = 'Resource1 ©' . time();
@@ -160,7 +164,9 @@
$this->assertTrue(IMAP::updateResource($resource, ['folder' => $imapFolder]));
$this->assertTrue($imapFolder != $newImapFolder);
$this->assertTrue(IMAP::verifySharedFolder($newImapFolder));
- $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder)));
+ $acl = $imap->getACL(IMAP::toUTF7($newImapFolder));
+ $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org']));
+ $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']);
// Update the resource (acl change)
$resource->setSetting('invitation_policy', 'accept');
@@ -196,7 +202,11 @@
'jack@kolab.org' => str_split('lrs')
];
- $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+ $acl = $imap->getACL(IMAP::toUTF7($imapFolder));
+ $this->assertTrue(is_array($acl) && isset($acl['john@kolab.org']));
+ $this->assertSame($expectedAcl['john@kolab.org'], $acl['john@kolab.org']);
+ $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org']));
+ $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']);
// Update shared folder (acl)
$folder->setSetting('acl', json_encode(['jack@kolab.org, read-only']));
@@ -205,7 +215,10 @@
$expectedAcl = ['jack@kolab.org' => str_split('lrs')];
- $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($imapFolder)));
+ $acl = $imap->getACL(IMAP::toUTF7($imapFolder));
+ $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org']));
+ $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']);
+ $this->assertTrue(!isset($acl['john@kolab.org']));
// Update the shared folder (rename)
$folder->name = 'SharedFolder1 ©' . time();
@@ -215,7 +228,10 @@
$this->assertTrue(IMAP::updateSharedFolder($folder, ['folder' => $imapFolder]));
$this->assertTrue($imapFolder != $newImapFolder);
$this->assertTrue(IMAP::verifySharedFolder($newImapFolder));
- $this->assertSame($expectedAcl, $imap->getACL(IMAP::toUTF7($newImapFolder)));
+
+ $acl = $imap->getACL(IMAP::toUTF7($newImapFolder));
+ $this->assertTrue(is_array($acl) && isset($acl['jack@kolab.org']));
+ $this->assertSame($expectedAcl['jack@kolab.org'], $acl['jack@kolab.org']);
// Delete the shared folder
$this->assertTrue(IMAP::deleteSharedFolder($folder));
diff --git a/src/tests/Feature/Controller/PaymentsMollieTest.php b/src/tests/Feature/Controller/PaymentsMollieTest.php
--- a/src/tests/Feature/Controller/PaymentsMollieTest.php
+++ b/src/tests/Feature/Controller/PaymentsMollieTest.php
@@ -4,6 +4,7 @@
use App\Http\Controllers\API\V4\PaymentsController;
use App\Payment;
+use App\Plan;
use App\Providers\PaymentProvider;
use App\Transaction;
use App\Wallet;
@@ -47,6 +48,7 @@
Transaction::WALLET_CHARGEBACK,
];
Transaction::where('object_id', $wallet->id)->whereIn('type', $types)->delete();
+ Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email', 'months' => 1]);
}
/**
@@ -68,6 +70,7 @@
Transaction::WALLET_CHARGEBACK,
];
Transaction::where('object_id', $wallet->id)->whereIn('type', $types)->delete();
+ Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email', 'months' => 1]);
Utils::setTestExchangeRates([]);
parent::tearDown();
@@ -337,6 +340,44 @@
$this->assertNull($wallet->fresh()->getSetting('mollie_mandate_id'));
}
+ /**
+ * Test fetching an outo-payment mandate parameters
+ *
+ * @group mollie
+ */
+ public function testMandateParams(): void
+ {
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $user = $this->getTestUser('payment-test@' . \config('app.domain'));
+ $wallet = $user->wallets()->first();
+
+ $response = $this->actingAs($user)->get("api/v4/payments/mandate");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame((int) ceil(Payment::MIN_AMOUNT / 100), $json['amount']);
+ $this->assertSame($json['amount'], $json['minAmount']);
+ $this->assertSame(0, $json['balance']);
+ $this->assertFalse($json['isValid']);
+ $this->assertFalse($json['isDisabled']);
+
+ $plan->months = 12;
+ $plan->save();
+ $user->setSetting('plan_id', $plan->id);
+
+ $response = $this->actingAs($user)->get("api/v4/payments/mandate");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame((int) ceil(Payment::MIN_AMOUNT / 100), $json['amount']);
+ $this->assertSame((int) ceil(($plan->cost() * $plan->months) / 100), $json['minAmount']);
+
+ // TODO: Test more cases
+ // TODO: Test user unrestricting if mandate is valid
+ }
+
/**
* Test creating a payment and receiving a status via webhook
*
diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php
--- a/src/tests/Feature/Controller/SignupTest.php
+++ b/src/tests/Feature/Controller/SignupTest.php
@@ -84,11 +84,29 @@
return $this->domain;
}
+ /**
+ * Test fetching public domains for signup
+ */
+ public function testSignupDomains(): void
+ {
+ $response = $this->get('/api/auth/signup/domains');
+ $json = $response->json();
+
+ $response->assertStatus(200);
+
+ $this->assertCount(2, $json);
+ $this->assertSame('success', $json['status']);
+ $this->assertSame(Domain::getPublicDomains(), $json['domains']);
+ }
+
/**
* Test fetching plans for signup
*/
public function testSignupPlans(): void
{
+ $individual = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $group = Plan::withEnvTenantContext()->where('title', 'group')->first();
+
$response = $this->get('/api/auth/signup/plans');
$json = $response->json();
@@ -96,10 +114,16 @@
$this->assertSame('success', $json['status']);
$this->assertCount(2, $json['plans']);
- $this->assertArrayHasKey('title', $json['plans'][0]);
- $this->assertArrayHasKey('name', $json['plans'][0]);
- $this->assertArrayHasKey('description', $json['plans'][0]);
+ $this->assertSame($individual->title, $json['plans'][0]['title']);
+ $this->assertSame($individual->name, $json['plans'][0]['name']);
+ $this->assertSame($individual->description, $json['plans'][0]['description']);
+ $this->assertFalse($json['plans'][0]['isDomain']);
$this->assertArrayHasKey('button', $json['plans'][0]);
+ $this->assertSame($group->title, $json['plans'][1]['title']);
+ $this->assertSame($group->name, $json['plans'][1]['name']);
+ $this->assertSame($group->description, $json['plans'][1]['description']);
+ $this->assertTrue($json['plans'][1]['isDomain']);
+ $this->assertArrayHasKey('button', $json['plans'][1]);
}
/**
diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php
--- a/src/tests/Feature/Controller/UsersTest.php
+++ b/src/tests/Feature/Controller/UsersTest.php
@@ -6,6 +6,7 @@
use App\Domain;
use App\Http\Controllers\API\V4\UsersController;
use App\Package;
+use App\Plan;
use App\Sku;
use App\Tenant;
use App\User;
@@ -47,7 +48,10 @@
$wallet->save();
$user->settings()->whereIn('key', ['greylist_enabled', 'guam_enabled'])->delete();
$user->status |= User::STATUS_IMAP_READY | User::STATUS_LDAP_READY;
+ $user->status &= ~User::STATUS_RESTRICTED;
$user->save();
+ Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email']);
+ $user->setSettings(['plan_id' => null]);
}
/**
@@ -78,7 +82,10 @@
$wallet->save();
$user->settings()->whereIn('key', ['greylist_enabled', 'guam_enabled'])->delete();
$user->status |= User::STATUS_IMAP_READY | User::STATUS_LDAP_READY;
+ $user->status &= ~User::STATUS_RESTRICTED;
$user->save();
+ Plan::withEnvTenantContext()->where('title', 'individual')->update(['mode' => 'email']);
+ $user->setSettings(['plan_id' => null]);
parent::tearDown();
}
@@ -1372,14 +1379,15 @@
public function testUserResponse(): void
{
$provider = \config('services.payment_provider') ?: 'mollie';
- $user = $this->getTestUser('john@kolab.org');
- $wallet = $user->wallets()->first();
+ $john = $this->getTestUser('john@kolab.org');
+ $wallet = $john->wallets()->first();
$wallet->setSettings(['mollie_id' => null, 'stripe_id' => null]);
- $result = $this->invokeMethod(new UsersController(), 'userResponse', [$user]);
+ $wallet->owner->setSettings(['plan_id' => null]);
+ $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]);
- $this->assertEquals($user->id, $result['id']);
- $this->assertEquals($user->email, $result['email']);
- $this->assertEquals($user->status, $result['status']);
+ $this->assertEquals($john->id, $result['id']);
+ $this->assertEquals($john->email, $result['email']);
+ $this->assertEquals($john->status, $result['status']);
$this->assertTrue(is_array($result['statusInfo']));
$this->assertTrue(is_array($result['settings']));
@@ -1392,13 +1400,20 @@
$this->assertCount(1, $result['wallets']);
$this->assertSame($wallet->id, $result['wallet']['id']);
$this->assertArrayNotHasKey('discount', $result['wallet']);
+ $this->assertFalse($result['isLocked']);
$this->assertTrue($result['statusInfo']['enableDomains']);
$this->assertTrue($result['statusInfo']['enableWallets']);
+ $this->assertTrue($result['statusInfo']['enableWalletMandates']);
+ $this->assertTrue($result['statusInfo']['enableWalletPayments']);
$this->assertTrue($result['statusInfo']['enableUsers']);
$this->assertTrue($result['statusInfo']['enableSettings']);
// Ned is John's wallet controller
+ $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
+ $plan->mode = 'mandate';
+ $plan->save();
+ $wallet->owner->setSettings(['plan_id' => $plan->id]);
$ned = $this->getTestUser('ned@kolab.org');
$ned_wallet = $ned->wallets()->first();
$result = $this->invokeMethod(new UsersController(), 'userResponse', [$ned]);
@@ -1414,9 +1429,12 @@
$this->assertSame($ned_wallet->id, $result['wallets'][0]['id']);
$this->assertSame($provider, $result['wallet']['provider']);
$this->assertSame($provider, $result['wallets'][0]['provider']);
+ $this->assertFalse($result['isLocked']);
$this->assertTrue($result['statusInfo']['enableDomains']);
$this->assertTrue($result['statusInfo']['enableWallets']);
+ $this->assertTrue($result['statusInfo']['enableWalletMandates']);
+ $this->assertFalse($result['statusInfo']['enableWalletPayments']);
$this->assertTrue($result['statusInfo']['enableUsers']);
$this->assertTrue($result['statusInfo']['enableSettings']);
@@ -1426,11 +1444,11 @@
$wallet->save();
$mod_provider = $provider == 'mollie' ? 'stripe' : 'mollie';
$wallet->setSetting($mod_provider . '_id', 123);
- $user->refresh();
+ $john->refresh();
- $result = $this->invokeMethod(new UsersController(), 'userResponse', [$user]);
+ $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]);
- $this->assertEquals($user->id, $result['id']);
+ $this->assertEquals($john->id, $result['id']);
$this->assertSame($discount->id, $result['wallet']['discount_id']);
$this->assertSame($discount->discount, $result['wallet']['discount']);
$this->assertSame($discount->description, $result['wallet']['discount_description']);
@@ -1439,6 +1457,7 @@
$this->assertSame($discount->discount, $result['wallets'][0]['discount']);
$this->assertSame($discount->description, $result['wallets'][0]['discount_description']);
$this->assertSame($mod_provider, $result['wallets'][0]['provider']);
+ $this->assertFalse($result['isLocked']);
// Jack is not a John's wallet controller
$jack = $this->getTestUser('jack@kolab.org');
@@ -1446,8 +1465,17 @@
$this->assertFalse($result['statusInfo']['enableDomains']);
$this->assertFalse($result['statusInfo']['enableWallets']);
+ $this->assertFalse($result['statusInfo']['enableWalletMandates']);
+ $this->assertFalse($result['statusInfo']['enableWalletPayments']);
$this->assertFalse($result['statusInfo']['enableUsers']);
$this->assertFalse($result['statusInfo']['enableSettings']);
+ $this->assertFalse($result['isLocked']);
+
+ // Test locked user
+ $john->restrict();
+ $result = $this->invokeMethod(new UsersController(), 'userResponse', [$john]);
+
+ $this->assertTrue($result['isLocked']);
}
/**
diff --git a/src/tests/Feature/PaymentTest.php b/src/tests/Feature/PaymentTest.php
--- a/src/tests/Feature/PaymentTest.php
+++ b/src/tests/Feature/PaymentTest.php
@@ -82,7 +82,8 @@
$wallet->balance = -5000;
$wallet->save();
- // Credit the 2nd payment
+ // Credit the 2nd payment (restricted user)
+ $user->restrict();
$payment2->credit('Test2');
$wallet->refresh();
$transaction = $wallet->transactions()->first();
@@ -91,6 +92,7 @@
$this->assertSame('1', $wallet->getSetting('mandate_disabled'));
$this->assertSame($payment2->credit_amount, $transaction->amount);
$this->assertSame("Auto-payment transaction {$payment2->id} using Test2", $transaction->description);
+ $this->assertFalse($user->refresh()->isRestricted());
}
/**
diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php
--- a/src/tests/Feature/UserTest.php
+++ b/src/tests/Feature/UserTest.php
@@ -1183,7 +1183,8 @@
\App\Jobs\User\UpdateJob::class,
function ($job) use ($user) {
return TestCase::getObjectProperty($job, 'userId') == $user->id;
- });
+ }
+ );
$userB->restrict();
$this->assertTrue($userB->fresh()->isRestricted());
@@ -1200,7 +1201,8 @@
\App\Jobs\User\UpdateJob::class,
function ($job) use ($user) {
return TestCase::getObjectProperty($job, 'userId') == $user->id;
- });
+ }
+ );
Queue::fake(); // reset queue state
@@ -1213,7 +1215,8 @@
\App\Jobs\User\UpdateJob::class,
function ($job) use ($userB) {
return TestCase::getObjectProperty($job, 'userId') == $userB->id;
- });
+ }
+ );
}
/**
diff --git a/src/tests/Infrastructure/ActivesyncTest.php b/src/tests/Infrastructure/ActivesyncTest.php
--- a/src/tests/Infrastructure/ActivesyncTest.php
+++ b/src/tests/Infrastructure/ActivesyncTest.php
@@ -38,15 +38,38 @@
{
parent::setUp();
- if (!self::$user) {
- self::$user = $this->getTestUser('activesynctest@kolab.org', ['password' => 'simple123'], true);
- }
if (!self::$deviceId) {
// By always creating a new device we force syncroton to initialize.
// Otherwise we work against uninitialized metadata (subscription states),
// because the account has been removed, but syncroton doesn't reinitalize the metadata for known devices.
self::$deviceId = (string) Str::uuid();
}
+
+ $deviceId = self::$deviceId;
+ \config(['imap.default_folders' => [
+ 'Drafts' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'mail.drafts',
+ '/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
+ ],
+ ],
+ 'Calendar' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'event.default',
+ '/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
+ ],
+ ],
+ 'Contacts' => [
+ 'metadata' => [
+ '/private/vendor/kolab/folder-type' => 'contact.default',
+ '/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
+ ],
+ ],
+ ]]);
+
+ if (!self::$user) {
+ self::$user = $this->getTestUser('activesynctest@kolab.org', ['password' => 'simple123'], true);
+ }
if (!self::$client) {
self::$client = new \GuzzleHttp\Client([
'http_errors' => false, // No exceptions
@@ -103,12 +126,9 @@
// The hash is based on the name, so it's always the same
$inboxId = '38b950ebd62cd9a66929c89615d0fc04';
$this->assertStringContainsString($inboxId, $xml);
- //TODO for this to work we need to create the default folders in IMAP::createUser
- // $this->assertStringContainsString('Drafts', $result);
- // $this->assertStringContainsString('Sent', $result);
- // $this->assertStringContainsString('Trash', $result);
- // $this->assertStringContainsString('Calendar', $result);
- // $this->assertStringContainsString('Contacts', $result);
+ $this->assertStringContainsString('Drafts', $xml);
+ $this->assertStringContainsString('Calendar', $xml);
+ $this->assertStringContainsString('Contacts', $xml);
// Find the inbox for the next step
// $collectionIds = $dom->getElementsByTagName('ServerId');
diff --git a/src/tests/Unit/Backends/DAV/FolderTest.php b/src/tests/Unit/Backends/DAV/FolderTest.php
--- a/src/tests/Unit/Backends/DAV/FolderTest.php
+++ b/src/tests/Unit/Backends/DAV/FolderTest.php
@@ -14,7 +14,11 @@
{
$xml = <<<XML
<?xml version="1.0" encoding="utf-8"?>
-<d:multistatus xmlns:d="DAV:" xmlns:cs="http://calendarserver.org/ns/" xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:a="http://apple.com/ns/ical/" xmlns:k="Kolab:">
+ <d:multistatus xmlns:d="DAV:"
+ xmlns:cs="http://calendarserver.org/ns/"
+ xmlns:c="urn:ietf:params:xml:ns:caldav"
+ xmlns:a="http://apple.com/ns/ical/"
+ xmlns:k="Kolab:">
<d:response>
<d:href>/dav/calendars/user/alec@aphy.io/Default/</d:href>
<d:propstat>
diff --git a/utils/kolabendpointtester.py b/utils/kolabendpointtester.py
--- a/utils/kolabendpointtester.py
+++ b/utils/kolabendpointtester.py
@@ -352,10 +352,10 @@
print(' autodiscover CNAME', rdata.target)
except dns.resolver.NXDOMAIN:
success = False
- print(" ERROR on autodiscover. CNAME entry")
+ print(f" ERROR on autodiscover.{host} CNAME entry")
except dns.resolver.NoAnswer:
success = False
- print(" ERROR on autodiscover. CNAME entry")
+ print(f" ERROR on autodiscover.{host} CNAME entry")
srv_records = [
f"_autodiscover._tcp.{host}",
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Apr 6, 4:39 AM (8 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18835640
Default Alt Text
D4130.1775450386.diff (132 KB)
Attached To
Mode
D4130: Signup 'mandate' mode, and other improvements
Attached
Detach File
Event Timeline