Compare commits

..

249 Commits

Author SHA1 Message Date
FreddleSpl0it
2c47145dee Merge pull request #6419 from mailcow/staging
Update 2025-03a
2025-03-27 09:19:29 +01:00
FreddleSpl0it
9b41b24522 Merge pull request #6402 from marvinruder/fix/long-dropdown-label
fix(ui): Swap translations for oversized dropdown
2025-03-27 08:07:51 +01:00
FreddleSpl0it
1c9d80f554 Merge pull request #6406 from mailcow/fix/6392
[Web] Fix SOGo access after Passwordless auth
2025-03-27 07:42:07 +01:00
FreddleSpl0it
7172cad257 Merge pull request #6407 from mailcow/fix/6396
[Web] Fix oauth2 redirect after user login
2025-03-27 07:41:08 +01:00
FreddleSpl0it
b550c6f88e Merge pull request #6408 from mailcow/fix/6373
[Swagger] Fix type property for /api/v1/add/bcc endpoint
2025-03-27 07:40:19 +01:00
FreddleSpl0it
5baf9eb375 Merge pull request #6409 from mailcow/fix/6372
[Web] Check if mailbox is active before renaming
2025-03-27 07:40:03 +01:00
FreddleSpl0it
4eb89f67ed Merge pull request #6410 from mailcow/fix/6395
[Web] Use absolute paths for flag SVGs
2025-03-27 07:39:34 +01:00
FreddleSpl0it
efdc798238 Merge pull request #6411 from mailcow/fix/6340
[Nginx] Move conf.d include before SNI vhosts
2025-03-27 07:39:06 +01:00
FreddleSpl0it
65fb4c2aa8 [Nginx] Move conf.d include before SNI vhosts 2025-03-26 13:04:43 +01:00
FreddleSpl0it
a5ca3353da [Web] Use absolute paths for flag SVGs 2025-03-26 10:59:56 +01:00
FreddleSpl0it
95aa35e133 [Web] Check if mailbox is active before renaming 2025-03-26 10:10:22 +01:00
FreddleSpl0it
21b11ed999 [Swagger] Fix type property for /api/v1/add/bcc endpoint 2025-03-26 09:24:03 +01:00
FreddleSpl0it
348107dae8 [Web] Fix oauth2 redirect after user login 2025-03-26 09:13:05 +01:00
FreddleSpl0it
fcb1b29c89 [Web] Fix SOGo access after Passwordless auth 2025-03-26 08:32:34 +01:00
Marvin A. Ruder
05fc4f7aba fix(ui): Swap translations for oversized dropdown
* Fix other typos
* Fixes #6400

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
2025-03-25 21:24:22 +01:00
FreddleSpl0it
c3c68360dc Merge pull request #6391 from mailcow/staging
Update 2025-03
2025-03-25 08:10:50 +01:00
FreddleSpl0it
d584dd387e Merge pull request #6390 from mailcow/nightly
Nightly 2025-03 to staging
2025-03-25 07:36:20 +01:00
FreddleSpl0it
986b0afbfa ldap-sync: Fix template selection 2025-03-24 15:33:42 +01:00
FreddleSpl0it
59d139bc63 Merge branch 'nightly' into staging 2025-03-24 13:39:43 +01:00
FreddleSpl0it
cf2d3c1b4e Merge branch 'staging' into nightly 2025-03-24 11:38:59 +01:00
FreddleSpl0it
ba7437a8f3 Merge pull request #6380 from mailcow/feat/legacy-switch
Add Legacy Updates
2025-03-20 14:25:13 +01:00
FreddleSpl0it
684256b66e update.sh: Fix legacy typo 2025-03-20 14:23:28 +01:00
FreddleSpl0it
70ba361583 update.sh: Fix text in legacy update prompt 2025-03-20 14:15:15 +01:00
FreddleSpl0it
94d4817ecb [Web] Add default_template parameter to edit/identity-provider documentation 2025-03-20 13:38:27 +01:00
FreddleSpl0it
72ced70e33 [Web] Fix mailbox authsource selection 2025-03-20 13:08:42 +01:00
FreddleSpl0it
887b7114a8 Add default template for IdP attribute mapping 2025-03-19 14:35:32 +01:00
FreddleSpl0it
8910135f02 [Web] Add edit/identity-provider Api Documentation 2025-03-17 13:21:28 +01:00
FreddleSpl0it
e21696ff27 Add error message when mailbox creation fails 2025-03-14 09:36:40 +01:00
FreddleSpl0it
5a7275843a Add error message when mailbox creation fails 2025-03-14 09:30:33 +01:00
FreddleSpl0it
c93106f9d6 [Web] Fix redirect after renaming mailbox 2025-03-14 09:29:02 +01:00
FreddleSpl0it
43c1597051 [Web] Check if authsource is configured before adding or updating a mailbox 2025-03-14 09:19:39 +01:00
FreddleSpl0it
c3aa4f7418 [Web] Add authsource property to mailbox API Documentation 2025-03-14 09:17:51 +01:00
FreddleSpl0it
cb08132a74 [Web] Fix authentication when mailbox or domain is deactivated 2025-03-13 14:39:03 +01:00
FreddleSpl0it
2596b9d386 [Web] Improve auth logging and language strings 2025-03-12 11:42:14 +01:00
FreddleSpl0it
aac0a900ce [Web] Fix JSON parsing issue for api requests 2025-03-10 10:49:27 +01:00
FreddleSpl0it
5f15475b55 [Rspamd] Remove redis.conf from tracking 2025-03-07 15:18:42 +01:00
FreddleSpl0it
25d34b5acf [Web] Remove default ui help text 2025-03-07 14:52:08 +01:00
FreddleSpl0it
6b165887d8 Merge branch 'staging' into nightly 2025-03-07 13:21:57 +01:00
FreddleSpl0it
82eb3c64cd [Web] Use SQL password only when authsource is mailcow 2025-03-07 13:15:27 +01:00
FreddleSpl0it
bc21e7fe50 [Web] Separate FIDO2 logins 2025-03-07 13:12:48 +01:00
FreddleSpl0it
6f9c8deab7 [Web] Support old style app links 2025-03-07 09:56:20 +01:00
FreddleSpl0it
8761d8fc47 [Web] Fix app layout issue 2025-03-07 09:54:35 +01:00
FreddleSpl0it
a632980871 Merge pull request #6336 from mailcow/staging
Update 2025-02
2025-02-27 11:48:57 +01:00
FreddleSpl0it
2d1ef41d32 Merge pull request #6335 from mailcow/staging
Update 2025-02
2025-02-27 11:05:55 +01:00
FreddleSpl0it
8d0c03b2fc small adjustment for legacy version 2025-02-26 10:39:41 +01:00
FreddleSpl0it
b77ff2f51c Add switch to legacy version 2025-02-26 09:47:59 +01:00
FreddleSpl0it
fcebe98557 Merge branch 'staging' into nightly 2025-02-24 15:09:36 +01:00
FreddleSpl0it
54728bf780 [Dovecot] Fix create sogo-sso.conf 2025-02-11 14:40:38 +01:00
FreddleSpl0it
f64c6aa1d4 Merge pull request #6269 from mailcow/staging
Automatic PR to nightly from 2025-01-27T10:00:26Z
2025-02-07 15:10:10 +01:00
FreddleSpl0it
e2cf22ff9e Merge pull request #6268 from mailcow/feat/nightly-separated-login
[Web] Separate Login pages
2025-02-07 15:09:39 +01:00
FreddleSpl0it
55dcae4a01 [Web] Fix Generic-OIDC connection test 2025-02-07 15:05:43 +01:00
FreddleSpl0it
f0016eeecd [Web] Add german translation for idp settings 2025-02-07 14:19:20 +01:00
FreddleSpl0it
120366fec7 Merge pull request #6291 from mailcow/staging
Update 2025-01a
2025-02-04 13:55:30 +01:00
DerLinkman
244d4b8c4c compose: rollback clamd version until next major... accidentally pushed 2025-01-29 13:46:53 +01:00
DerLinkman
f92ddd86c5 clamd: update to 1.4.2 + build from source instead using alpine packages (#6273)
* clamd: update to 1.4.2 + build from source instead using alpine packages

* clamd: remove exposed ports from buildfile

* clamd: cleanup dockerfile
2025-01-29 09:49:04 +01:00
FreddleSpl0it
cb5cae3e44 Merge branch 'nightly' into feat/nightly-separated-login 2025-01-27 16:37:09 +01:00
FreddleSpl0it
aca01c8aa2 [Web] Separate Login pages 2025-01-27 15:59:50 +01:00
FreddleSpl0it
de6bd222fc [Web] increase db_version 2025-01-24 09:25:19 +01:00
FreddleSpl0it
36d4fcbf39 Merge pull request #6255 from mailcow/staging
Automatic PR to nightly from 2025-01-23T11:01:42Z
2025-01-23 15:21:39 +01:00
FreddleSpl0it
ba0349a911 Merge pull request #6256 from mailcow/staging
[Nginx] move conf.d include to end of nginx.conf
2025-01-23 14:55:38 +01:00
FreddleSpl0it
9d791d0c4f Merge branch 'staging' into nightly 2025-01-23 12:06:47 +01:00
FreddleSpl0it
8caf09cd80 Merge pull request #6253 from mailcow/staging
2025-01
2025-01-23 12:01:38 +01:00
FreddleSpl0it
1e77f8d8a1 Merge pull request #6250 from mailcow/staging
Automatic PR to nightly from 2025-01-22T19:10:32Z
2025-01-23 11:01:55 +01:00
FreddleSpl0it
5f45f8ae34 [Web] Fix mailbox datatable search 2025-01-23 09:18:45 +01:00
FreddleSpl0it
d430b595c1 Merge branch 'staging' into nightly 2025-01-23 08:11:45 +01:00
FreddleSpl0it
1e70a20188 [SOGo] Add mailcow Buttons to SOGo navbar 2025-01-15 16:15:25 +01:00
FreddleSpl0it
8048e0a53c [Web] Fix permission exception in IdP actions 2025-01-15 12:48:10 +01:00
FreddleSpl0it
69b03791a2 Add missing Redis authentication 2024-12-09 13:54:44 +01:00
FreddleSpl0it
c9dd102741 [Dovecot] use auth_cache 2024-12-06 12:55:44 +01:00
FreddleSpl0it
bbddfc3eab [Web] rearrange login buttons 2024-12-05 15:21:07 +01:00
FreddleSpl0it
a41bb55c83 Merge remote-tracking branch 'origin/staging' into nightly 2024-12-05 14:33:41 +01:00
FreddleSpl0it
b6174fae23 Merge pull request #6194 from mailcow/feat/nightly-enhancements
[Nightly] Enhancements
2024-12-05 13:18:39 +01:00
FreddleSpl0it
1d6513ffba [Web] fix idp login alerts and updates 2024-12-04 14:49:31 +01:00
FreddleSpl0it
896a9638d6 Fix mailcowauth 2024-12-02 14:16:43 +01:00
FreddleSpl0it
83e53eb524 [Web] fix incomplete session on broken logins 2024-12-02 11:55:17 +01:00
FreddleSpl0it
f36184df64 [Web] update mailbox on idp login 2024-12-02 10:35:45 +01:00
FreddleSpl0it
6fa1c9f63d [Web] protect /get/identity-provider 2024-12-02 10:24:15 +01:00
FreddleSpl0it
ccc8595665 [SOGo] redirect to /user if unauthenticated 2024-12-01 16:51:56 +01:00
FreddleSpl0it
45c13c687b [Web] update user based on template after login 2024-12-01 16:36:16 +01:00
FreddleSpl0it
d61a08c2a9 [Web] hide auth heading for external managed users 2024-11-30 14:39:05 +01:00
FreddleSpl0it
c8c4cfd939 [Web] add ignore ssl option for keycloak and generic-oidc provider 2024-11-30 14:37:07 +01:00
FreddleSpl0it
ec4b9b088c [Web] support multiple ldap hosts separated by comma 2024-11-29 18:59:07 +01:00
FreddleSpl0it
b2db8e6b31 [Dovecot] init identity provider before user login 2024-11-29 16:52:34 +01:00
FreddleSpl0it
05e4bd7602 [Web] use global vars for iam_provider and iam_settings 2024-11-29 15:50:35 +01:00
FreddleSpl0it
dc379267a9 Merge remote-tracking branch 'origin/staging' into nightly 2024-11-12 16:07:55 +01:00
Niklas Meyer
b90375b6e5 php: use correct php image + workaround of #6149 (#6159)
* compose: bump php-fpm container to correctly use patched c-ares

* [Web] check $containers_info contains required fields

---------

Co-authored-by: FreddleSpl0it <patschul@posteo.de>
2024-11-12 15:56:23 +01:00
FreddleSpl0it
9542698e95 Merge remote-tracking branch 'origin/staging' into nightly 2024-11-12 15:10:03 +01:00
Niklas Meyer
afe0ba74d2 compose: bump sogo version to include 5.11.2 (#6156) 2024-11-12 11:11:34 +01:00
milkmaker
dc5a28111d [Web] Updated lang.zh-cn.json (#6151)
[Web] Updated lang.zh-cn.json

Co-authored-by: Easton Man <me@eastonman.com>
2024-11-11 21:39:15 +01:00
Niklas Meyer
52f3f93aee update.sh: precaution ask for deletion of dns_blocklists.cf if old format (#6154) 2024-11-11 16:50:14 +01:00
FreddleSpl0it
f9304dcd9b [Web] check if $iam_provider is null on ldap_mbox_login 2024-10-09 12:34:39 +02:00
FreddleSpl0it
0b9b8c9060 [Web] Ensure correct SOGo SSO password is used after Dovecot restart 2024-09-06 10:05:00 +02:00
FreddleSpl0it
0d2046baeb Merge branch 'staging' into nightly 2024-09-05 14:53:37 +02:00
FreddleSpl0it
82fcddb177 [Web] Fix catch block in LDAP connection test 2024-09-02 10:12:51 +02:00
FreddleSpl0it
320bd31d37 [Web] fix LDAP "ignore ssl errors" option 2024-09-02 10:02:10 +02:00
FreddleSpl0it
b307e0a0d5 [PHP-FPM] Add missing space in log message 2024-09-02 09:57:33 +02:00
FreddleSpl0it
ef238e5332 [LDAP] skip sync user if username_field in LDAP is empty 2024-08-28 11:28:37 +02:00
FreddleSpl0it
dbf87e99fc [Web] Convert LDAP username_field and attribute_field to lowercase 2024-08-21 10:48:04 +02:00
milkmaker
aeeac63e1f Fix: Escape a ' character in update.sh (#6034) (#6035)
Co-authored-by: Hassan A Hashim <h3ssan@protonmail.com>
2024-08-20 14:22:51 +02:00
Niklas Meyer
ffcd242048 Merge pull request #6027 from mailcow/staging
Automatic PR to nightly from 2024-08-19T12:28:50Z
2024-08-20 13:41:54 +02:00
DerLinkman
e21157c10d Merge branch 'staging' into nightly 2024-08-19 11:42:12 +02:00
FreddleSpl0it
fa3c453d6e Use DN instead of DistinguishedName for LDAP login 2024-08-15 12:49:57 +02:00
FreddleSpl0it
962ac39e4a Merge remote-tracking branch 'origin/staging' into nightly 2024-08-15 12:45:52 +02:00
Niklas Meyer
ebc8e6b838 Merge pull request #6008 from mailcow/staging
Automatic PR to nightly from 2024-08-15T07:42:17Z
2024-08-15 09:53:17 +02:00
DerLinkman
1fc964d72e compose: bump dovecot image to newest nightly (20240814) 2024-08-14 10:12:10 +02:00
DerLinkman
5571d80ae6 Merge branch 'staging' into nightly 2024-08-14 10:10:34 +02:00
DerLinkman
3396e1b427 Merge branch 'staging' into nightly 2024-08-13 16:03:30 +02:00
FreddleSpl0it
58a5a4578c [Web] use cn as fallback ldap login 2024-08-13 12:14:05 +02:00
FreddleSpl0it
519d95cb8b [Web] extend ldap auth logging 2024-08-13 09:30:54 +02:00
FreddleSpl0it
092d3cd80b [Web] extend ldap auth logging 2024-08-12 10:14:53 +02:00
FreddleSpl0it
c034f4bd27 [Web] fix wrong log type on PDOException 2024-08-12 10:10:33 +02:00
FreddleSpl0it
73d60eb085 Merge pull request #5997 from mailcow/fix/nightly-tfa
[Web] fix incorrect user role assignment after TFA verification
2024-08-08 17:11:57 +02:00
FreddleSpl0it
b39b7c24a5 [Web] fix incorrect user role assignment after TFA verification 2024-08-08 17:04:56 +02:00
DerLinkman
d0ecb72e08 Merge branch 'staging' into nightly 2024-08-08 08:44:12 +02:00
DerLinkman
772d5c51fd Merge branch 'staging' into nightly 2024-08-07 14:21:23 +02:00
FreddleSpl0it
9b86ff764e Merge pull request #5975 from mailcow/staging
Automatic PR to nightly from 2024-08-01T03:13:55Z
2024-08-01 11:07:55 +02:00
FreddleSpl0it
57bc03b878 Merge remote-tracking branch 'origin/staging' into nightly 2024-07-31 10:35:44 +02:00
FreddleSpl0it
f7ae2a6162 [Nightly][SOGo] Update 5.10.0 2024-06-06 12:12:30 +02:00
FreddleSpl0it
3080a70287 [Nightly][SOGo] Update to 5.10.0 2024-06-03 09:20:54 +02:00
Patrick Schult
caee770e36 Merge pull request #5850 from mailcow/fix/nightly-autodiscover
[Web] Fix autodiscover fails with external IdP
2024-04-19 20:38:56 +02:00
FreddleSpl0it
95d6eeb37a [Web] revert include prerequisites in autodiscover - include autoload 2024-04-19 20:32:44 +02:00
FreddleSpl0it
eadf70d809 [Web] include prerequisites in autodiscover 2024-04-18 14:23:35 +02:00
Patrick Schult
6e9c3e2687 Merge pull request #5821 from mailcow/fix/web-nightly
Fix/web nightly
2024-04-04 09:33:22 +02:00
FreddleSpl0it
cf2fda66e2 [Web] escape html of alert messages 2024-04-04 09:31:20 +02:00
FreddleSpl0it
cd24057f1a [Web] use SEC_FETCH_DEST header to block api requests 2024-04-04 09:31:03 +02:00
FreddleSpl0it
c68a436a22 [Web] fix invalid rspamd map check 2024-04-04 09:30:30 +02:00
FreddleSpl0it
0807c122f6 [Web] set default LDAP options on get 2024-03-08 15:11:49 +01:00
FreddleSpl0it
e0bda6ca6a [Web] prevent multiple dual-logins 2024-03-08 14:05:37 +01:00
FreddleSpl0it
2ba64e93f9 [Web] allow SSL / TLS connections for LDAP 2024-03-08 13:50:20 +01:00
FreddleSpl0it
e1c3ad9fe8 [Web] return idp instance after init 2024-03-08 13:15:35 +01:00
FreddleSpl0it
ffbf1758e0 [Web] fix identity_provider ArgumentCountError 2024-02-26 13:40:34 +01:00
Patrick Schult
a3af2d8392 Merge pull request #5764 from mailcow/fix/nightly-issues
Fix nightly issues with new ldap provider
2024-02-26 13:33:44 +01:00
FreddleSpl0it
39a4b115ed [SOGo] fix plist_ldap.sh example 2024-02-26 13:14:08 +01:00
FreddleSpl0it
881c2d6e02 [SOGo] remove custom logout from toolbar 2024-02-26 13:13:50 +01:00
FreddleSpl0it
d237157c0b init identity_provider only after all conditions are met 2024-02-26 13:12:44 +01:00
FreddleSpl0it
6928eb632e [Dovecot] move sogo sso to mailcowauth.php 2024-02-26 13:10:08 +01:00
FreddleSpl0it
010d898786 [Web] apply LDAP filter 2024-02-23 10:01:56 +01:00
FreddleSpl0it
766c270b1f [SOGo] fix custom html elements and wrong redirection 2024-02-23 09:12:17 +01:00
FreddleSpl0it
916d0fd46a [Web] catch all exceptions on ldap connect 2024-02-23 08:12:06 +01:00
Patrick Schult
9561526f33 Merge pull request #5754 from mailcow/feat/idp-ldap2
[Nightly] add LDAP as direct IdP
2024-02-20 15:33:58 +01:00
FreddleSpl0it
45811bc2dc [Web] fix mailbox datatable search 2024-02-20 15:00:06 +01:00
FreddleSpl0it
132e37bfec [SOGo] use bash script for ldap plist template 2024-02-20 12:42:37 +01:00
FreddleSpl0it
b3e26e14ef [Ofelia] add ldap sync cronjob 2024-02-20 12:01:08 +01:00
FreddleSpl0it
a3bb889def [Web] fix set_tfa for ldap users 2024-02-20 11:41:02 +01:00
FreddleSpl0it
3a1dcb3aaf [Web] fix set_tfa for ldap users 2024-02-20 11:34:01 +01:00
FreddleSpl0it
d22cafacc8 [Web] fix ldap filter if empty 2024-02-20 11:21:25 +01:00
FreddleSpl0it
78e7266368 [Web] add LDAP query filter 2024-02-20 10:46:23 +01:00
FreddleSpl0it
a06c78362a [Web] add ldap idp 2024-02-20 10:31:14 +01:00
FreddleSpl0it
d479d18507 [Web] update directorytree/ldaprecord 2024-02-20 10:30:11 +01:00
DerLinkman
40146839ef docker-compose: bumped versions 2024-02-08 12:42:36 +01:00
DerLinkman
448f85abe8 Remove unused files 2024-02-08 12:42:36 +01:00
FreddleSpl0it
9a4b79a629 [Web] fix idp mailbox login 2024-02-08 12:42:35 +01:00
DerLinkman
058b79ed5c dovecot: corrected dockerfile inside nightly 2024-02-08 12:42:35 +01:00
FreddleSpl0it
216398355b fix functions.inc.php, update sogo and dovecot nightly image 2024-02-08 12:42:35 +01:00
Geert Hauwaerts
1cda16523d Fixed SQL query for retrieving SSO users. 2024-02-08 12:42:34 +01:00
FreddleSpl0it
2f1e1438e9 [Web] add log messages to verify-sso function 2024-02-08 12:42:34 +01:00
FreddleSpl0it
9039ab4e12 [Web] add missing file to autodiscover.php 2024-02-08 12:42:34 +01:00
DerLinkman
db47696ba7 Updated Dovecot Image to use OpenSSL 3.0 fix 2024-02-08 12:42:33 +01:00
FreddleSpl0it
eb9e3b8391 [Web] add configurable client scopes for generic-oidc 2024-02-08 12:42:33 +01:00
FreddleSpl0it
ba32f1131e [Web] dont rtrim generic-oidc urls 2024-02-08 12:42:32 +01:00
DerLinkman
27ef04baa0 Update Dovecot to reuse lz4 compression 2024-02-08 12:42:32 +01:00
DerLinkman
b3a94e79e3 Use dedicated nightly images on nightly branch + updates of images itself 2024-02-08 12:42:32 +01:00
FreddleSpl0it
3a4c0c84a3 fix keycloak mailpassword flow 2024-02-08 12:42:31 +01:00
Mirko Ceroni
73a044ec14 Update sogo-auth.php 2024-02-08 12:42:31 +01:00
Mirko Ceroni
389eb99c10 Update sogo-auth.php
initialize db before validating credentials
2024-02-08 12:42:31 +01:00
FreddleSpl0it
597d98e1d7 Fixes #5408 2024-02-08 12:42:30 +01:00
FreddleSpl0it
981307a1c6 [Web] add missing break 2024-02-08 12:42:30 +01:00
FreddleSpl0it
2d51881ae3 [Web] fix user protocol badges 2024-02-08 12:42:30 +01:00
FreddleSpl0it
788f03e993 [Dovecot] remove passwd-verify.lua generation 2024-02-08 12:42:29 +01:00
DerLinkman
81024b8c12 Clamd using Alpine Packages instead self compile 2024-02-08 12:42:29 +01:00
DerLinkman
89c5064213 Rebased Dovecot on Alpine + fixed logging 2024-02-08 12:42:29 +01:00
DerLinkman
4b18a99e55 Small fixes for CLAMD Health Check 2024-02-08 12:42:28 +01:00
DerLinkman
92d2cca7c3 Added missing Labels to Dockerfiles 2024-02-08 12:42:28 +01:00
DerLinkman
466e36ecbb Optimized Build Process for Dovecot 2024-02-08 12:42:28 +01:00
DerLinkman
7ec7bd21cb Changed Dovecot Base to Bullseye again (Self compile) 2024-02-08 12:42:27 +01:00
DerLinkman
38db7226a8 Optimized CLAMAV Builds to match exact version instead of Repo 2024-02-08 12:42:27 +01:00
DerLinkman
60f9412bb8 Switched to Alpine Edge (for IMAPSYNC Deps) 2024-02-08 12:42:26 +01:00
DerLinkman
737c0502ac Rebased Dovecot on Alpine 3.17 instead Bullseye (ARM64 Support) 2024-02-08 12:42:26 +01:00
DerLinkman
6da41b1027 Removed Test self compiled SOGo Dockerfile 2024-02-08 12:42:26 +01:00
DerLinkman
2bd46ae0fd Changed Maintainer to tinc within Dockerfiles 2024-02-08 12:42:25 +01:00
DerLinkman
c15ab10b1b Updated Clamd Building to be x86 and ARM Compatible 2024-02-08 12:42:25 +01:00
DerLinkman
ddaeebc822 [Rspamd] Update to 3.6 (Ratelimit fix) 2024-02-08 12:42:25 +01:00
FreddleSpl0it
e64293c82f [Web] minor fixes 2024-02-08 12:42:24 +01:00
FreddleSpl0it
ccc17e4a20 [SOGo] deny direct login on external users 2024-02-08 12:42:24 +01:00
FreddleSpl0it
a53ef2ed7a [SOGo] remove sogo_view and triggers 2024-02-08 12:42:24 +01:00
FreddleSpl0it
185c36cdfe [Web] catch update_sogo exceptions 2024-02-08 12:42:23 +01:00
FreddleSpl0it
9beb47c067 [Web] fix malformed_username check 2024-02-08 12:42:23 +01:00
FreddleSpl0it
3d486678ae [Web] remove keycloak sync disabled warning 2024-02-08 12:42:23 +01:00
FreddleSpl0it
04e2494af8 deny changes on identity provider if it's in use 2024-02-08 12:42:22 +01:00
FreddleSpl0it
7b47159478 rework auth - move dovecot sasl log to php 2024-02-08 12:42:22 +01:00
FreddleSpl0it
17b6ac3313 [Web] allow mailbox authsource to be switchable 2024-02-08 12:42:22 +01:00
FreddleSpl0it
43600cd127 [Web] fix identity-provider settings layout 2024-02-08 12:42:21 +01:00
FreddleSpl0it
6d3a32c1d9 [Web] trim CRON_LOG 2024-02-08 12:42:21 +01:00
FreddleSpl0it
21fa3c8458 [Web] remove unnecessary if block 2024-02-08 12:42:21 +01:00
FreddleSpl0it
6df663825a [Web] add curl timeouts to oidc requests 2024-02-08 12:42:20 +01:00
FreddleSpl0it
8ce4600562 [Web] update lang files 2024-02-08 12:42:20 +01:00
FreddleSpl0it
3179c0e712 [Dovecot] mailcowauth minor fixes 2024-02-08 12:42:19 +01:00
FreddleSpl0it
37254738e2 [Web] improve identity-provider template 2024-02-08 12:42:19 +01:00
FreddleSpl0it
a4cce147aa [Web] improve attribute sync performance & make authsource editable 2024-02-08 12:42:19 +01:00
FreddleSpl0it
b176585a9c [Web] add crontasks logs 2024-02-08 12:42:18 +01:00
FreddleSpl0it
f8647bb15e [Web] add keycloak sync crontask 2024-02-08 12:42:18 +01:00
FreddleSpl0it
85368971fd [Web] handle fatal errors on getAccessToken 2024-02-08 12:42:18 +01:00
FreddleSpl0it
e4284b8e19 [Web] fix attribute mapping list 2024-02-08 12:42:17 +01:00
FreddleSpl0it
5545d8a56c [Web] hide auth settings for external users 2024-02-08 12:42:17 +01:00
FreddleSpl0it
4dc3222f03 [Web] fix bug on mailbox login 2024-02-08 12:42:17 +01:00
FreddleSpl0it
7cf6a9d808 [Web] update lang.en-gb.json 2024-02-08 12:42:16 +01:00
FreddleSpl0it
95a15d18a7 [Web] update guzzlehttp/psr7 2024-02-08 12:42:16 +01:00
FreddleSpl0it
cee771a3fb [Web] update stevenmaguire/oauth2-keycloak and firebase/php-jwt 2024-02-08 12:42:16 +01:00
FreddleSpl0it
a805d3b2e3 [Web] add league/oauth2-client 2024-02-08 12:42:15 +01:00
FreddleSpl0it
b251c58b23 update gitignore 2024-02-08 12:42:15 +01:00
FreddleSpl0it
cc7516685f [Web] functions.auth.inc.php corrections 2024-02-08 12:42:15 +01:00
FreddleSpl0it
ad19ff5429 [Web] remove ropc flow 2024-02-08 12:42:14 +01:00
FreddleSpl0it
e784c98a5a [Web] add "add mailbox_from_template" function 2024-02-08 12:42:14 +01:00
FreddleSpl0it
28679eb916 [Web] add generic-oidc provider 2024-02-08 12:42:13 +01:00
FreddleSpl0it
c8fec24da3 [Web] add "edit mailbox_from_template" function 2024-02-08 12:42:13 +01:00
FreddleSpl0it
0c1e2ed6f2 [Web] revert configurable authsource 2024-02-08 12:42:13 +01:00
FreddleSpl0it
90476ae057 [Web] rename var for tab-config-identity-provider.twig 2024-02-08 12:42:12 +01:00
FreddleSpl0it
3b6a1d50bd [Web] add generic-oidc provider 2024-02-08 12:42:12 +01:00
FreddleSpl0it
1ab1505c88 [Web] remove sso login alertbox 2024-02-08 12:42:12 +01:00
FreddleSpl0it
593e581cf3 [Web] move iam sso functions 2024-02-08 12:42:11 +01:00
FreddleSpl0it
e202d00beb [Dovecot] group auth files 2024-02-08 12:42:11 +01:00
FreddleSpl0it
dca5f1baab [Web] move /process/login to internal endpoint 2024-02-08 12:42:11 +01:00
FreddleSpl0it
f0689e08d9 [Web] iam - add switch for direct login flow 2024-02-08 12:42:10 +01:00
FreddleSpl0it
5bbb12b53e [Dovecot] fix wrong lua syntax 2024-02-08 12:42:10 +01:00
FreddleSpl0it
c6a56e0748 [Web] add IAM delete button & fix add mbox modal 2024-02-08 12:42:10 +01:00
FreddleSpl0it
3c62a7fd9f [Web] IAM - add delete option & fix test connection 2024-02-08 12:42:09 +01:00
FreddleSpl0it
61ab17d8a1 [Web] fix iam attribute mapping ui 2024-02-08 12:42:09 +01:00
FreddleSpl0it
d4ae616460 replace ropc flow with keycloak rest api flow 2024-02-08 12:42:09 +01:00
FreddleSpl0it
b7a18255fe [Web] rename role mapping to attribute mapping 2024-02-08 12:42:08 +01:00
FreddleSpl0it
1c73a16ca0 new dovecout lua auth - use https 2024-02-08 12:42:08 +01:00
FreddleSpl0it
1aeb36d40e [Web] create ratelimit acl on iam mbox creation 2 2024-02-08 12:42:07 +01:00
FreddleSpl0it
f251c9826e [Web] create ratelimit acl on iam mbox creation 2024-02-08 12:42:07 +01:00
FreddleSpl0it
204063819c [Web] fix broken sogo-sso 2024-02-08 12:42:07 +01:00
FreddleSpl0it
13f8882616 [Web] fix app_pass ignore_access 2024-02-08 12:42:06 +01:00
FreddleSpl0it
eba1d469c8 [Web] keycloak auth functions 2024-02-08 12:42:06 +01:00
FreddleSpl0it
6e9980bf0f [Web] add manage identity provider 2024-02-08 12:42:06 +01:00
FreddleSpl0it
67c9c5b8ed [Web] remove u2f lib from prerequisites 2024-02-08 12:42:05 +01:00
FreddleSpl0it
cd3660a96d [Web] add oauth2-keycloak lib 2024-02-08 12:42:05 +01:00
FreddleSpl0it
9d8c1a01ac [Web] remove u2f lib 2024-02-08 12:42:05 +01:00
FreddleSpl0it
0a77cad2dd [Web] limit identity_provider function better 2024-02-08 12:42:04 +01:00
FreddleSpl0it
f6869da3a0 [Web] manage keycloak identity provider 2024-02-08 12:42:04 +01:00
FreddleSpl0it
6adad79e5c [Web] organize auth functions+api auth w/ dovecot 2024-02-08 12:42:04 +01:00
FreddleSpl0it
50d4d59626 [Web] update de-de + en-gb lang 2024-02-08 12:42:03 +01:00
FreddleSpl0it
56a9f1a411 [Web] organize user landing 2024-02-08 12:42:03 +01:00
FreddleSpl0it
84ff6ff2c5 [Web] fix user login history 2024-02-08 12:42:03 +01:00
FreddleSpl0it
6e35574c72 [Web] add app hide option 2024-02-08 12:42:02 +01:00
FreddleSpl0it
415c1d0574 [Web] add seperate link for logged in users 2024-02-08 12:42:02 +01:00
FreddleSpl0it
cfce7086a5 [Web] few style changes 2024-02-08 12:42:01 +01:00
FreddleSpl0it
c90d637a48 [Web] redirect to sogo after failed sogo-auth 2024-02-08 12:42:01 +01:00
791 changed files with 48893 additions and 9612 deletions

2
.gitignore vendored
View File

@@ -45,6 +45,7 @@ data/conf/rspamd/local.d/*
data/conf/rspamd/override.d/*
data/conf/sogo/custom-theme.js
data/conf/sogo/plist_ldap
data/conf/sogo/plist_ldap.sh
data/conf/sogo/sieve.creds
data/conf/sogo/cron.creds
data/conf/sogo/custom-fulllogo.svg
@@ -73,3 +74,4 @@ rebuild-images.sh
refresh_images.sh
update_diffs/
create_cold_standby.sh
!data/conf/nginx/mailcow_auth.conf

View File

@@ -11,4 +11,4 @@ if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then
echo "Clamd is up"
fi
exit 0
exit 0

View File

@@ -241,9 +241,9 @@ async def handle_pubsub_messages(channel: aioredis.client.PubSub):
else:
dockerapi.logger.error("api call: missing container_name, post_action or request")
else:
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json))
else:
dockerapi.logger.error("Unknwon PubSub recieved - %s" % json.dumps(data_json))
dockerapi.logger.error("Unknown PubSub received - %s" % json.dumps(data_json))
await asyncio.sleep(0.0)
except asyncio.TimeoutError:

View File

@@ -34,9 +34,13 @@ RUN addgroup -g 5000 vmail \
lua5.3-sql-mysql \
icu-data-full \
mariadb-connector-c \
lua-sec \
mariadb-dev \
glib-dev \
gcompat \
mariadb-client \
perl \
perl-dev \
perl-ntlm \
perl-cgi \
perl-crypt-openssl-rsa \

View File

@@ -28,7 +28,7 @@ ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null
# Create missing directories
[[ ! -d /etc/dovecot/sql/ ]] && mkdir -p /etc/dovecot/sql/
[[ ! -d /etc/dovecot/lua/ ]] && mkdir -p /etc/dovecot/lua/
[[ ! -d /etc/dovecot/auth/ ]] && mkdir -p /etc/dovecot/auth/
[[ ! -d /etc/dovecot/conf.d/ ]] && mkdir -p /etc/dovecot/conf.d/
[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
@@ -131,123 +131,6 @@ user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_VALUE(attributes, '$.mailbox_format
iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
EOF
cat <<EOF > /etc/dovecot/lua/passwd-verify.lua
function auth_password_verify(req, pass)
if req.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
if cur == nil then
script_init()
end
if req.user == nil then
req.user = ''
end
respbody = {}
-- check against mailbox passwds
local cur,errorString = con:execute(string.format([[SELECT password FROM mailbox
WHERE username = '%s'
AND active = '1'
AND domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.force_pw_update')), 0) != '1'
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(attributes, '$.%s_access')), 1) = '1']], con:escape(req.user), con:escape(req.domain), con:escape(req.service)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
row = cur:fetch (row, "a")
end
-- check against app passwds for imap and smtp
-- app passwords are only available for imap, smtp, sieve and pop3 when using sasl
if req.service == "smtp" or req.service == "imap" or req.service == "sieve" or req.service == "pop3" then
local cur,errorString = con:execute(string.format([[SELECT app_passwd.id, %s_access AS has_prot_access, app_passwd.password FROM app_passwd
INNER JOIN mailbox ON mailbox.username = app_passwd.mailbox
WHERE mailbox = '%s'
AND app_passwd.active = '1'
AND mailbox.active = '1'
AND app_passwd.domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')]], con:escape(req.service), con:escape(req.user), con:escape(req.domain)))
local row = cur:fetch ({}, "a")
while row do
if req.password_verify(req, row.password, pass) == 1 then
-- if password is valid and protocol access is 1 OR real_rip matches SOGo, proceed
if tostring(req.real_rip) == "__IPV4_SOGO__" then
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
elseif row.has_prot_access == "1" then
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_OK, ""
end
end
row = cur:fetch (row, "a")
end
end
cur:close()
con:close()
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
-- PoC
-- local reqbody = string.format([[{
-- "success":0,
-- "service":"%s",
-- "app_password":false,
-- "username":"%s",
-- "real_rip":"%s"
-- }]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip))
-- http.request {
-- method = "POST",
-- url = "http://nginx:8081/sasl_log.php",
-- source = ltn12.source.string(reqbody),
-- headers = {
-- ["content-type"] = "application/json",
-- ["content-length"] = tostring(#reqbody)
-- },
-- sink = ltn12.sink.table(respbody)
-- }
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end
function script_init()
mysql = require "luasql.mysql"
http = require "socket.http"
http.TIMEOUT = 5
ltn12 = require "ltn12"
env = mysql.mysql()
con = env:connect("__DBNAME__","__DBUSER__","__DBPASS__","localhost")
return 0
end
function script_deinit()
con:close()
env:close()
end
EOF
# Replace patterns in app-passdb.lua
sed -i "s/__DBUSER__/${DBUSER}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__DBPASS__/${DBPASS}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__DBNAME__/${DBNAME}/g" /etc/dovecot/lua/passwd-verify.lua
sed -i "s/__IPV4_SOGO__/${IPV4_NETWORK}.248/g" /etc/dovecot/lua/passwd-verify.lua
# Migrate old sieve_after file
[[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after
@@ -385,8 +268,8 @@ sievec /usr/lib/dovecot/sieve/report-ham.sieve
# Fix permissions
chown root:root /etc/dovecot/sql/*.conf
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/lua/passwd-verify.lua
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/auth/passwd-verify.lua
chmod 640 /etc/dovecot/sql/*.conf /etc/dovecot/auth/passwd-verify.lua
chown -R vmail:vmail /var/vmail/sieve
chown -R vmail:vmail /var/volatile
chown -R vmail:vmail /var/vmail_index
@@ -456,7 +339,7 @@ done
# For some strange, unknown and stupid reason, Dovecot may run into a race condition, when this file is not touched before it is read by dovecot/auth
# May be related to something inside Docker, I seriously don't know
touch /etc/dovecot/lua/passwd-verify.lua
touch /etc/dovecot/auth/passwd-verify.lua
if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf

View File

@@ -8,8 +8,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import jinja2
from jinja2 import TemplateError
from jinja2.sandbox import SandboxedEnvironment
from jinja2 import Template
import json
import redis
import time
@@ -81,22 +80,17 @@ try:
if len(meta_query) == 0:
return
msg_count = len(meta_query)
env = SandboxedEnvironment()
if r.get('Q_HTML'):
try:
template = env.from_string(r.get('Q_HTML'))
except Exception:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
else:
try:
template = Template(r.get('Q_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
try:
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
except (jinja2.exceptions.SecurityError, TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
return
template = Template(file_.read())
else:
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
text = html2text.html2text(html)
count = 0
while count < 15:
@@ -171,4 +165,4 @@ try:
notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
finally:
os.unlink(pidfile)
os.unlink(pidfile)

View File

@@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import jinja2
from jinja2.sandbox import SandboxedEnvironment
from jinja2 import Template
import redis
import time
import json
@@ -33,24 +33,16 @@ while True:
if r.get('QW_HTML'):
try:
env = SandboxedEnvironment()
template = env.from_string(r.get('QW_HTML'))
except Exception:
print("Error: Cannot parse quota template, falling back to default template.")
template = Template(r.get('QW_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quota.tpl') as file_:
env = SandboxedEnvironment()
template = env.from_string(file_.read())
template = Template(file_.read())
else:
with open('/templates/quota.tpl') as file_:
env = SandboxedEnvironment()
template = env.from_string(file_.read())
try:
html = template.render(username=username, percent=percent)
except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
sys.exit(1)
template = Template(file_.read())
html = template.render(username=username, percent=percent)
text = html2text.html2text(html)
try:
@@ -99,4 +91,4 @@ except:
try:
sys.stderr.close()
except:
pass
pass

View File

@@ -23,3 +23,4 @@ catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}"
catch_non_zero "${REDIS_CMDLINE} LTRIM CRON_LOG 0 ${LOG_LINES}"

View File

@@ -6,7 +6,7 @@ ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
ARG CODENAME=bookworm
ENV LC_ALL=C
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
tzdata \
ca-certificates \
gnupg2 \

View File

@@ -47,6 +47,7 @@ COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY acl.diff /acl.diff
COPY navMailcowBtns.diff /navMailcowBtns.diff
COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
COPY docker-entrypoint.sh /

View File

@@ -24,110 +24,6 @@ while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
done
echo "DB schema is ${DBV_NOW}"
# Recreate view
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing sogo_view..."
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
while [[ ${VIEW_OK} != 'OK' ]]; do
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) AS
SELECT
mailbox.username,
mailbox.domain,
mailbox.username,
IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0', IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'), '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
mailbox.name,
mailbox.username,
IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
IFNULL(gda.ad_alias, ''),
IFNULL(external_acl.send_as_acl, ''),
mailbox.kind,
mailbox.multiple_bookings
FROM
mailbox
LEFT OUTER JOIN
grouped_mail_aliases ga
ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
LEFT OUTER JOIN
grouped_domain_alias_address gda
ON gda.username = mailbox.username
LEFT OUTER JOIN
grouped_sender_acl_external external_acl
ON external_acl.username = mailbox.username
WHERE
mailbox.active = '1'
GROUP BY
mailbox.username;
EOF
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Will retry to setup SOGo view in 3s..."
sleep 3
fi
done
else
while [[ ${VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Waiting for SOGo view to be created by master..."
sleep 3
fi
done
fi
# Wait for static view table if missing after update and update content
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing _sogo_static_view..."
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
echo "Updating _sogo_static_view content..."
# If changed, also update init_db.inc.php
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings from sogo_view;"
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
else
echo "Waiting for database initialization..."
sleep 3
fi
done
else
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
else
echo "Waiting for database initialization by master..."
sleep 3
fi
done
fi
# Recreate password update trigger
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing update trigger..."
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
while [[ ${TRIGGER_OK} != 'OK' ]]; do
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELIMITER -
CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view
FOR EACH ROW
BEGIN
UPDATE mailbox SET password = NEW.c_password WHERE NEW.c_uid = username;
END;
-
DELIMITER ;
EOF
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
TRIGGER_OK=OK
else
echo "Will retry to setup SOGo password update trigger in 3s"
sleep 3
fi
done
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
@@ -213,7 +109,7 @@ while read -r line gal
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
# Generate alternative LDAP authentication dict, when SQL authentication fails
# This will nevertheless read attributes from LDAP
line=${line} envsubst < /etc/sogo/plist_ldap >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
/etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
echo " </array>
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
@@ -240,6 +136,10 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
# fi
#fi
if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
fi
# Rename custom logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg

View File

@@ -0,0 +1,20 @@
59,65d58
< ng-show="::!activeUser.isSuperUser"
< var:ng-click="navButtonClick"
< ng-href="/user">
< <md-icon>build</md-icon>
< <md-tooltip><var:string label:value="mailcow"/></md-tooltip>
< </md-button>
< <md-button class="md-icon-button"
83c76
< onclick="document.getElementById('mc_logout').setAttribute('action', '/'); document.getElementById('mc_logout').submit();"
---
> ng-show="::activeUser.path.logoff.length"
85c78
< ng-href="#">
---
> ng-href="{{::activeUser.path.logoff}}">
89,91d81
< <form method="POST" id="mc_logout" action="user">
< <input type="hidden" name="logout" value="1">
< </form>

View File

@@ -49,7 +49,7 @@
# 2013101601 Optical clean up #
# 2013101602 Rewrite help output #
# 2013101700 Handle Slave IO in 'Connecting' state #
# 2013101701 Minor changes in output, handling UNKWNON situations now #
# 2013101701 Minor changes in output, handling UNKNOWN situations now #
# 2013101702 Exit CRITICAL when Slave IO in Connecting state #
# 2013123000 Slave_SQL_Running also matched Slave_SQL_Running_State #
# 2015011600 Added 'moving' check to catch possible connection issues #
@@ -131,7 +131,7 @@ elif [[ -n "${socket}" && (-z "${user}" || -z "${password}") ]]; then
fi
# Connect to the DB server and store output in vars
if [[ -n $socket ]]; then
if [[ -n $socket ]]; then
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
else
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
@@ -178,33 +178,33 @@ if [ ${check} = ${ok} ] && [ ${checkio} = ${ok} ]; then
then echo "CRITICAL: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_CRITICAL}
elif [[ ${delayinfo} -ge ${warn_delay} ]]
then echo "WARNING: Slave is ${delayinfo} seconds behind Master | delay=${delayinfo}s"; exit ${STATE_WARNING}
else
else
# Everything looks OK here but now let us check if the replication is moving
if [[ -n ${moving} ]] && [[ -n ${tmpfile} ]] && [[ $readpos -eq $execpos ]]
then
#echo "Debug: Read pos is $readpos - Exec pos is $execpos"
then
#echo "Debug: Read pos is $readpos - Exec pos is $execpos"
# Check if tmp file exists
curtime=`date +%s`
if [[ -w $tmpfile ]]
then
if [[ -w $tmpfile ]]
then
tmpfiletime=`date +%s -r $tmpfile`
if [[ `expr $curtime - $tmpfiletime` -gt ${moving} ]]
then
exectmp=`cat $tmpfile`
#echo "Debug: Exec pos in tmpfile is $exectmp"
if [[ $exectmp -eq $execpos ]]
then
then
# The value read from the tmp file and from db are the same. Replication hasnt moved!
echo "WARNING: Slave replication has not moved in ${moving} seconds. Manual check required."; exit ${STATE_WARNING}
else
else
# Replication has moved since the tmp file was written. Delete tmp file and output OK.
rm $tmpfile
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi
else
else
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi
else
else
echo "$execpos" > $tmpfile
echo "OK: Slave SQL running: ${check} Slave IO running: ${checkio} / master: ${masterinfo} / slave is ${delayinfo} seconds behind master | delay=${delayinfo}s"; exit ${STATE_OK};
fi

View File

@@ -0,0 +1,108 @@
<?php
ini_set('error_reporting', 0);
header('Content-Type: application/json');
$post = trim(file_get_contents('php://input'));
if ($post) {
$post = json_decode($post, true);
}
$return = array("success" => false);
if(!isset($post['username']) || !isset($post['password']) || !isset($post['real_rip'])){
error_log("MAILCOWAUTH: Bad Request");
http_response_code(400); // Bad Request
echo json_encode($return);
exit();
}
require_once('../../../web/inc/vars.inc.php');
if (file_exists('../../../web/inc/vars.local.inc.php')) {
include_once('../../../web/inc/vars.local.inc.php');
}
require_once '../../../web/inc/lib/vendor/autoload.php';
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// Init database
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
error_log("MAILCOWAUTH: " . $e . PHP_EOL);
http_response_code(500); // Internal Server Error
echo json_encode($return);
exit;
}
// Load core functions first
require_once 'functions.inc.php';
require_once 'functions.auth.inc.php';
require_once 'sessions.inc.php';
require_once 'functions.mailbox.inc.php';
require_once 'functions.ratelimit.inc.php';
require_once 'functions.acl.inc.php';
$isSOGoRequest = $post['real_rip'] == getenv('IPV4_NETWORK') . '.248';
$result = false;
$protocol = $post['protocol'];
if ($isSOGoRequest) {
$protocol = null;
// This is a SOGo Auth request. First check for SSO password.
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
if ($sogo_sso_pass === $post['password']){
error_log('MAILCOWAUTH: SOGo SSO auth for user ' . $post['username']);
$result = true;
}
}
if ($result === false){
$result = apppass_login($post['username'], $post['password'], $protocol, array(
'is_internal' => true,
'remote_addr' => $post['real_rip']
));
if ($result) error_log('MAILCOWAUTH: App auth for user ' . $post['username']);
}
if ($result === false){
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$result = user_login($post['username'], $post['password'], array('is_internal' => true));
if ($result) error_log('MAILCOWAUTH: User auth for user ' . $post['username']);
}
if ($result) {
http_response_code(200); // OK
$return['success'] = true;
} else {
error_log("MAILCOWAUTH: Login failed for user " . $post['username']);
http_response_code(401); // Unauthorized
}
echo json_encode($return);
session_destroy();
exit;

View File

@@ -0,0 +1,42 @@
function auth_password_verify(request, password)
if request.domain == nil then
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
end
json = require "cjson"
ltn12 = require "ltn12"
https = require "ssl.https"
https.TIMEOUT = 5
local req = {
username = request.user,
password = password,
real_rip = request.real_rip,
protocol = {}
}
req.protocol[request.service] = true
local req_json = json.encode(req)
local res = {}
local b, c = https.request {
method = "POST",
url = "https://nginx:9082",
source = ltn12.source.string(req_json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#req_json)
},
sink = ltn12.sink.table(res),
insecure = true
}
local api_response = json.decode(table.concat(res))
if api_response.success == true then
return dovecot.auth.PASSDB_RESULT_OK, ""
end
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
end
function auth_passdb_lookup(req)
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
end

View File

@@ -53,7 +53,7 @@ mail_shared_explicit_inbox = yes
mail_prefetch_count = 30
passdb {
driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes cache_key=%u:%w
result_success = return-ok
result_failure = continue
result_internalfail = continue
@@ -69,7 +69,7 @@ passdb {
# a return of the following passdb is mandatory
passdb {
driver = lua
args = file=/etc/dovecot/lua/passwd-verify.lua blocking=yes
args = file=/etc/dovecot/auth/passwd-verify.lua blocking=yes
}
# Set doveadm_password=your-secret-password in data/conf/dovecot/extra.conf (create if missing)
service doveadm {
@@ -125,6 +125,7 @@ service managesieve-login {
}
service imap-login {
service_count = 1
process_min_avail = 2
process_limit = 10000
vsz_limit = 1G
user = dovenull
@@ -140,6 +141,7 @@ service imap-login {
}
service pop3-login {
service_count = 1
process_min_avail = 1
vsz_limit = 1G
inet_listener pop3_haproxy {
port = 10110
@@ -239,7 +241,7 @@ plugin {
mail_crypt_global_public_key = </mail_crypt/ecpubkey.pem
mail_crypt_save_version = 2
# Enable compression while saving, lz4 Dovecot v2.2.11+
# Enable compression while saving, lz4 Dovecot v2.3.17+
zlib_save = lz4
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
@@ -274,10 +276,10 @@ service stats {
}
}
imap_max_line_length = 2 M
#auth_cache_verify_password_with_worker = yes
#auth_cache_negative_ttl = 0
#auth_cache_ttl = 30 s
#auth_cache_size = 2 M
auth_cache_verify_password_with_worker = yes
auth_cache_negative_ttl = 60s
auth_cache_ttl = 300s
auth_cache_size = 10M
auth_verbose_passwords = sha1:6
service replicator {
process_min_avail = 1
@@ -302,7 +304,6 @@ replication_dsync_parameters = -d -l 30 -U -n INBOX
!include_try /etc/dovecot/sni.conf
!include_try /etc/dovecot/sogo_trusted_ip.conf
!include_try /etc/dovecot/extra.conf
!include_try /etc/dovecot/sogo-sso.conf
!include_try /etc/dovecot/shared_namespace.conf
!include_try /etc/dovecot/conf.d/fts.conf
# </Includes>

View File

@@ -159,6 +159,31 @@ http {
}
}
server {
listen 9082 ssl http2;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
index mailcowauth.php;
server_name _;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /mailcowauth;
client_max_body_size 10M;
location ~ \.php$ {
client_max_body_size 10M;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9001;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
include /etc/nginx/conf.d/*.conf;
{% for cert in valid_cert_dirs %}
server {
{% if not HTTP_REDIRECT %}
@@ -183,6 +208,4 @@ http {
include /etc/nginx/includes/sites-default.conf;
}
{% endfor %}
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -0,0 +1,231 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Init database
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
logMsg("err", $e->getMessage());
session_destroy();
exit;
}
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "Keycloak Sync") {
global $redis;
$finalMsg = array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message
);
$redis->lPush('CRON_LOG', json_encode($finalMsg));
}
// Load core functions first
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';
require_once __DIR__ . '/../web/inc/functions.acl.inc.php';
$_SESSION['mailcow_cc_username'] = "admin";
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1";
$_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "keycloak" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// lock sync if already running
$lock_file = '/tmp/iam-sync.lock';
if (file_exists($lock_file)) {
$lock_file_parts = explode("\n", file_get_contents($lock_file));
$pid = $lock_file_parts[0];
if (count($lock_file_parts) > 1){
$last_execution = $lock_file_parts[1];
$elapsed_time = (time() - $last_execution) / 60;
if ($elapsed_time < intval($iam_settings['sync_interval'])) {
logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");
session_destroy();
exit;
}
}
if (posix_kill($pid, 0)) {
logMsg("warning", "Sync is already running");
session_destroy();
exit;
} else {
unlink($lock_file);
}
}
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid());
fclose($lock_file_handle);
// Init Keycloak Provider
$iam_provider = identity_provider('init');
// Loop until all users have been retrieved
while (true) {
// Get admin access token
$admin_token = identity_provider("get-keycloak-admin-token");
// Make the API request to retrieve the users
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users?first=$start&max=$max";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer " . $admin_token
]);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code != 200){
logMsg("err", "Received HTTP {$code}");
session_destroy();
exit;
}
try {
$response = json_decode($response, true);
} catch (Exception $e) {
logMsg("err", $e->getMessage());
break;
}
if (!is_array($response)){
logMsg("err", "Received malformed response from keycloak api");
break;
}
if (count($response) == 0) {
break;
}
// Process the batch of users
foreach ($response as $user) {
if (empty($user['email'])){
logMsg("warning", "No email address in keycloak found for user " . $user['name']);
continue;
}
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user['email']));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user['attributes']['mailcow_template'][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
logMsg("warning", "Using default template for user " . $user['email']);
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user['email']);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user['email'])[1],
'local_part' => explode('@', $user['email'])[0],
'name' => $user['firstName'] . " " . $user['lastName'],
'authsource' => 'keycloak',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user['email']);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user['email']);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user['email'],
'name' => $user['firstName'] . " " . $user['lastName'],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user['email']);
}
$_SESSION['access_all_exception'] = '0';
sleep(0.025);
}
// Update the pagination variables for the next batch
$start += $max;
sleep(1);
}
logMsg("info", "DONE!");
// add last execution time to lock file
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid() . "\n" . time());
fclose($lock_file_handle);
session_destroy();

View File

@@ -0,0 +1,198 @@
<?php
require_once(__DIR__ . '/../web/inc/vars.inc.php');
if (file_exists(__DIR__ . '/../web/inc/vars.local.inc.php')) {
include_once(__DIR__ . '/../web/inc/vars.local.inc.php');
}
require_once __DIR__ . '/../web/inc/lib/vendor/autoload.php';
// Init database
//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
logMsg("err", $e->getMessage());
session_destroy();
exit;
}
// Init Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
$redis->auth(getenv("REDISPASS"));
}
catch (Exception $e) {
echo "Exiting: " . $e->getMessage();
session_destroy();
exit;
}
function logMsg($priority, $message, $task = "LDAP Sync") {
global $redis;
$finalMsg = array(
"time" => time(),
"priority" => $priority,
"task" => $task,
"message" => $message
);
$redis->lPush('CRON_LOG', json_encode($finalMsg));
}
// Load core functions first
require_once __DIR__ . '/../web/inc/functions.inc.php';
require_once __DIR__ . '/../web/inc/functions.auth.inc.php';
require_once __DIR__ . '/../web/inc/sessions.inc.php';
require_once __DIR__ . '/../web/inc/functions.mailbox.inc.php';
require_once __DIR__ . '/../web/inc/functions.ratelimit.inc.php';
require_once __DIR__ . '/../web/inc/functions.acl.inc.php';
$_SESSION['mailcow_cc_username'] = "admin";
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['acl']['tls_policy'] = "1";
$_SESSION['acl']['quarantine_notification'] = "1";
$_SESSION['acl']['quarantine_category'] = "1";
$_SESSION['acl']['ratelimit'] = "1";
$_SESSION['acl']['sogo_access'] = "1";
$_SESSION['acl']['protocol_access'] = "1";
$_SESSION['acl']['mailbox_relayhost'] = "1";
$_SESSION['acl']['unlimited_quota'] = "1";
$iam_settings = identity_provider('get');
if ($iam_settings['authsource'] != "ldap" || (intval($iam_settings['periodic_sync']) != 1 && intval($iam_settings['import_users']) != 1)) {
session_destroy();
exit;
}
// Set pagination variables
$start = 0;
$max = 100;
// lock sync if already running
$lock_file = '/tmp/iam-sync.lock';
if (file_exists($lock_file)) {
$lock_file_parts = explode("\n", file_get_contents($lock_file));
$pid = $lock_file_parts[0];
if (count($lock_file_parts) > 1){
$last_execution = $lock_file_parts[1];
$elapsed_time = (time() - $last_execution) / 60;
if ($elapsed_time < intval($iam_settings['sync_interval'])) {
logMsg("warning", "Sync not ready (".number_format((float)$elapsed_time, 2, '.', '')."min / ".$iam_settings['sync_interval']."min)");
session_destroy();
exit;
}
}
if (posix_kill($pid, 0)) {
logMsg("warning", "Sync is already running");
session_destroy();
exit;
} else {
unlink($lock_file);
}
}
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid());
fclose($lock_file_handle);
// Init Provider
$iam_provider = identity_provider('init');
// Get ldap users
$ldap_query = $iam_provider->query();
if (!empty($iam_settings['filter'])) {
$ldap_query = $ldap_query->rawFilter($iam_settings['filter']);
}
$response = $ldap_query->where($iam_settings['username_field'], "*")
->where($iam_settings['attribute_field'], "*")
->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname'])
->paginate($max);
// Process the users
foreach ($response as $user) {
// try get mailbox user
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user[$iam_settings['username_field']][0]));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// check if matching attribute mapping exists
$user_template = $user[$iam_settings['attribute_field']][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (empty($user[$iam_settings['username_field']][0])){
logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property.");
continue;
}
$_SESSION['access_all_exception'] = '1';
if (!$row && intval($iam_settings['import_users']) == 1){
if ($mapper_key === false){
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
} else {
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// mailbox user does not exist, create...
logMsg("info", "Creating user " . $user[$iam_settings['username_field']][0]);
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user[$iam_settings['username_field']][0])[1],
'local_part' => explode('@', $user[$iam_settings['username_field']][0])[0],
'name' => $user['displayname'][0],
'authsource' => 'ldap',
'template' => $mbox_template
));
if (!$create_res){
logMsg("err", "Could not create user " . $user[$iam_settings['username_field']][0]);
continue;
}
} else if ($row && intval($iam_settings['periodic_sync']) == 1) {
if ($mapper_key === false){
logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
continue;
}
$mbox_template = $iam_settings['templates'][$mapper_key];
// mailbox user does exist, sync attribtues...
logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]);
mailbox('edit', 'mailbox_from_template', array(
'username' => $user[$iam_settings['username_field']][0],
'name' => $user['displayname'][0],
'template' => $mbox_template
));
} else {
// skip mailbox user
logMsg("info", "Skipping user " . $user[$iam_settings['username_field']][0]);
}
$_SESSION['access_all_exception'] = '0';
sleep(0.025);
}
logMsg("info", "DONE!");
// add last execution time to lock file
$lock_file_handle = fopen($lock_file, 'w');
fwrite($lock_file_handle, getmypid() . "\n" . time());
fclose($lock_file_handle);
session_destroy();

View File

@@ -1,2 +0,0 @@
servers = "redis:6379";
timeout = 10;

View File

@@ -1,3 +1,11 @@
// redirect to mailcow login form
document.addEventListener('DOMContentLoaded', function () {
var loginForm = document.forms.namedItem("loginForm");
if (loginForm) {
window.location.href = '/user';
}
});
// Custom SOGo JS
// Change the visible font-size in the editor, this does not change the font of a html message by default
@@ -5,3 +13,4 @@ CKEDITOR.addCss("body {font-size: 16px !important}");
// Enable scayt by default
//CKEDITOR.config.scayt_autoStartup = true;

View File

@@ -1,28 +1,34 @@
<!--
<example>
<key>canAuthenticate</key>
<string>YES</string>
<key>id</key>
<string>${line}_ldap</string>
<key>isAddressBook</key>
<string>NO</string>
<key>IDFieldName</key>
<string>mail</string>
<key>UIDFieldName</key>
<string>uid</string>
<key>bindFields</key>
<array>
<string>mail</string>
</array>
<key>type</key>
<string>ldap</string>
<key>bindDN</key>
<string>cn=admin,dc=example,dc=local</string>
<key>bindPassword</key>
<string>password</string>
<key>baseDN</key>
<string>ou=People,dc=example,dc=local</string>
<key>hostname</key>
<string>ldap://1.2.3.4:389</string>
</example>
-->
#!/bin/bash
domain="$1"
gal_status="$2"
echo "
<!--
<example>
<key>canAuthenticate</key>
<string>YES</string>
<key>id</key>
<string>"${domain}"_ldap</string>
<key>isAddressBook</key>
<string>"${gal_status}"</string>
<key>IDFieldName</key>
<string>mail</string>
<key>UIDFieldName</key>
<string>uid</string>
<key>bindFields</key>
<array>
<string>mail</string>
</array>
<key>type</key>
<string>ldap</string>
<key>bindDN</key>
<string>cn=admin,dc=example,dc=local</string>
<key>bindPassword</key>
<string>password</string>
<key>baseDN</key>
<string>ou=People,dc=example,dc=local</string>
<key>hostname</key>
<string>ldap://1.2.3.4:389</string>
</example>
-->"

View File

@@ -1,8 +1,17 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
@@ -15,7 +24,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
$_SESSION['gal'] = json_decode($license_cache, true);
}
$js_minifier->add('/web/js/site/debug.js');
$js_minifier->add('/web/js/site/dashboard.js');
// vmail df
$exec_fields = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
@@ -59,7 +68,7 @@ foreach ($containers_info as $container => $container_info) {
$hostname = getenv('MAILCOW_HOSTNAME');
$timezone = getenv('TZ');
$template = 'debug.twig';
$template = 'dashboard.twig';
$template_data = [
'log_lines' => getenv('LOG_LINES'),
'vmail_df' => $vmail_df,

29
data/web/admin/index.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
$template = 'admin_index.twig';
$template_data = [
'login_delay' => @$_SESSION['ldelay']
];
$js_minifier->add('/web/js/site/index.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -1,10 +1,20 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
@@ -14,7 +24,7 @@ $js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js');
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
$role = "admin";
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';

View File

@@ -1,8 +1,17 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
@@ -11,7 +20,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$js_minifier->add('/web/js/site/queue.js');
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$role = ($_SESSION['mailcow_cc_role'] == "admin") ? 'admin' : 'domainadmin';
$role = "admin";
$template = 'queue.twig';
$template_data = [

View File

@@ -1,8 +1,17 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.admin.inc.php';
if (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /');
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "admin") {
header('Location: /admin');
exit();
}
@@ -86,6 +95,8 @@ $cors_settings['allowed_origins'] = str_replace(", ", "\n", $cors_settings['allo
$cors_settings['allowed_methods'] = explode(", ", $cors_settings['allowed_methods']);
$f2b_data = fail2ban('get');
// mbox templates
$mbox_templates = mailbox('get', 'mailbox_templates');
$template = 'admin.twig';
$template_data = [
@@ -118,6 +129,8 @@ $template_data = [
'show_rspamd_global_filters' => @$_SESSION['show_rspamd_global_filters'],
'cors_settings' => $cors_settings,
'is_https' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
'iam_settings' => $iam_settings,
'mbox_templates' => $mbox_templates,
'lang_admin' => json_encode($lang['admin']),
'lang_datatables' => json_encode($lang['datatables'])
];

View File

@@ -346,7 +346,8 @@ paths:
description: the domain which emails should be forwarded
type: string
type:
description: the type of bcc map can be `sender` or `recipient`
description: the type of bcc map can be `sender` or `rcpt`
enum: [sender, rcpt]
type: string
type: object
summary: Create BCC Map
@@ -1112,6 +1113,7 @@ paths:
domain: domain.tld
local_part: info
name: Full name
authsource: mailcow
password: atedismonsin
password2: atedismonsin
quota: "3072"
@@ -1132,11 +1134,16 @@ paths:
name:
description: Full name of the mailbox user
type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
default: mailcow
password2:
description: mailbox password for confirmation
type: string
password:
description: mailbox password
description: mailbox password when using `mailcow` as the authentication source.
type: string
quota:
description: mailbox quota
@@ -3374,6 +3381,7 @@ paths:
active: "1"
force_pw_update: "0"
name: Full name
authsource: mailcow
password: ""
password2: ""
quota: "3072"
@@ -3398,11 +3406,15 @@ paths:
name:
description: Full name of the mailbox user
type: string
authsource:
description: Specifies the authentication source for the mailbox.
type: string
enum: [mailcow, ldap, keycloak, generic-oidc]
password2:
description: new mailbox password for confirmation
type: string
password:
description: new mailbox password
description: new mailbox password when using `mailcow` as the authentication source.
type: string
quota:
description: mailbox quota
@@ -5687,7 +5699,7 @@ paths:
- description: name of domain
in: path
name: domain
required: false
required: true
schema:
type: string
- description: e.g. api-key-string
@@ -5755,8 +5767,8 @@ paths:
tags:
- Cross-Origin Resource Sharing (CORS)
description: >-
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
This endpoint allows you to manage Cross-Origin Resource Sharing (CORS) settings for the API.
CORS is a security feature implemented by web browsers to prevent unauthorized cross-origin requests.
By editing the CORS settings, you can specify which domains and which methods are permitted to access the API resources from outside the mailcow domain.
operationId: Edit Cross-Origin Resource Sharing (CORS) settings
requestBody:
@@ -5814,6 +5826,220 @@ paths:
Using this endpoint you can get the global spam filter score or the spam filter score of a certain mailbox.
operationId: Get mailbox or global spam filter score
summary: Get mailbox or global spam filter score
/api/v1/edit/identity-provider:
post:
responses:
"401":
$ref: "#/components/responses/Unauthorized"
"200":
content:
application/json:
examples:
response:
value:
- type: "success"
log:
- "identity_provider"
- "edit"
- authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "*"
redirect_url: "https://mail.mailcow.tld"
version: "26.1.3"
default_template: "Default"
mappers:
- "small_mbox"
- "medium_mbox"
templates:
- "small"
- "medium"
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
msg:
- "object_modified"
- ""
description: OK
headers: { }
tags:
- Identity Provider
description: >-
Configure an external Identity Provider to use as user authentication
operationId: Edit external Identity Provider settings
requestBody:
content:
application/json:
schema:
properties:
items:
type: array
default: ["identity-provider"]
attr:
type: object
properties:
authsource:
description: Specifies the type of the Identity Provider
type: string
enum: [ldap, keycloak, generic-oidc]
server_url:
description: The base URL of your Keycloak server. Required if `authsource` is keycloak.
type: string
realm:
description: The Keycloak realm where the mailcow client is configured. Required if `authsource` is keycloak.
type: string
client_id:
description: The Client ID assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
client_secret:
description: The Client Secret assigned to mailcow Client in OIDC Provider. Required if `authsource` is keycloak or generic-oidc.
type: string
redirect_url:
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
type: string
version:
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
type: string
default_template:
description: (Optional) If no matching Attribute Mapping exists for a User, the default template will be used for creating the mailbox, but not for updating the mailbox.
type: string
mappers:
description: (Optional) Attribute values used to match a mailbox template. Each element corresponds to the respective index in the templates array (i.e., the first element matches the first element of templates, the second matches the second, and so on).
type: array
templates:
description: (Optional) Defines the mailbox templates to be assigned. Each element corresponds to the respective index in the `mappers` array.
type: array
ignore_ssl_error:
description: If enabled, SSL certificate validation is bypassed
type: boolean
default: false
mailpassword_flow:
description: If enabled, mailcow will attempt to validate user credentials using the Keycloak Admin REST API instead of relying solely on the Authorization Code Flow.
type: boolean
default: false
periodic_sync:
description: If enabled, mailcow periodically performs a full sync of all users from Keycloak or LDAP.
type: boolean
default: false
import_users:
description: If enabled, new users are automatically imported from Keycloak or LDAP into mailcow.
type: boolean
default: false
sync_interval:
description: Defines the time interval (in minutes) for periodic synchronization and user imports.
type: number
default: 15
host:
description: The address of your LDAP server. You can provide a single hostname or a comma-separated list of hosts for fallback in case the primary server is unreachable. Required if `authsource` is ldap.
type: string
port:
description: The port used to connect to the LDAP server. Required if `authsource` is ldap.
type: string
use_ssl:
description: enable LDAPS connection. If Port is set to 389 it will be overriden to 636.
type: boolean
default: false
use_tls:
description: enable TLS connection. TLS is recommended over SSL. SSL Ports cannot be used.
type: boolean
default: false
basedn:
description: The Distinguished Name (DN) from which searches will be performed. Required if `authsource` is ldap.
type: string
username_field:
description: The LDAP attribute used to identify users during authentication. Required if `authsource` is ldap.
type: string
default: mail
filter:
description: An optional LDAP search filter to refine which users can authenticate.
type: string
attribute_field:
description: Specifies an LDAP attribute that holds a specific value which can be mapped to a mailbox template using the Attribute Mapping section. Required if `authsource` is ldap.
type: string
binddn:
description: The Distinguished Name (DN) of the LDAP user that will be used to authenticate and perform LDAP searches. This account should have sufficient permissions to read the required attributes. Required if `authsource` is ldap.
type: string
bindpass:
description: The password for the Bind DN user. It is required for authentication when connecting to the LDAP server. Required if `authsource` is ldap.
type: string
authorize_url:
description: The OIDC provider's authorization server URL. Required if `authsource` is generic-oidc.
type: string
token_url:
description: The OIDC provider's token server URL. Required if `authsource` is generic-oidc.
type: string
userinfo_url:
description: The OIDC provider's user info server URL. Required if `authsource` is generic-oidc.
type: string
client_scopes:
description: Specifies the OIDC scopes requested during authentication.
type: string
default: "openid profile email mailcow_template"
examples:
keycloak:
value:
items:
- "identity-provider"
attr:
authsource: "keycloak"
server_url: "https://auth.mailcow.tld"
realm: "mailcow"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
version: "26.1.3"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
mailpassword_flow: true
periodic_sync: true
import_users: true
sync_interval: 30
ldap:
value:
items:
- "identity-provider"
attr:
authsource: "ldap"
host: "127.0.0.1"
port: "389"
use_ssl: false
use_tls: false
ignore_ssl_error: false
basedn: "DC=mailcow,DC=local"
username_field: "mail"
filter: "(memberOf:1.2.840.113556.1.4.1941:=DC=mailcow,DC=local)"
attribute_field: "othermailbox"
binddn: "CN=LDAP Read Only,CN=Users,DC=mailcow,DC=local"
bindpass: "moohoo"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
periodic_sync: true
import_users: true
sync_interval: 30
generic-oidc:
value:
items:
- "identity-provider"
attr:
authsource: "generic-oidc"
authorize_url: "https://auth.mailcow.tld/application/o/authorize/"
token_url: "https://auth.mailcow.tld/application/o/token/"
userinfo_url: "https://auth.mailcow.tld/application/o/userinfo/"
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
client_scopes: "openid profile email mailcow_template"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
templates: ["small", "medium"]
ignore_ssl_error: true
summary: Edit external Identity Provider
tags:
- name: Domains
@@ -5860,3 +6086,5 @@ tags:
description: Edit domain ratelimits
- name: Cross-Origin Resource Sharing (CORS)
description: Manage Cross-Origin Resource Sharing (CORS) settings
- name: Identity Provider
description: Manage external Identity Provider settings

View File

@@ -1,10 +1,13 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
$default_autodiscover_config = $autodiscover_config;
if(file_exists('inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php';
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
$default_autodiscover_config = $autodiscover_config;
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
// Redis
@@ -50,6 +53,11 @@ $opt = [
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
// Init Identity Provider
$iam_provider = identity_provider('init');
$iam_settings = identity_provider('get');
$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
$login_pass = trim(htmlspecialchars_decode($_SERVER['PHP_AUTH_PW']));

File diff suppressed because one or more lines are too long

View File

@@ -8,9 +8,6 @@
.dtr-details {
width: 100%;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #F2F2F2;
}
td.child>ul>li {
display: flex;
}

View File

@@ -33,6 +33,13 @@
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'),
url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff');
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #fbfbfb;
}
#maxmsgsize { min-width: 80px; }
#slider1 .slider-selection {
background: #FFD700;
@@ -74,10 +81,23 @@
align-items: center;
padding: 0 10px !important;
}
.navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-bottom .navbar-collapse,
.navbar-fixed-top .navbar-collapse {
max-height: 1000px
}
.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus {
border-color: #dfdfdf;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
border-color: #dfdfdf;
border-bottom: 1px solid #ffffff;
}
.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
border-color: #dfdfdf;
}
.nav-tabs {
border-bottom: 1px solid #dfdfdf;
}
.bi {
display: inline-block;
font-size: 12pt;
@@ -123,18 +143,18 @@
}
}
@keyframes blink {
50% {
color: transparent
50% {
color: transparent
}
}
.loader-dot {
animation: 1s blink infinite
.loader-dot {
animation: 1s blink infinite
}
.loader-dot:nth-child(2) {
animation-delay: 250ms
.loader-dot:nth-child(2) {
animation-delay: 250ms
}
.loader-dot:nth-child(3) {
animation-delay: 500ms
.loader-dot:nth-child(3) {
animation-delay: 500ms
}
pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;}
@@ -200,13 +220,13 @@ legend {
}
.haveibeenpwned {
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.full-width-select {
width: 100%!important;
width: 100%!important;
}
.tooltip {
font-family: inherit;
@@ -330,7 +350,7 @@ code {
.caret {
transform: rotate(0deg);
}
a[aria-expanded='true'] > .caret,
a[aria-expanded='true'] > .caret,
button[aria-expanded='true'] > .caret {
transform: rotate(-180deg);
}
@@ -340,7 +360,7 @@ button[aria-expanded='true'] > .caret {
}
.list-group-header {
background: #f7f7f7;
}
}
.bg-primary, .alert-primary, .btn-primary {
@@ -366,12 +386,13 @@ button[aria-expanded='true'] > .caret {
background-color: #f0f0f0;
}
.btn.btn-outline-secondary {
border-color: #cfcfcf !important;
color: #000000 !important;
border-color: #cfcfcf !important;
}
.btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
background-color: #f0f0f0 !important;
}
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
.btn-check:checked+.btn-light, .btn-check:active+.btn-light, .btn-light:active, .btn-light.active, .show>.btn-light.dropdown-toggle {
color: #fff;
background-color: #555;
background-image: none;
@@ -389,4 +410,26 @@ button[aria-expanded='true'] > .caret {
.badge.bg-danger > a {
color: #fff !important;
text-decoration: none;
}
.hr-title {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
}
.hr-title::before,
.hr-title::after {
content: "";
flex: 1;
border-bottom: 1px solid #ccc;
}
.hr-title:not(:empty)::before {
margin-right: 10px;
}
.hr-title:not(:empty)::after {
margin-left: 10px;
}

View File

@@ -6,15 +6,9 @@
max-width: 350px;
}
.card-login .apps .btn {
width: auto;
float: left;
margin-right: 10px;
margin-top: auto;
}
.card-login .apps .btn:hover {
margin-top: 1px !important;
border-bottom-width: 3px;
.card .apps {
display: flex;
flex-wrap: wrap;
}
.responsive-tabs .nav-tabs {
@@ -43,16 +37,6 @@
opacity: 1;
}
.card-login .apps .btn {
width: 100%;
float: none;
margin-bottom: 10px;
}
.card-login .apps .btn {
border-bottom-width: 4px;
}
.xs-show {
display: block !important;
}
@@ -113,9 +97,6 @@
.btn-group.nowrap .dropdown-menu {
width: 100%;
}
.card-login .btn-group {
display: block;
}
.mass-actions-user .btn-group {
float: none;
}
@@ -191,9 +172,6 @@
.btn-group .btn i {
margin-right: 5px;
}
.card-login .btn-group .btn {
display: block !important;
}
.dt-sm-head-hidden .dtr-title {
display: none !important;
@@ -206,7 +184,7 @@
.senders-mw220 {
max-width: 100% !important;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before,
table.dataTable td.dt-control:before {
@@ -215,7 +193,7 @@
line-height: 2rem;
margin-top: -15px;
}
li .dtr-data {
padding: 0;
}

View File

@@ -59,9 +59,6 @@ body.modal-open {
.table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td {
padding: 3px;
}
table tbody tr {
cursor: pointer;
}
table tbody tr td input[type="checkbox"] {
cursor: pointer;
}

View File

@@ -0,0 +1,28 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
header('Location: /domainadmin/mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$_SESSION['index_query_string'] = $_SERVER['QUERY_STRING'];
$template = 'domainadmin_index.twig';
$template_data = [
'login_delay' => @$_SESSION['ldelay'],
];
$js_minifier->add('/web/js/site/index.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -0,0 +1,58 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
elseif (!isset($_SESSION['mailcow_cc_role']) || $_SESSION['mailcow_cc_role'] != "domainadmin") {
header('Location: /domainadmin');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$js_minifier->add('/web/js/site/mailbox.js');
$js_minifier->add('/web/js/presets/sieveMailbox.js');
$js_minifier->add('/web/js/site/pwgen.js');
$role = "domainadmin";
$is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
$allow_admin_email_login = (preg_match("/^([yY][eE][sS]|[yY])+$/", $_ENV["ALLOW_ADMIN_EMAIL_LOGIN"])) ? 'true' : 'false';
// domains
$domains = mailbox('get', 'domains');
// mailboxes
$mailboxes = [];
foreach ($domains as $domain) {
foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) {
$mailboxes[] = $mailbox;
}
}
$template = 'mailbox.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'acl_json' => json_encode($_SESSION['acl']),
'role' => $role,
'is_dual' => $is_dual,
'allow_admin_email_login' => $allow_admin_email_login,
'global_filters' => mailbox('get', 'global_filter_details'),
'domains' => $domains,
'mailboxes' => $mailboxes,
'lang_mailbox' => json_encode($lang['mailbox']),
'lang_rl' => json_encode($lang['ratelimit']),
'lang_edit' => json_encode($lang['edit']),
'lang_datatables' => json_encode($lang['datatables']),
];
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -0,0 +1,44 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.domainadmin.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
/*
/ DOMAIN ADMIN
*/
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
$fido2_data = fido2(array("action" => "get_friendly_names"));
$username = $_SESSION['mailcow_cc_username'];
$template = 'domainadmin.twig';
$template_data = [
'acl' => $_SESSION['acl'],
'acl_json' => json_encode($_SESSION['acl']),
'user_spam_score' => mailbox('get', 'spam_score', $username),
'tfa_data' => $tfa_data,
'fido2_data' => $fido2_data,
'lang_user' => json_encode($lang['user']),
'lang_datatables' => json_encode($lang['datatables']),
];
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
header('Location: /admin/dashboard');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
header('Location: /user');
exit();
}
else {
header('Location: /domainadmin');
exit();
}
$js_minifier->add('/web/js/site/user.js');
$js_minifier->add('/web/js/site/pwgen.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';

View File

@@ -131,7 +131,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
'rlyhosts' => $rlyhosts,
'sender_acl_handles' => mailbox('get', 'sender_acl_handles', $mailbox),
'user_acls' => acl('get', 'user', $mailbox),
'mailbox_details' => $result
'mailbox_details' => $result,
'iam_settings' => $iam_settings,
];
}
}

View File

@@ -66,6 +66,8 @@ $globalVariables = [
'lang_acl' => json_encode($lang['acl']),
'lang_tfa' => json_encode($lang['tfa']),
'lang_fido2' => json_encode($lang['fido2']),
'lang_success' => json_encode($lang['success']),
'lang_danger' => json_encode($lang['danger']),
'docker_timeout' => $DOCKER_TIMEOUT,
'session_lifetime' => (int)$SESSION_LIFETIME,
'csrf_token' => $_SESSION['CSRF']['TOKEN'],

View File

@@ -1,5 +1,5 @@
<?php
function acl($_action, $_scope = null, $_data = null) {
function acl($_action, $_scope = null, $_data = null, $_extra = null) {
global $pdo;
global $lang;
$_data_log = $_data;
@@ -24,7 +24,7 @@ function acl($_action, $_scope = null, $_data = null) {
}
// Users cannot change their own ACL
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) {
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin' && $_SESSION['access_all_exception'] != '1')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),
@@ -34,7 +34,7 @@ function acl($_action, $_scope = null, $_data = null) {
}
// Read all available acl options by calling acl(get)
// Set all available acl options we cannot find in the post data to 0, else 1
$is_now = acl('get', 'user', $username);
$is_now = acl('get', 'user', $username, $_extra);
if (!empty($is_now)) {
foreach ($is_now as $acl_now_name => $acl_now_val) {
$set_acls[$acl_now_name] = (isset($acl_post[$acl_now_name])) ? 1 : 0;

View File

@@ -0,0 +1,680 @@
<?php
function check_login($user, $pass, $app_passwd_data = false, $extra = null) {
global $pdo;
global $redis;
$is_internal = $extra['is_internal'];
$role = $extra['role'];
// Try validate admin
if (!isset($role) || $role == "admin") {
$result = admin_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate domain admin
if (!isset($role) || $role == "domain_admin") {
$result = domainadmin_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate user
if (!isset($role) || $role == "user") {
$result = user_login($user, $pass);
if ($result !== false) return $result;
}
// Try validate app password
if (!isset($role) || $role == "app") {
$result = apppass_login($user, $pass, $app_passwd_data);
if ($result !== false) return $result;
}
// skip log and only return false if it's an internal request
if ($is_internal == true) return false;
if (!isset($_SESSION['ldelay'])) {
$_SESSION['ldelay'] = "0";
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
elseif (!isset($_SESSION['mailcow_cc_username'])) {
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'login_failed'
);
sleep($_SESSION['ldelay']);
return false;
}
function admin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$user = strtolower(trim($user));
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `active` = '1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass)) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
// active tfa authenticators found, set pending user login
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "admin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
} else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "admin";
}
}
return false;
}
function domainadmin_login($user, $pass){
global $pdo;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '0'
AND `active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "domainadmin";
}
}
return false;
}
function user_login($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$stmt = $pdo->prepare("SELECT
mailbox.*,
domain.active AS d_active
FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// user does not exist, try call idp login and create user if possible via rest flow
if (!$row){
$result = false;
if ($iam_settings['authsource'] == 'keycloak' && intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal, 'create' => true));
} else if ($iam_settings['authsource'] == 'ldap') {
$result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal, 'create' => true));
}
if ($result !== false){
// double check if mailbox is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
return true;
}
}
clear_session();
return false;
}
switch ($row['authsource']) {
case 'keycloak':
// user authsource is keycloak, try using via rest flow
if (intval($iam_settings['mailpassword_flow']) == 1){
$result = keycloak_mbox_login_rest($user, $pass, array('is_internal' => $is_internal));
if ($result !== false) {
// double check if mailbox and domain is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: Keycloak'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
return $result;
} else {
return false;
}
break;
case 'ldap':
// user authsource is ldap
$result = ldap_mbox_login($user, $pass, array('is_internal' => $is_internal));
if ($result !== false) {
// double check if mailbox and domain is active
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: LDAP'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
return $result;
break;
case 'mailcow':
if ($row['active'] != 1 || $row['d_active'] != 1) {
return false;
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
// check for tfa authenticators
$authenticators = get_tfa($user);
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
// authenticators found, init TFA flow
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "user";
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
'msg' => array('logged_in_as', $user)
);
return "pending";
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
// no authenticators found, login successfull
if (!$is_internal){
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*', 'Provider: mailcow'),
'msg' => array('logged_in_as', $user)
);
}
return "user";
}
}
break;
}
return false;
}
function apppass_login($user, $pass, $app_passwd_data, $extra = null){
global $pdo;
$is_internal = $extra['is_internal'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
$protocol = false;
if ($app_passwd_data['eas']){
$protocol = 'eas';
} else if ($app_passwd_data['dav']){
$protocol = 'dav';
} else if ($app_passwd_data['smtp']){
$protocol = 'smtp';
} else if ($app_passwd_data['imap']){
$protocol = 'imap';
} else if ($app_passwd_data['sieve']){
$protocol = 'sieve';
} else if ($app_passwd_data['pop3']){
$protocol = 'pop3';
} else if (!$is_internal) {
return false;
}
// fetch app password data
$stmt = $pdo->prepare("SELECT `app_passwd`.*, `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active` = '1'
AND `domain`.`active` = '1'
AND `app_passwd`.`active` = '1'
AND `app_passwd`.`mailbox` = :user"
);
// fetch password data
$stmt->execute(array(
':user' => $user,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if ($protocol && $row[$protocol . '_access'] != '1'){
continue;
}
// verify password
if (verify_hash($row['password'], $pass) !== false) {
if ($is_internal){
$remote_addr = $extra['remote_addr'];
} else {
$remote_addr = ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR']);
}
$service = strtoupper($is_app_passwd);
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
$stmt->execute(array(
':service' => $service,
':app_id' => $row['app_passwd_id'],
':username' => $user,
':remote_addr' => $remote_addr
));
unset($_SESSION['ldelay']);
return "user";
}
}
return false;
}
// Keycloak REST Api Flow - auth user by mailcow_password attribute
// This password will be used for direct UI, IMAP and SMTP Auth
// To use direct user credentials, only Authorization Code Flow is valid
function keycloak_mbox_login_rest($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
$create = $extra['create'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
// get access_token for service account of mailcow client
$admin_token = identity_provider("get-keycloak-admin-token");
// get the mailcow_password attribute from keycloak user
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users";
$queryParams = array('email' => $user, 'exact' => true);
$queryString = http_build_query($queryParams);
$curl = curl_init();
curl_setopt($curl, CURLOPT_TIMEOUT, 7);
curl_setopt($curl, CURLOPT_URL, $url . '?' . $queryString);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' . $admin_token,
'Content-Type: application/json'
));
$user_res = json_decode(curl_exec($curl), true)[0];
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($code != 200) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Identity Provider returned HTTP ' . $code),
'msg' => 'generic_server_error'
);
return false;
}
if (!isset($user_res['attributes']['mailcow_password']) || !is_array($user_res['attributes']['mailcow_password'])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'User has no mailcow_password attribute'),
'msg' => 'generic_server_error'
);
return false;
}
if (empty($user_res['attributes']['mailcow_password'][0])){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', "User's mailcow_password attribute is empty"),
'msg' => 'generic_server_error'
);
return false;
}
// validate mailcow_password
$mailcow_password = $user_res['attributes']['mailcow_password'][0];
if (!verify_hash($mailcow_password, $pass)) {
return false;
}
// get mapped template
$user_template = $user_res['attributes']['mailcow_template'][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (!$create) {
// login success
if ($mapper_key !== false) {
// update user
$_SESSION['access_all_exception'] = '1';
mailbox('edit', 'mailbox_from_template', array(
'username' => $user,
'name' => $user_res['name'],
'template' => $iam_settings['templates'][$mapper_key]
));
$_SESSION['access_all_exception'] = '0';
}
return 'user';
}
// check if matching attribute exist
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
if (!empty($iam_settings['default_template'])) {
$mbox_template = $iam_settings['default_template'];
} else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
'msg' => 'generic_server_error'
);
return false;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// create mailbox
$_SESSION['access_all_exception'] = '1';
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user)[1],
'local_part' => explode('@', $user)[0],
'name' => $user_res['name'],
'authsource' => 'keycloak',
'template' => $mbox_template
));
$_SESSION['access_all_exception'] = '0';
if (!$create_res){
clear_session();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'),
'msg' => 'generic_server_error'
);
return false;
}
return 'user';
}
function ldap_mbox_login($user, $pass, $extra = null){
global $pdo;
global $iam_provider;
global $iam_settings;
$is_internal = $extra['is_internal'];
$create = $extra['create'];
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
if (!$is_internal){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
}
return false;
}
if (!$iam_provider) {
return false;
}
try {
$ldap_query = $iam_provider->query();
if (!empty($iam_settings['filter'])) {
$ldap_query = $ldap_query->rawFilter($iam_settings['filter']);
}
$ldap_query = $ldap_query->where($iam_settings['username_field'], '=', $user)
->select([$iam_settings['username_field'], $iam_settings['attribute_field'], 'displayname', 'distinguishedname', 'dn']);
$user_res = $ldap_query->firstOrFail();
} catch (Exception $e) {
// clear $_SESSION['return'] to not leak data
$_SESSION['return'] = array();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),
'msg' => 'generic_server_error'
);
return false;
}
try {
if (!$iam_provider->auth()->attempt($user_res['dn'], $pass)) {
return false;
}
} catch (Exception $e) {
// clear $_SESSION['return'] to not leak data
$_SESSION['return'] = array();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', $e->getMessage()),
'msg' => 'generic_server_error'
);
return false;
}
// get mapped template
$user_template = $user_res[$iam_settings['attribute_field']][0];
$mapper_key = array_search($user_template, $iam_settings['mappers']);
if (!$create) {
// login success
if ($mapper_key !== false) {
// update user
$_SESSION['access_all_exception'] = '1';
mailbox('edit', 'mailbox_from_template', array(
'username' => $user,
'name' => $user_res['displayname'][0],
'template' => $iam_settings['templates'][$mapper_key]
));
$_SESSION['access_all_exception'] = '0';
}
return 'user';
}
// check if matching attribute exist
if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
if (!empty($iam_settings['default_tempalte'])) {
$mbox_template = $iam_settings['default_tempalte'];
} else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
'msg' => 'generic_server_error'
);
return false;
}
} else {
$mbox_template = $iam_settings['templates'][$mapper_key];
}
// create mailbox
$_SESSION['access_all_exception'] = '1';
$create_res = mailbox('add', 'mailbox_from_template', array(
'domain' => explode('@', $user)[1],
'local_part' => explode('@', $user)[0],
'name' => $user_res['displayname'][0],
'authsource' => 'ldap',
'template' => $mbox_template
));
$_SESSION['access_all_exception'] = '0';
if (!$create_res){
clear_session();
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*', 'Could not create mailbox on login'),
'msg' => 'generic_server_error'
);
return false;
}
return 'user';
}

View File

@@ -3,7 +3,7 @@ function customize($_action, $_item, $_data = null) {
global $redis;
global $lang;
global $LOGO_LIMITS;
switch ($_action) {
case 'add':
// disable functionality when demo mode is enabled
@@ -122,10 +122,16 @@ function customize($_action, $_item, $_data = null) {
case 'app_links':
$apps = (array)$_data['app'];
$links = (array)$_data['href'];
$user_links = (array)$_data['user_href'];
$hide = (array)$_data['hide'];
$out = array();
if (count($apps) == count($links)) {
if (count($apps) == count($links) && count($apps) == count($user_links) && count($apps) == count($hide)) {
for ($i = 0; $i < count($apps); $i++) {
$out[] = array($apps[$i] => $links[$i]);
$out[] = array($apps[$i] => array(
'link' => $links[$i],
'user_link' => $user_links[$i],
'hide' => ($hide[$i] === '0' || $hide[$i] === 0) ? false : true
));
}
try {
$redis->set('APP_LINKS', json_encode($out));
@@ -256,7 +262,23 @@ function customize($_action, $_item, $_data = null) {
);
return false;
}
return ($app_links) ? $app_links : false;
if (empty($app_links)){
return false;
}
// convert from old style
foreach($app_links as $i => $entry){
foreach($entry as $app => $link){
if (empty($link['link']) && empty($link['user_link'])){
$app_links[$i][$app] = array();
$app_links[$i][$app]['link'] = $link;
$app_links[$i][$app]['user_link'] = $link;
}
}
}
return $app_links;
break;
case 'main_logo':
case 'main_logo_dark':

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
global $redis;
global $lang;
global $MAILBOX_DEFAULT_ATTRIBUTES;
global $iam_settings;
$_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
@@ -1005,6 +1007,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$local_part = strtolower(trim($_data['local_part']));
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$username = $local_part . '@' . $domain;
$authsource = 'mailcow';
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -1021,15 +1024,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
if ($_data['authsource'] == "mailcow" ||
in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){
$authsource = $_data['authsource'];
}
if (empty($name)) {
$name = $local_part;
}
$template_attr = null;
if ($_data['template']){
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes'];
$template_attr = mailbox('get', 'mailbox_templates', $_data['template'], $_extra)['attributes'];
}
if (empty($template_attr)) {
$template_attr = mailbox('get', 'mailbox_templates')[0]['attributes'];
$template_attr = mailbox('get', 'mailbox_templates', null, $_extra)[0]['attributes'];
}
$MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr);
@@ -1038,7 +1045,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$name = ltrim(rtrim($_data['name'], '>'), '<');
$tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags'];
$quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2;
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
if ($authsource != 'mailcow'){
$password = '';
$password2 = '';
$password_hashed = '';
}
if (!hasACLAccess("unlimited_quota") && $quota_m === 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1067,6 +1079,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
$quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
$quota_b = ($quota_m * 1048576);
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
$mailbox_attrs = json_encode(
array(
'force_pw_update' => strval($force_pw_update),
@@ -1081,7 +1097,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'passwd_update' => time(),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
'quarantine_notification' => strval($quarantine_notification),
'quarantine_category' => strval($quarantine_category)
'quarantine_category' => strval($quarantine_category),
'attribute_hash' => $attribute_hash
)
);
if (!is_valid_domain_name($domain)) {
@@ -1156,10 +1173,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
if (password_check($password, $password2) !== true) {
return false;
if ($authsource == 'mailcow'){
if (password_check($password, $password2) !== true) {
return false;
}
$password_hashed = hash_password($password);
}
$password_hashed = hash_password($password);
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -1185,8 +1204,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)");
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `authsource`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :authsource, :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
@@ -1195,6 +1214,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
':local_part' => $local_part,
':domain' => $domain,
':mailbox_attrs' => $mailbox_attrs,
':authsource' => $authsource,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
@@ -1214,11 +1234,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
break;
}
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
try {
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
} catch (Exception $e) {
}
}
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
@@ -1312,16 +1335,62 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'object' => $username,
'rl_frame' => $_data['rl_frame'],
'rl_value' => $_data['rl_value']
));
), $_extra);
}
update_sogo_static_view($username);
try {
update_sogo_static_view($username);
} catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_added', htmlspecialchars($username))
);
return true;
break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['domain'] = $_data['domain'];
$mbox_template_data['name'] = $_data['name'];
$mbox_template_data['local_part'] = $_data['local_part'];
$mbox_template_data['authsource'] = $_data['authsource'];
$mbox_template_data['attribute_hash'] = $attribute_hash;
$mbox_template_data['quota'] = intval($mbox_template_data['quota'] / 1048576);
$mailbox_attributes = array('acl' => array());
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($mailbox_attributes['acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
return mailbox('add', 'mailbox', $mailbox_attributes);
break;
case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
@@ -1689,7 +1758,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) {
if (!hasACLAccess("tls_policy")) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1698,7 +1767,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1706,7 +1775,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
$is_now = mailbox('get', 'tls_policy', $username);
$is_now = mailbox('get', 'tls_policy', $username, $_extra);
if (!empty($is_now)) {
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
@@ -1743,7 +1812,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) {
if (!hasACLAccess("quarantine_notification")) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1752,7 +1821,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1760,7 +1829,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
$is_now = mailbox('get', 'quarantine_notification', $username);
$is_now = mailbox('get', 'quarantine_notification', $username, $_extra);
if (!empty($is_now)) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
}
@@ -1802,7 +1871,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['quarantine_category']) || $_SESSION['acl']['quarantine_category'] != "1" ) {
if (!hasACLAccess("quarantine_category")) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1811,7 +1880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -1819,7 +1888,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
$is_now = mailbox('get', 'quarantine_category', $username);
$is_now = mailbox('get', 'quarantine_category', $username, $_extra);
if (!empty($is_now)) {
$quarantine_category = (isset($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
}
@@ -2863,7 +2932,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
$is_now = mailbox('get', 'mailbox_details', $username);
$is_now = mailbox('get', 'mailbox_details', $username, $_extra);
if (isset($_data['protocol_access'])) {
$_data['protocol_access'] = (array)$_data['protocol_access'];
$_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0;
@@ -2874,20 +2943,29 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && isset($_SESSION['acl']['protocol_access']) && $_SESSION['acl']['protocol_access'] == "1") ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && isset($_SESSION['acl']['mailbox_relayhost']) && $_SESSION['acl']['mailbox_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
(int)$sogo_access = (isset($_data['sogo_access']) && hasACLAccess("sogo_access")) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$imap_access = (isset($_data['imap_access']) && hasACLAccess("protocol_access")) ? intval($_data['imap_access']) : intval($is_now['attributes']['imap_access']);
(int)$pop3_access = (isset($_data['pop3_access']) && hasACLAccess("protocol_access")) ? intval($_data['pop3_access']) : intval($is_now['attributes']['pop3_access']);
(int)$smtp_access = (isset($_data['smtp_access']) && hasACLAccess("protocol_access")) ? intval($_data['smtp_access']) : intval($is_now['attributes']['smtp_access']);
(int)$sieve_access = (isset($_data['sieve_access']) && hasACLAccess("protocol_access")) ? intval($_data['sieve_access']) : intval($is_now['attributes']['sieve_access']);
(int)$relayhost = (isset($_data['relayhost']) && hasACLAccess("mailbox_relayhost")) ? intval($_data['relayhost']) : intval($is_now['attributes']['relayhost']);
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
$domain = $is_now['domain'];
$quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
$pw_recovery_email = (isset($_data['pw_recovery_email'])) ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
$tags = (is_array($_data['tags']) ? $_data['tags'] : array());
$attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : '';
$authsource = $is_now['authsource'];
if ($_data['authsource'] == "mailcow" ||
in_array($_data['authsource'], array('keycloak', 'generic-oidc', 'ldap')) && $iam_settings['authsource'] == $_data['authsource']){
$authsource = $_data['authsource'];
}
if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){
$force_pw_update = 0;
}
$pw_recovery_email = (isset($_data['pw_recovery_email']) && $authsource == 'mailcow') ? $_data['pw_recovery_email'] : $is_now['attributes']['recovery_email'];
}
else {
$_SESSION['return'][] = array(
@@ -2898,7 +2976,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue;
}
// if already 0 == ok
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) {
if (!hasACLAccess("unlimited_quota") && ($quota_m == 0 && $is_now['quota'] != 0)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -2914,7 +2992,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
continue;
}
$DomainData = mailbox('get', 'domain_details', $domain);
$DomainData = mailbox('get', 'domain_details', $domain, $_extra);
if ($quota_m > ($is_now['max_new_quota'] / 1048576)) {
$_SESSION['return'][] = array(
'type' => 'danger',
@@ -2933,7 +3011,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
$extra_acls = array();
if (isset($_data['extended_sender_acl'])) {
if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) {
if (!hasACLAccess("extend_sender_acl")) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -3126,7 +3204,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed,
`attributes` = JSON_SET(`attributes`, '$.passwd_update', NOW())
WHERE `username` = :username");
WHERE `username` = :username AND authsource = 'mailcow'");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
@@ -3145,6 +3223,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`active` = :active,
`name`= :name,
`quota` = :quota_b,
`authsource` = :authsource,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access),
`attributes` = JSON_SET(`attributes`, '$.imap_access', :imap_access),
@@ -3152,22 +3231,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`attributes` = JSON_SET(`attributes`, '$.pop3_access', :pop3_access),
`attributes` = JSON_SET(`attributes`, '$.relayhost', :relayhost),
`attributes` = JSON_SET(`attributes`, '$.smtp_access', :smtp_access),
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email)
`attributes` = JSON_SET(`attributes`, '$.recovery_email', :recovery_email),
`attributes` = JSON_SET(`attributes`, '$.attribute_hash', :attribute_hash)
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':force_pw_update' => $force_pw_update,
':sogo_access' => $sogo_access,
':imap_access' => $imap_access,
':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access,
':smtp_access' => $smtp_access,
':recovery_email' => $pw_recovery_email,
':relayhost' => $relayhost,
':username' => $username
));
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':attribute_hash' => $attribute_hash,
':force_pw_update' => $force_pw_update,
':sogo_access' => $sogo_access,
':imap_access' => $imap_access,
':pop3_access' => $pop3_access,
':sieve_access' => $sieve_access,
':smtp_access' => $smtp_access,
':recovery_email' => $pw_recovery_email,
':relayhost' => $relayhost,
':username' => $username,
':authsource' => $authsource
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
@@ -3188,11 +3270,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
);
break;
}
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
try {
$stmt = $pdo->prepare("INSERT INTO `tags_mailbox` (`username`, `tag_name`) VALUES (:username, :tag_name)");
$stmt->execute(array(
':username' => $username,
':tag_name' => $tag,
));
} catch (Exception $e) {
}
}
$_SESSION['return'][] = array(
@@ -3201,7 +3286,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_modified', $username)
);
update_sogo_static_view($username);
try {
update_sogo_static_view($username);
} catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
}
return true;
break;
@@ -3231,7 +3324,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
}
$is_now = mailbox('get', 'mailbox_details', $old_username);
if (empty($is_now)) {
if (empty($is_now) || ($is_now['active'] != '1' && $is_now['active'] != '2')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -3401,6 +3494,75 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'msg' => array('mailbox_renamed', $old_username, $new_username)
);
break;
case 'mailbox_from_template':
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $_data['template']
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($mbox_template_data)){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'template_missing'
);
return false;
}
$attribute_hash = sha1(json_encode($mbox_template_data["attributes"]));
$is_now = mailbox('get', 'mailbox_details', $_data['username']);
$name = ltrim(rtrim($_data['name'], '>'), '<');
if ($is_now['attributes']['attribute_hash'] == $attribute_hash && $is_now['name'] == $name)
return true;
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['attribute_hash'] = $attribute_hash;
$mbox_template_data['name'] = $name;
$quarantine_attributes = array('username' => $_data['username']);
$tls_attributes = array('username' => $_data['username']);
$ratelimit_attributes = array('object' => $_data['username']);
$acl_attributes = array('username' => $_data['username'], 'user_acl' => array());
$mailbox_attributes = array('username' => $_data['username']);
foreach ($mbox_template_data as $key => $value){
switch (true) {
case (strpos($key, 'quarantine_') === 0):
$quarantine_attributes[$key] = $value;
break;
case (strpos($key, 'tls_') === 0):
if ($value == null)
$value = 0;
$tls_attributes[$key] = $value;
break;
case (strpos($key, 'rl_') === 0):
$ratelimit_attributes[$key] = $value;
break;
case (strpos($key, 'acl_') === 0 && $value != 0):
array_push($acl_attributes['user_acl'], str_replace('acl_' , '', $key));
break;
default:
$mailbox_attributes[$key] = $value;
break;
}
}
$mailbox_attributes['quota'] = intval($mailbox_attributes['quota'] / 1048576);
$result = mailbox('edit', 'mailbox', $mailbox_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'tls_policy', $tls_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_notification', $quarantine_attributes);
if ($result === false) return $result;
$result = mailbox('edit', 'quarantine_category', $quarantine_attributes);
if ($result === false) return $result;
$result = ratelimit('edit', 'mailbox', $ratelimit_attributes);
if ($result === false) return $result;
$result = acl('edit', 'user', $acl_attributes);
if ($result === false) return $result;
$_SESSION['return'] = array();
return true;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
@@ -4666,6 +4828,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2`.`bytes`,
`attributes`,
`custom_attributes`,
@@ -4687,6 +4850,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
`mailbox`.`quota`,
`mailbox`.`created`,
`mailbox`.`modified`,
`mailbox`.`authsource`,
`quota2replica`.`bytes`,
`attributes`,
`custom_attributes`,
@@ -4716,6 +4880,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['created'] = $row['created'];
$mailboxdata['modified'] = $row['modified'];
$mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow';
if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info";
@@ -4746,7 +4911,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
else if ($SaslLogs['service'] == 'pop3') {
$last_pop3_login = strtotime($SaslLogs['datetime']);
}
else if ($SaslLogs['service'] == 'SSO') {
else if ($SaslLogs['service'] == 'SSO') {
$last_sso_login = strtotime($SaslLogs['datetime']);
}
}
@@ -4759,7 +4924,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
if (!isset($last_pop3_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_pop3_login = 0;
}
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
if (!isset($last_sso_login) || $GLOBALS['SHOW_LAST_LOGIN'] === false) {
$last_sso_login = 0;
}
$mailboxdata['last_imap_login'] = $last_imap_login;
@@ -4811,7 +4976,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
return $mailboxdata;
break;
case 'mailbox_templates':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin" && $_SESSION['access_all_exception'] != "1") {
return false;
}
$_data = (isset($_data)) ? intval($_data) : null;
@@ -5565,7 +5730,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
continue;
}
update_sogo_static_view($username);
try {
update_sogo_static_view($username);
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -5779,6 +5952,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
break;
}
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
update_sogo_static_view();
try {
update_sogo_static_view();
}catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => $e->getMessage()
);
}
}
return true;
}

View File

@@ -1,10 +1,10 @@
<?php
function ratelimit($_action, $_scope, $_data = null) {
function ratelimit($_action, $_scope, $_data = null, $_extra = null) {
global $redis;
$_data_log = $_data;
switch ($_action) {
case 'edit':
if (!isset($_SESSION['acl']['ratelimit']) || $_SESSION['acl']['ratelimit'] != "1" ) {
if (!hasACLAccess("ratelimit")) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
@@ -93,7 +93,7 @@ function ratelimit($_action, $_scope, $_data = null) {
continue;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin')) {
|| ($_SESSION['mailcow_cc_role'] != 'admin' && $_SESSION['mailcow_cc_role'] != 'domainadmin' && $_SESSION['access_all_exception'] != '1')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_scope, $_data_log),

View File

@@ -30,6 +30,40 @@ if(!file_exists($CSSPath)) {
cleanupCSS($hash);
}
$mailcow_apps_processed = $MAILCOW_APPS;
$app_links = customize('get', 'app_links');
$app_links_processed = $app_links;
$hide_mailcow_apps = true;
for ($i = 0; $i < count($mailcow_apps_processed); $i++) {
if ($hide_mailcow_apps && !$mailcow_apps_processed[$i]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
if ($app_links_processed[$i]['user_link']) {
$mailcow_apps_processed[$i]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $mailcow_apps_processed[$i]['user_link']);
} else {
$mailcow_apps_processed[$i]['user_link'] = $mailcow_apps_processed[$i]['link'];
}
}
}
if ($app_links_processed){
for ($i = 0; $i < count($app_links_processed); $i++) {
$key = array_key_first($app_links_processed[$i]);
if ($hide_mailcow_apps && !$app_links_processed[$i][$key]['hide']){
$hide_mailcow_apps = false;
}
if (!empty($_SESSION['mailcow_cc_username'])){
if ($app_links_processed[$i][$key]['user_link']) {
$app_links_processed[$i][$key]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links_processed[$i][$key]['user_link']);
} else {
$app_links_processed[$i][$key]['user_link'] = $app_links_processed[$i][$key]['link'];
}
}
}
}
$globalVariables = [
'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'),
'mailcow_locale' => @$_SESSION['mailcow_locale'],
@@ -45,8 +79,11 @@ $globalVariables = [
'lang' => $lang,
'skip_sogo' => (getenv('SKIP_SOGO') == 'y'),
'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'),
'hide_mailcow_apps' => $hide_mailcow_apps,
'mailcow_apps' => $MAILCOW_APPS,
'app_links' => customize('get', 'app_links'),
'mailcow_apps_processed' => $mailcow_apps_processed,
'app_links' => $app_links,
'app_links_processed' => $app_links_processed,
'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'),
'uri' => $_SERVER['REQUEST_URI'],
];

View File

@@ -4,7 +4,7 @@ function init_db_schema()
try {
global $pdo;
$db_version = "20112024_1105";
$db_version = "27012025_1555";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -368,6 +368,7 @@ function init_db_schema()
"custom_attributes" => "JSON NOT NULL DEFAULT ('{}')",
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
"multiple_bookings" => "INT NOT NULL DEFAULT -1",
"authsource" => "ENUM('mailcow', 'keycloak', 'generic-oidc', 'ldap') DEFAULT 'mailcow'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
@@ -575,6 +576,20 @@ function init_db_schema()
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"identity_provider" => array(
"cols" => array(
"key" => "VARCHAR(255) NOT NULL",
"value" => "TEXT NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("key")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"logs" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
@@ -1455,6 +1470,9 @@ function init_db_schema()
));
}
// remove old sogo views and triggers
$pdo->query("DROP TRIGGER IF EXISTS sogo_update_password");
if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL;
} else {
@@ -1478,6 +1496,7 @@ function init_db_schema()
}
if (php_sapi_name() == "cli") {
include '/web/inc/vars.inc.php';
include '/web/inc/functions.inc.php';
include '/web/inc/functions.docker.inc.php';
// $now = new DateTime();
// $mins = $now->getOffset() / 60;
@@ -1499,9 +1518,7 @@ if (php_sapi_name() == "cli") {
if (intval($res['OK_C']) === 2) {
// Be more precise when replacing into _sogo_static_view, col orders may change
try {
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
update_sogo_static_view();
echo "Fixed _sogo_static_view" . PHP_EOL;
} catch (Exception $e) {
// Dunno

View File

@@ -1,7 +1,6 @@
{
"require": {
"robthree/twofactorauth": "^1.6",
"yubico/u2flib-server": "^1.0",
"phpmailer/phpmailer": "^6.1",
"php-mime-mail-parser/php-mime-mail-parser": "^7",
"soundasleep/html2text": "^0.5.0",
@@ -9,7 +8,9 @@
"matthiasmullie/minify": "^1.3",
"bshaffer/oauth2-server-php": "^1.11",
"mustangostang/spyc": "^0.6.3",
"directorytree/ldaprecord": "^2.4",
"twig/twig": "^3.0"
"directorytree/ldaprecord": "^3.3",
"twig/twig": "^3.0",
"stevenmaguire/oauth2-keycloak": "^4.0",
"league/oauth2-client": "^2.7"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
@@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) {
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 21);
$opened_path = realpath($opened_path) ?: $opened_path;
$this->handle = fopen($opened_path, $mode);
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
// remove all traces of this stream wrapper once it has been used
stream_wrapper_unregister('composer-bin-proxy');
return (bool) $this->handle;
}
@@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) {
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
@@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) {
public function stream_stat()
{
return fstat($this->handle);
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) {
include("composer-bin-proxy://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
exit(0);
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nesbot/carbon/bin/carbon');
}
}
include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';
return include __DIR__ . '/..'.'/nesbot/carbon/bin/carbon';

View File

@@ -12,6 +12,7 @@
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
@@ -23,18 +24,17 @@ if (PHP_VERSION_ID < 80000) {
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of composer-bin-proxy:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 21);
$opened_path = realpath($opened_path) ?: $opened_path;
$this->handle = fopen($opened_path, $mode);
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
// remove all traces of this stream wrapper once it has been used
stream_wrapper_unregister('composer-bin-proxy');
return (bool) $this->handle;
}
@@ -66,6 +66,16 @@ if (PHP_VERSION_ID < 80000) {
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
@@ -78,20 +88,32 @@ if (PHP_VERSION_ID < 80000) {
public function stream_stat()
{
return fstat($this->handle);
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('composer-bin-proxy', 'Composer\BinProxyWrapper')) {
include("composer-bin-proxy://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
exit(0);
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
}
}
include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';
return include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Carbon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,14 @@
# carbonphp/carbon-doctrine-types
Types to use Carbon in Doctrine
## Documentation
[Check how to use in the official Carbon documentation](https://carbon.nesbot.com/symfony/)
This package is an externalization of [src/Carbon/Doctrine](https://github.com/briannesbitt/Carbon/tree/2.71.0/src/Carbon/Doctrine)
from `nestbot/carbon` package.
Externalization allows to better deal with different versions of dbal. With
version 4.0 of dbal, it no longer sustainable to be compatible with all version
using a single code.

View File

@@ -0,0 +1,36 @@
{
"name": "carbonphp/carbon-doctrine-types",
"description": "Types to use Carbon in Doctrine",
"type": "library",
"keywords": [
"date",
"time",
"DateTime",
"Carbon",
"Doctrine"
],
"require": {
"php": "^8.1"
},
"require-dev": {
"doctrine/dbal": "^4.0.0",
"nesbot/carbon": "^2.71.0 || ^3.0.0",
"phpunit/phpunit": "^10.3"
},
"conflict": {
"doctrine/dbal": "<4.0.0 || >=5.0.0"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Carbon\\Doctrine\\": "src/Carbon/Doctrine/"
}
},
"authors": [
{
"name": "KyleKatarn",
"email": "kylekatarnls@gmail.com"
}
],
"minimum-stability": "dev"
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Doctrine\DBAL\Platforms\AbstractPlatform;
interface CarbonDoctrineType
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform);
public function convertToPHPValue(mixed $value, AbstractPlatform $platform);
public function convertToDatabaseValue($value, AbstractPlatform $platform);
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonImmutableType extends DateTimeImmutableType implements CarbonDoctrineType
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
class CarbonType extends DateTimeType implements CarbonDoctrineType
{
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Carbon\Doctrine;
@@ -15,7 +8,12 @@ use Carbon\Carbon;
use Carbon\CarbonInterface;
use DateTimeInterface;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Types\Exception\InvalidType;
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
use Exception;
/**
@@ -23,6 +21,14 @@ use Exception;
*/
trait CarbonTypeConverter
{
/**
* This property differentiates types installed by carbonphp/carbon-doctrine-types
* from the ones embedded previously in nesbot/carbon source directly.
*
* @readonly
*/
public bool $external = true;
/**
* @return class-string<T>
*/
@@ -31,20 +37,12 @@ trait CarbonTypeConverter
return Carbon::class;
}
/**
* @return string
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
$precision = $fieldDeclaration['precision'] ?: 10;
if ($fieldDeclaration['secondPrecision'] ?? false) {
$precision = 0;
}
if ($precision === 10) {
$precision = DateTimeDefaultPrecision::get();
}
$precision = min(
$fieldDeclaration['precision'] ?? DateTimeDefaultPrecision::get(),
$this->getMaximumPrecision($platform),
);
$type = parent::getSQLDeclaration($fieldDeclaration, $platform);
@@ -63,10 +61,25 @@ trait CarbonTypeConverter
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return T|null
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if ($value === null) {
return $value;
}
if ($value instanceof DateTimeInterface) {
return $value->format('Y-m-d H:i:s.u');
}
throw InvalidType::new(
$value,
static::class,
['null', 'DateTime', 'Carbon']
);
}
private function doConvertToPHPValue(mixed $value)
{
$class = $this->getCarbonClassName();
@@ -88,9 +101,9 @@ trait CarbonTypeConverter
}
if (!$date) {
throw ConversionException::conversionFailedFormat(
throw ValueNotConvertible::new(
$value,
$this->getName(),
static::class,
'Y-m-d H:i:s.u or any format supported by '.$class.'::parse()',
$error
);
@@ -99,25 +112,20 @@ trait CarbonTypeConverter
return $date;
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return string|null
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
private function getMaximumPrecision(AbstractPlatform $platform): int
{
if ($value === null) {
return $value;
if ($platform instanceof DB2Platform) {
return 12;
}
if ($value instanceof DateTimeInterface) {
return $value->format('Y-m-d H:i:s.u');
if ($platform instanceof OraclePlatform) {
return 9;
}
throw ConversionException::conversionFailedInvalidType(
$value,
$this->getName(),
['null', 'DateTime', 'Carbon']
);
if ($platform instanceof SQLServerPlatform || $platform instanceof SQLitePlatform) {
return 3;
}
return 6;
}
}

View File

@@ -1,13 +1,6 @@
<?php
/**
* This file is part of the Carbon package.
*
* (c) Brian Nesbitt <brian@nesbot.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Carbon\Doctrine;

View File

@@ -1,12 +1,12 @@
<?php
/**
* Thanks to https://github.com/flaushi for his suggestion:
* https://github.com/doctrine/dbal/issues/2873#issuecomment-534956358
*/
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\CarbonImmutable;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeImmutableType;
class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDoctrineType
@@ -14,6 +14,14 @@ class DateTimeImmutableType extends VarDateTimeImmutableType implements CarbonDo
/** @use CarbonTypeConverter<CarbonImmutable> */
use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?CarbonImmutable
{
return $this->doConvertToPHPValue($value);
}
/**
* @return class-string<CarbonImmutable>
*/

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Carbon\Doctrine;
use Carbon\Carbon;
use DateTime;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\VarDateTimeType;
class DateTimeType extends VarDateTimeType implements CarbonDoctrineType
{
/** @use CarbonTypeConverter<Carbon> */
use CarbonTypeConverter;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?Carbon
{
return $this->doConvertToPHPValue($value);
}
}

View File

@@ -13,9 +13,4 @@ return array(
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'u2flib_server\\Error' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\RegisterRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\Registration' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\SignRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\U2F' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
);

View File

@@ -6,8 +6,12 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',

View File

@@ -15,17 +15,27 @@ return array(
'Symfony\\Contracts\\Translation\\' => array($vendorDir . '/symfony/translation-contracts'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Stevenmaguire\\OAuth2\\Client\\' => array($vendorDir . '/stevenmaguire/oauth2-keycloak/src'),
'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Psr\\Clock\\' => array($vendorDir . '/psr/clock/src'),
'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'),
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'MatthiasMullie\\PathConverter\\' => array($vendorDir . '/matthiasmullie/path-converter/src'),
'MatthiasMullie\\Minify\\' => array($vendorDir . '/matthiasmullie/minify/src'),
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'),
'LdapRecord\\' => array($vendorDir . '/directorytree/ldaprecord/src'),
'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'),
'Carbon\\Doctrine\\' => array($vendorDir . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine'),
'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
);

View File

@@ -7,8 +7,12 @@ namespace Composer\Autoload;
class ComposerStaticInit873464e4bd965a3168f133248b1b218b
{
public static $files = array (
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
@@ -24,12 +28,12 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
);
public static $prefixLengthsPsr4 = array (
'T' =>
'T' =>
array (
'Twig\\' => 5,
'Tightenco\\Collect\\' => 18,
),
'S' =>
'S' =>
array (
'Symfony\\Polyfill\\Php81\\' => 23,
'Symfony\\Polyfill\\Php80\\' => 23,
@@ -38,141 +42,198 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'Symfony\\Contracts\\Translation\\' => 30,
'Symfony\\Component\\VarDumper\\' => 28,
'Symfony\\Component\\Translation\\' => 30,
'Stevenmaguire\\OAuth2\\Client\\' => 28,
),
'R' =>
'R' =>
array (
'RobThree\\Auth\\' => 14,
),
'P' =>
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
'Psr\\Http\\Client\\' => 16,
'Psr\\Container\\' => 14,
'Psr\\Clock\\' => 10,
'PhpMimeMailParser\\' => 18,
'PHPMailer\\PHPMailer\\' => 20,
),
'M' =>
'M' =>
array (
'MatthiasMullie\\PathConverter\\' => 29,
'MatthiasMullie\\Minify\\' => 22,
),
'L' =>
'L' =>
array (
'League\\OAuth2\\Client\\' => 21,
'LdapRecord\\' => 11,
),
'I' =>
'I' =>
array (
'Illuminate\\Contracts\\' => 21,
),
'H' =>
'H' =>
array (
'Html2Text\\' => 10,
),
'D' =>
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
'GuzzleHttp\\Promise\\' => 19,
'GuzzleHttp\\' => 11,
),
'F' =>
array (
'Firebase\\JWT\\' => 13,
),
'D' =>
array (
'Ddeboer\\Imap\\' => 13,
),
'C' =>
'C' =>
array (
'Carbon\\Doctrine\\' => 16,
'Carbon\\' => 7,
),
);
public static $prefixDirsPsr4 = array (
'Twig\\' =>
'Twig\\' =>
array (
0 => __DIR__ . '/..' . '/twig/twig/src',
),
'Tightenco\\Collect\\' =>
'Tightenco\\Collect\\' =>
array (
0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
),
'Symfony\\Polyfill\\Php81\\' =>
'Symfony\\Polyfill\\Php81\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php81',
),
'Symfony\\Polyfill\\Php80\\' =>
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Mbstring\\' =>
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Ctype\\' =>
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Translation\\' =>
'Symfony\\Contracts\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation-contracts',
),
'Symfony\\Component\\VarDumper\\' =>
'Symfony\\Component\\VarDumper\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/var-dumper',
),
'Symfony\\Component\\Translation\\' =>
'Symfony\\Component\\Translation\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/translation',
),
'RobThree\\Auth\\' =>
'Stevenmaguire\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/stevenmaguire/oauth2-keycloak/src',
),
'RobThree\\Auth\\' =>
array (
0 => __DIR__ . '/..' . '/robthree/twofactorauth/lib',
),
'Psr\\SimpleCache\\' =>
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
),
'Psr\\Log\\' =>
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/src',
),
'Psr\\Container\\' =>
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Http\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-client/src',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'PhpMimeMailParser\\' =>
'Psr\\Clock\\' =>
array (
0 => __DIR__ . '/..' . '/psr/clock/src',
),
'PhpMimeMailParser\\' =>
array (
0 => __DIR__ . '/..' . '/php-mime-mail-parser/php-mime-mail-parser/src',
),
'PHPMailer\\PHPMailer\\' =>
'PHPMailer\\PHPMailer\\' =>
array (
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
),
'MatthiasMullie\\PathConverter\\' =>
'MatthiasMullie\\PathConverter\\' =>
array (
0 => __DIR__ . '/..' . '/matthiasmullie/path-converter/src',
),
'MatthiasMullie\\Minify\\' =>
'MatthiasMullie\\Minify\\' =>
array (
0 => __DIR__ . '/..' . '/matthiasmullie/minify/src',
),
'LdapRecord\\' =>
'League\\OAuth2\\Client\\' =>
array (
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
),
'LdapRecord\\' =>
array (
0 => __DIR__ . '/..' . '/directorytree/ldaprecord/src',
),
'Illuminate\\Contracts\\' =>
'Illuminate\\Contracts\\' =>
array (
0 => __DIR__ . '/..' . '/illuminate/contracts',
),
'Html2Text\\' =>
'Html2Text\\' =>
array (
0 => __DIR__ . '/..' . '/soundasleep/html2text/src',
),
'Ddeboer\\Imap\\' =>
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'GuzzleHttp\\Promise\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
),
'GuzzleHttp\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
),
'Firebase\\JWT\\' =>
array (
0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
),
'Ddeboer\\Imap\\' =>
array (
0 => __DIR__ . '/..' . '/ddeboer/imap/src',
),
'Carbon\\' =>
'Carbon\\Doctrine\\' =>
array (
0 => __DIR__ . '/..' . '/carbonphp/carbon-doctrine-types/src/Carbon/Doctrine',
),
'Carbon\\' =>
array (
0 => __DIR__ . '/..' . '/nesbot/carbon/src/Carbon',
),
);
public static $prefixesPsr0 = array (
'O' =>
'O' =>
array (
'OAuth2' =>
'OAuth2' =>
array (
0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src',
),
@@ -187,11 +248,6 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'u2flib_server\\Error' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\RegisterRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\Registration' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\SignRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
'u2flib_server\\U2F' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
);
public static function getInitializer(ClassLoader $loader)

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'carbonphp/carbon-doctrine-types' => array(
'pretty_version' => '3.2.0',
'version' => '3.2.0.0',
'reference' => '18ba5ddfec8976260ead6e866180bd5d2f71aa1d',
'type' => 'library',
'install_path' => __DIR__ . '/../carbonphp/carbon-doctrine-types',
'aliases' => array(),
'dev_requirement' => false,
),
'ddeboer/imap' => array(
'pretty_version' => '1.13.1',
'version' => '1.13.1.0',
@@ -38,9 +47,9 @@
'dev_requirement' => false,
),
'directorytree/ldaprecord' => array(
'pretty_version' => 'v2.10.1',
'version' => '2.10.1.0',
'reference' => 'bf512d9af7a7b0e2ed7a666ab29cefdd027bee88',
'pretty_version' => 'v2.20.5',
'version' => '2.20.5.0',
'reference' => '5bd0a5a9d257cf1049ae83055dbba4c3479ddf16',
'type' => 'library',
'install_path' => __DIR__ . '/../directorytree/ldaprecord',
'aliases' => array(),
@@ -52,15 +61,60 @@
0 => '*',
),
),
'firebase/php-jwt' => array(
'pretty_version' => 'v6.5.0',
'version' => '6.5.0.0',
'reference' => 'e94e7353302b0c11ec3cfff7180cd0b1743975d2',
'type' => 'library',
'install_path' => __DIR__ . '/../firebase/php-jwt',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/guzzle' => array(
'pretty_version' => '7.5.0',
'version' => '7.5.0.0',
'reference' => 'b50a2a1251152e43f6a37f0fa053e730a67d25ba',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/promises' => array(
'pretty_version' => '1.5.2',
'version' => '1.5.2.0',
'reference' => 'b94b2807d85443f9719887892882d0329d1e2598',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/promises',
'aliases' => array(),
'dev_requirement' => false,
),
'guzzlehttp/psr7' => array(
'pretty_version' => '2.4.5',
'version' => '2.4.5.0',
'reference' => '0454e12ef0cd597ccd2adb036f7bda4e7fface66',
'type' => 'library',
'install_path' => __DIR__ . '/../guzzlehttp/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'illuminate/contracts' => array(
'pretty_version' => 'v9.3.0',
'version' => '9.3.0.0',
'reference' => 'bf4b3c254c49d28157645d01e4883b5951b1e1d0',
'pretty_version' => 'v10.44.0',
'version' => '10.44.0.0',
'reference' => '8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac',
'type' => 'library',
'install_path' => __DIR__ . '/../illuminate/contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'league/oauth2-client' => array(
'pretty_version' => '2.7.0',
'version' => '2.7.0.0',
'reference' => '160d6274b03562ebeb55ed18399281d8118b76c8',
'type' => 'library',
'install_path' => __DIR__ . '/../league/oauth2-client',
'aliases' => array(),
'dev_requirement' => false,
),
'matthiasmullie/minify' => array(
'pretty_version' => '1.3.66',
'version' => '1.3.66.0',
@@ -95,9 +149,9 @@
'dev_requirement' => false,
),
'nesbot/carbon' => array(
'pretty_version' => '2.57.0',
'version' => '2.57.0.0',
'reference' => '4a54375c21eea4811dbd1149fe6b246517554e78',
'pretty_version' => '2.72.3',
'version' => '2.72.3.0',
'reference' => '0c6fd108360c562f6e4fd1dedb8233b423e91c83',
'type' => 'library',
'install_path' => __DIR__ . '/../nesbot/carbon',
'aliases' => array(),
@@ -130,6 +184,21 @@
'aliases' => array(),
'dev_requirement' => false,
),
'psr/clock' => array(
'pretty_version' => '1.0.0',
'version' => '1.0.0.0',
'reference' => 'e41a24703d4560fd0acb709162f73b8adfc3aa0d',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/clock',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/clock-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/container' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
@@ -139,6 +208,51 @@
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-client',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-client-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '1.0.1',
'version' => '1.0.1.0',
'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/log' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
@@ -157,6 +271,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'robthree/twofactorauth' => array(
'pretty_version' => '1.8.1',
'version' => '1.8.1.0',
@@ -175,6 +298,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'stevenmaguire/oauth2-keycloak' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => '05ead6bb6bcd2b6f96dfae87c769dcd3e5f6129d',
'type' => 'library',
'install_path' => __DIR__ . '/../stevenmaguire/oauth2-keycloak',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
@@ -194,18 +326,18 @@
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '0abb51d2f102e00a4eefcf46ba7fec406d245825',
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.24.0',
'version' => '1.24.0.0',
'reference' => '57b712b08eddb97c762a8caa32c84e037892d2e9',
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
@@ -221,18 +353,18 @@
'dev_requirement' => false,
),
'symfony/translation' => array(
'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0',
'reference' => 'e69501c71107cc3146b32aaa45f4edd0c3427875',
'pretty_version' => 'v6.4.3',
'version' => '6.4.3.0',
'reference' => '637c51191b6b184184bbf98937702bcf554f7d04',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/translation-contracts' => array(
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'reference' => '1b6ea5a7442af5a12dba3dbd6d71034b5b234e77',
'pretty_version' => 'v3.4.1',
'version' => '3.4.1.0',
'reference' => '06450585bf65e978026bda220cdebca3f867fde7',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/translation-contracts',
'aliases' => array(),
@@ -245,18 +377,18 @@
),
),
'symfony/var-dumper' => array(
'pretty_version' => 'v6.0.5',
'version' => '6.0.5.0',
'reference' => '60d6a756d5f485df5e6e40b337334848f79f61ce',
'pretty_version' => 'v6.4.3',
'version' => '6.4.3.0',
'reference' => '0435a08f69125535336177c29d56af3abc1f69da',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(),
'dev_requirement' => false,
),
'tightenco/collect' => array(
'pretty_version' => 'v8.83.2',
'version' => '8.83.2.0',
'reference' => 'd9c66d586ec2d216d8a31283d73f8df1400cc722',
'pretty_version' => 'v9.52.7',
'version' => '9.52.7.0',
'reference' => 'b15143cd11fe01a700fcc449df61adc64452fa6d',
'type' => 'library',
'install_path' => __DIR__ . '/../tightenco/collect',
'aliases' => array(),
@@ -271,14 +403,5 @@
'aliases' => array(),
'dev_requirement' => false,
),
'yubico/u2flib-server' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '55d813acf68212ad2cadecde07551600d6971939',
'type' => 'library',
'install_path' => __DIR__ . '/../yubico/u2flib-server',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@@ -7,7 +7,10 @@ assignees: ''
---
<!-- Please update the below information with your environment. -->
<!--
Please update the below information with your environment.
Issues filed without the below information will be closed.
-->
**Environment:**
- LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA]
- PHP Version: [e.g. 7.3 / 7.4 / 8.0]

View File

@@ -11,7 +11,10 @@ assignees: ''
<!-- https://github.com/sponsors/stevebauman -->
<!-- Thank you for your understanding. -->
<!-- Please update the below information with your environment. -->
<!--
Please update the below information with your environment.
Issues filed without the below information will be closed.
-->
**Environment:**
- LDAP Server Type: [e.g. ActiveDirectory / OpenLDAP / FreeIPA]
- PHP Version: [e.g. 7.3 / 7.4 / 8.0]

View File

@@ -0,0 +1,62 @@
name: run-integration-tests
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
run-tests:
runs-on: ${{ matrix.os }}
services:
ldap:
image: osixia/openldap:1.4.0
env:
LDAP_TLS_VERIFY_CLIENT: try
LDAP_OPENLDAP_UID: 1000
LDAP_OPENLDAP_GID: 1000
LDAP_ORGANISATION: Local
LDAP_DOMAIN: local.com
LDAP_ADMIN_PASSWORD: secret
ports:
- 389:389
- 636:636
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.1, 8.0, 7.4]
name: ${{ matrix.os }} - P${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
- name: Set ldap.conf file permissions
run: sudo chown -R $USER:$USER /etc/ldap/ldap.conf
- name: Create ldap.conf file disabling TLS verification
run: sudo echo "TLS_REQCERT never" > "/etc/ldap/ldap.conf"
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ldap, json
coverage: none
- name: Install dependencies
run: composer update --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/phpunit --testsuite Integration

View File

@@ -9,20 +9,20 @@ on:
jobs:
run-tests:
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} - P${{ matrix.php }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.1, 8.0, 7.4, 7.3]
name: ${{ matrix.os }} - P${{ matrix.php }}
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v3
uses: actions/cache@v2
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
@@ -38,41 +38,4 @@ jobs:
run: composer update --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/phpunit
run-analysis:
runs-on: ${{ matrix.os }}
name: Static code analysis (PHP ${{ matrix.php }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [8.0]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ldap, json
coverage: none
tools: psalm
- name: Validate composer.json
run: composer validate
- name: Install dependencies
run: composer update --prefer-dist --no-interaction
- name: Run Psalm
run: psalm
run: vendor/bin/phpunit --testsuite Unit

View File

@@ -1,8 +1 @@
preset: laravel
enabled:
- phpdoc_align
- phpdoc_separation
- unalign_double_arrow
disabled:
- laravel_phpdoc_alignment
- laravel_phpdoc_separation

View File

@@ -32,11 +32,12 @@
"php": ">=7.3",
"ext-ldap": "*",
"ext-json": "*",
"psr/log": "*",
"psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^1.0|^2.0",
"nesbot/carbon": "^1.0|^2.0",
"tightenco/collect": "^5.6|^6.0|^7.0|^8.0",
"illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0"
"tightenco/collect": "^5.6|^6.0|^7.0|^8.0|^9.0",
"illuminate/contracts": "^5.0|^6.0|^7.0|^8.0|^9.0|^10.0",
"symfony/polyfill-php80": "^1.25"
},
"require-dev": {
"phpunit/phpunit": "^9.0",

View File

@@ -0,0 +1,35 @@
version: '3'
services:
ldap:
image: osixia/openldap:1.4.0
container_name: ldap
restart: always
hostname: local.com
environment:
LDAP_TLS_VERIFY_CLIENT: try
LDAP_OPENLDAP_UID: 1000
LDAP_OPENLDAP_GID: 1000
LDAP_ORGANISATION: Local
LDAP_DOMAIN : local.com
LDAP_ADMIN_PASSWORD: secret
ports:
- "389:389"
- "636:636"
networks:
- local
ldapadmin:
image: osixia/phpldapadmin:0.9.0
container_name: ldapadmin
environment:
PHPLDAPADMIN_LDAP_HOSTS: ldap
restart: always
ports:
- "6443:443"
networks:
- local
networks:
local:
driver: bridge

View File

@@ -10,8 +10,11 @@
stopOnFailure="false"
>
<testsuites>
<testsuite name="LdapRecord Test Suite">
<directory suffix="Test.php">./tests/</directory>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@@ -6,7 +6,7 @@
<p align="center">
<a href="https://github.com/DirectoryTree/LdapRecord/actions">
<img src="https://img.shields.io/github/workflow/status/directorytree/ldaprecord/run-tests.svg?style=flat-square">
<img src="https://img.shields.io/github/actions/workflow/status/directorytree/ldaprecord/run-tests.yml?branch=master&style=flat-square">
</a>
<a href="https://scrutinizer-ci.com/g/DirectoryTree/LdapRecord/?branch=master">
<img src="https://img.shields.io/scrutinizer/g/DirectoryTree/LdapRecord/master.svg?style=flat-square"/>
@@ -45,7 +45,7 @@
**Up and Running Fast**
Connect to your LDAP servers and start running queries at lightning speed.
Connect to your LDAP servers and start running queries in a matter of minutes.
💡 **Fluent Filter Builder**

View File

@@ -30,9 +30,9 @@ abstract class Event
/**
* Constructor.
*
* @param LdapInterface $connection
* @param string $username
* @param string $password
* @param LdapInterface $connection
* @param string $username
* @param string $password
*/
public function __construct(LdapInterface $connection, $username, $password)
{

View File

@@ -38,8 +38,8 @@ class Guard
/**
* Constructor.
*
* @param LdapInterface $connection
* @param DomainConfiguration $configuration
* @param LdapInterface $connection
* @param DomainConfiguration $configuration
*/
public function __construct(LdapInterface $connection, DomainConfiguration $configuration)
{
@@ -50,10 +50,9 @@ class Guard
/**
* Attempt binding a user to the LDAP server.
*
* @param string $username
* @param string $password
* @param bool $stayBound
*
* @param string $username
* @param string $password
* @param bool $stayBound
* @return bool
*
* @throws UsernameRequiredException
@@ -90,8 +89,8 @@ class Guard
/**
* Attempt binding a user to the LDAP server. Supports anonymous binding.
*
* @param string|null $username
* @param string|null $password
* @param string|null $username
* @param string|null $password
*
* @throws BindException
* @throws \LdapRecord\ConnectionException
@@ -148,8 +147,7 @@ class Guard
/**
* Set the event dispatcher instance.
*
* @param DispatcherInterface $dispatcher
*
* @param DispatcherInterface $dispatcher
* @return void
*/
public function setDispatcher(DispatcherInterface $dispatcher)
@@ -160,9 +158,8 @@ class Guard
/**
* Fire the attempting event.
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return void
*/
protected function fireAttemptingEvent($username, $password)
@@ -175,9 +172,8 @@ class Guard
/**
* Fire the passed event.
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return void
*/
protected function firePassedEvent($username, $password)
@@ -190,9 +186,8 @@ class Guard
/**
* Fire the failed event.
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return void
*/
protected function fireFailedEvent($username, $password)
@@ -205,9 +200,8 @@ class Guard
/**
* Fire the binding event.
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return void
*/
protected function fireBindingEvent($username, $password)
@@ -220,9 +214,8 @@ class Guard
/**
* Fire the bound event.
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return void
*/
protected function fireBoundEvent($username, $password)

View File

@@ -58,7 +58,7 @@ class DomainConfiguration
/**
* Constructor.
*
* @param array $options
* @param array $options
*
* @throws ConfigurationException When an option value given is an invalid type.
*/
@@ -74,9 +74,8 @@ class DomainConfiguration
/**
* Extend the configuration with a custom option, or override an existing.
*
* @param string $option
* @param mixed $default
*
* @param string $option
* @param mixed $default
* @return void
*/
public static function extend($option, $default = null)
@@ -107,8 +106,8 @@ class DomainConfiguration
/**
* Set a configuration option.
*
* @param string $key
* @param mixed $value
* @param string $key
* @param mixed $value
*
* @throws ConfigurationException When an option value given is an invalid type.
*/
@@ -122,8 +121,7 @@ class DomainConfiguration
/**
* Returns the value for the specified configuration options.
*
* @param string $key
*
* @param string $key
* @return mixed
*
* @throws ConfigurationException When the option specified does not exist.
@@ -140,8 +138,7 @@ class DomainConfiguration
/**
* Checks if a configuration option exists.
*
* @param string $key
*
* @param string $key
* @return bool
*/
public function has($key)
@@ -152,9 +149,8 @@ class DomainConfiguration
/**
* Validate the configuration option.
*
* @param string $key
* @param mixed $value
*
* @param string $key
* @param mixed $value
* @return bool
*
* @throws ConfigurationException When an option value given is an invalid type.

View File

@@ -30,8 +30,8 @@ abstract class Validator
/**
* Constructor.
*
* @param string $key
* @param mixed $value
* @param string $key
* @param mixed $value
*/
public function __construct($key, $value)
{

View File

@@ -88,8 +88,8 @@ class Connection
/**
* Constructor.
*
* @param array $config
* @param LdapInterface|null $ldap
* @param array|DomainConfiguration $config
* @param LdapInterface|null $ldap
*/
public function __construct($config = [], LdapInterface $ldap = null)
{
@@ -109,15 +109,18 @@ class Connection
/**
* Set the connection configuration.
*
* @param array $config
*
* @param array|DomainConfiguration $config
* @return $this
*
* @throws Configuration\ConfigurationException
*/
public function setConfiguration($config = [])
{
$this->configuration = new DomainConfiguration($config);
if (! $config instanceof DomainConfiguration) {
$config = new DomainConfiguration($config);
}
$this->configuration = $config;
$this->hosts = $this->configuration->get('hosts');
@@ -129,8 +132,7 @@ class Connection
/**
* Set the LDAP connection.
*
* @param LdapInterface $ldap
*
* @param LdapInterface $ldap
* @return $this
*/
public function setLdapConnection(LdapInterface $ldap)
@@ -143,8 +145,7 @@ class Connection
/**
* Set the event dispatcher.
*
* @param DispatcherInterface $dispatcher
*
* @param DispatcherInterface $dispatcher
* @return $this
*/
public function setDispatcher(DispatcherInterface $dispatcher)
@@ -192,8 +193,7 @@ class Connection
/**
* Set the cache store.
*
* @param CacheInterface $store
*
* @param CacheInterface $store
* @return $this
*/
public function setCache(CacheInterface $store)
@@ -238,9 +238,8 @@ class Connection
*
* If no username or password is specified, then the configured credentials are used.
*
* @param string|null $username
* @param string|null $password
*
* @param string|null $username
* @param string|null $password
* @return Connection
*
* @throws Auth\BindException
@@ -298,6 +297,16 @@ class Connection
$this->initialize();
}
/**
* Clone the connection.
*
* @return static
*/
public function replicate()
{
return new static($this->configuration, new $this->ldap);
}
/**
* Disconnect from the LDAP server.
*
@@ -311,8 +320,7 @@ class Connection
/**
* Dispatch an event.
*
* @param object $event
*
* @param object $event
* @return void
*/
public function dispatch($event)
@@ -335,8 +343,7 @@ class Connection
/**
* Perform the operation on the LDAP connection.
*
* @param Closure $operation
*
* @param Closure $operation
* @return mixed
*/
public function run(Closure $operation)
@@ -359,11 +366,27 @@ class Connection
}
}
/**
* Perform the operation on an isolated LDAP connection.
*
* @param Closure $operation
* @return mixed
*/
public function isolate(Closure $operation)
{
$connection = $this->replicate();
try {
return $operation($connection);
} finally {
$connection->disconnect();
}
}
/**
* Attempt to get an exception for the cause of failure.
*
* @param LdapRecordException $e
*
* @param LdapRecordException $e
* @return mixed
*/
protected function getExceptionForCauseOfFailure(LdapRecordException $e)
@@ -383,8 +406,7 @@ class Connection
/**
* Run the operation callback on the current LDAP connection.
*
* @param Closure $operation
*
* @param Closure $operation
* @return mixed
*
* @throws LdapRecordException
@@ -439,9 +461,8 @@ class Connection
/**
* Attempt to retry an LDAP operation if due to a lost connection.
*
* @param LdapRecordException $e
* @param Closure $operation
*
* @param LdapRecordException $e
* @param Closure $operation
* @return mixed
*
* @throws LdapRecordException
@@ -461,8 +482,7 @@ class Connection
/**
* Retry the operation on the current host.
*
* @param Closure $operation
*
* @param Closure $operation
* @return mixed
*
* @throws LdapRecordException
@@ -483,9 +503,8 @@ class Connection
/**
* Attempt the operation again on the next host.
*
* @param LdapRecordException $e
* @param Closure $operation
*
* @param LdapRecordException $e
* @param Closure $operation
* @return mixed
*
* @throws LdapRecordException

View File

@@ -81,9 +81,8 @@ class ConnectionManager
/**
* Forward missing method calls onto the instance.
*
* @param string $method
* @param mixed $args
*
* @param string $method
* @param mixed $args
* @return mixed
*/
public function __call($method, $args)
@@ -104,9 +103,8 @@ class ConnectionManager
/**
* Add a new connection.
*
* @param Connection $connection
* @param string|null $name
*
* @param Connection $connection
* @param string|null $name
* @return $this
*/
public function add(Connection $connection, $name = null)
@@ -123,8 +121,7 @@ class ConnectionManager
/**
* Remove a connection.
*
* @param $name
*
* @param $name
* @return $this
*/
public function remove($name)
@@ -147,8 +144,7 @@ class ConnectionManager
/**
* Get a connection by name or return the default.
*
* @param string|null $name
*
* @param string|null $name
* @return Connection
*
* @throws ContainerException If the given connection does not exist.
@@ -185,8 +181,7 @@ class ConnectionManager
/**
* Checks if the connection exists.
*
* @param string $name
*
* @param string $name
* @return bool
*/
public function exists($name)
@@ -197,8 +192,7 @@ class ConnectionManager
/**
* Set the default connection name.
*
* @param string $name
*
* @param string $name
* @return $this
*/
public function setDefault($name = null)
@@ -237,8 +231,7 @@ class ConnectionManager
/**
* Set the event logger to use.
*
* @param LoggerInterface $logger
*
* @param LoggerInterface $logger
* @return void
*/
public function setLogger(LoggerInterface $logger)
@@ -299,8 +292,7 @@ class ConnectionManager
/**
* Set the event dispatcher.
*
* @param DispatcherInterface $dispatcher
*
* @param DispatcherInterface $dispatcher
* @return void
*/
public function setDispatcher(DispatcherInterface $dispatcher)

View File

@@ -48,9 +48,8 @@ class Container
/**
* Forward missing static calls onto the current instance.
*
* @param string $method
* @param mixed $args
*
* @param string $method
* @param mixed $args
* @return mixed
*/
public static function __callStatic($method, $args)
@@ -71,8 +70,7 @@ class Container
/**
* Set the container instance.
*
* @param Container|null $container
*
* @param Container|null $container
* @return Container|null
*/
public static function setInstance(self $container = null)
@@ -103,9 +101,8 @@ class Container
/**
* Forward missing method calls onto the connection manager.
*
* @param string $method
* @param mixed $args
*
* @param string $method
* @param mixed $args
* @return mixed
*/
public function __call($method, $args)

View File

@@ -28,9 +28,9 @@ class DetailedError
/**
* Constructor.
*
* @param int $errorCode
* @param string $errorMessage
* @param string $diagnosticMessage
* @param int $errorCode
* @param string $errorMessage
* @param string $diagnosticMessage
*/
public function __construct($errorCode, $errorMessage, $diagnosticMessage)
{

View File

@@ -7,8 +7,7 @@ trait DetectsErrors
/**
* Determine if the error was caused by a lost connection.
*
* @param string $error
*
* @param string $error
* @return bool
*/
protected function causedByLostConnection($error)
@@ -19,8 +18,7 @@ trait DetectsErrors
/**
* Determine if the error was caused by lack of pagination support.
*
* @param string $error
*
* @param string $error
* @return bool
*/
protected function causedByPaginationSupport($error)
@@ -31,8 +29,7 @@ trait DetectsErrors
/**
* Determine if the error was caused by a size limit warning.
*
* @param $error
*
* @param $error
* @return bool
*/
protected function causedBySizeLimit($error)
@@ -43,8 +40,7 @@ trait DetectsErrors
/**
* Determine if the error was caused by a "No such object" warning.
*
* @param string $error
*
* @param string $error
* @return bool
*/
protected function causedByNoSuchObject($error)
@@ -55,15 +51,14 @@ trait DetectsErrors
/**
* Determine if the error contains the any of the messages.
*
* @param string $error
* @param string|array $messages
*
* @param string $error
* @param string|array $messages
* @return bool
*/
protected function errorContainsMessage($error, $messages = [])
{
foreach ((array) $messages as $message) {
if (strpos($error, $message) !== false) {
if (str_contains((string) $error, $message)) {
return true;
}
}

View File

@@ -9,10 +9,9 @@ trait EscapesValues
/**
* Prepare a value to be escaped.
*
* @param string $value
* @param string $ignore
* @param int $flags
*
* @param string $value
* @param string $ignore
* @param int $flags
* @return EscapedValue
*/
public function escape($value, $ignore = '', $flags = 0)

View File

@@ -16,7 +16,7 @@ abstract class ConnectionEvent
/**
* Constructor.
*
* @param Connection $connection
* @param Connection $connection
*/
public function __construct(Connection $connection)
{

View File

@@ -46,7 +46,7 @@ class Dispatcher implements DispatcherInterface
public function listen($events, $listener)
{
foreach ((array) $events as $event) {
if (strpos($event, '*') !== false) {
if (str_contains((string) $event, '*')) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][] = $this->makeListener($listener);
@@ -57,9 +57,8 @@ class Dispatcher implements DispatcherInterface
/**
* Setup a wildcard listener callback.
*
* @param string $event
* @param mixed $listener
*
* @param string $event
* @param mixed $listener
* @return void
*/
protected function setupWildcardListen($event, $listener)
@@ -134,9 +133,8 @@ class Dispatcher implements DispatcherInterface
/**
* Parse the given event and payload and prepare them for dispatching.
*
* @param mixed $event
* @param mixed $payload
*
* @param mixed $event
* @param mixed $payload
* @return array
*/
protected function parseEventAndPayload($event, $payload)
@@ -168,8 +166,7 @@ class Dispatcher implements DispatcherInterface
/**
* Get the wildcard listeners for the event.
*
* @param string $eventName
*
* @param string $eventName
* @return array
*/
protected function getWildcardListeners($eventName)
@@ -190,9 +187,8 @@ class Dispatcher implements DispatcherInterface
*
* This function is a direct excerpt from Laravel's Str::is().
*
* @param string $wildcard
* @param string $eventName
*
* @param string $wildcard
* @param string $eventName
* @return bool
*/
protected function wildcardContainsEvent($wildcard, $eventName)
@@ -229,9 +225,8 @@ class Dispatcher implements DispatcherInterface
/**
* Add the listeners for the event's interfaces to the given array.
*
* @param string $eventName
* @param array $listeners
*
* @param string $eventName
* @param array $listeners
* @return array
*/
protected function addInterfaceListeners($eventName, array $listeners = [])
@@ -250,9 +245,8 @@ class Dispatcher implements DispatcherInterface
/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string $listener
* @param bool $wildcard
*
* @param \Closure|string $listener
* @param bool $wildcard
* @return \Closure
*/
public function makeListener($listener, $wildcard = false)
@@ -273,9 +267,8 @@ class Dispatcher implements DispatcherInterface
/**
* Create a class based listener.
*
* @param string $listener
* @param bool $wildcard
*
* @param string $listener
* @param bool $wildcard
* @return \Closure
*/
protected function createClassListener($listener, $wildcard = false)
@@ -295,8 +288,7 @@ class Dispatcher implements DispatcherInterface
/**
* Create the class based event callable.
*
* @param string $listener
*
* @param string $listener
* @return callable
*/
protected function createClassCallable($listener)
@@ -309,13 +301,12 @@ class Dispatcher implements DispatcherInterface
/**
* Parse the class listener into class and method.
*
* @param string $listener
*
* @param string $listener
* @return array
*/
protected function parseListenerCallback($listener)
{
return strpos($listener, '@') !== false
return str_contains((string) $listener, '@')
? explode('@', $listener, 2)
: [$listener, 'handle'];
}
@@ -325,7 +316,7 @@ class Dispatcher implements DispatcherInterface
*/
public function forget($event)
{
if (strpos($event, '*') !== false) {
if (str_contains((string) $event, '*')) {
unset($this->wildcards[$event]);
} else {
unset($this->listeners[$event]);

View File

@@ -7,9 +7,8 @@ interface DispatcherInterface
/**
* Register an event listener with the dispatcher.
*
* @param string|array $events
* @param mixed $listener
*
* @param string|array $events
* @param mixed $listener
* @return void
*/
public function listen($events, $listener);
@@ -17,8 +16,7 @@ interface DispatcherInterface
/**
* Determine if a given event has listeners.
*
* @param string $eventName
*
* @param string $eventName
* @return bool
*/
public function hasListeners($eventName);
@@ -26,9 +24,8 @@ interface DispatcherInterface
/**
* Fire an event until the first non-null response is returned.
*
* @param string|object $event
* @param mixed $payload
*
* @param string|object $event
* @param mixed $payload
* @return array|null
*/
public function until($event, $payload = []);
@@ -36,10 +33,9 @@ interface DispatcherInterface
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return mixed
*/
public function fire($event, $payload = [], $halt = false);
@@ -47,10 +43,9 @@ interface DispatcherInterface
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false);
@@ -58,8 +53,7 @@ interface DispatcherInterface
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
*
* @param string $eventName
* @return array
*/
public function getListeners($eventName);
@@ -67,8 +61,7 @@ interface DispatcherInterface
/**
* Remove a set of listeners from the dispatcher.
*
* @param string $event
*
* @param string $event
* @return void
*/
public function forget($event);

View File

@@ -21,7 +21,7 @@ class Logger
/**
* Constructor.
*
* @param LoggerInterface|null $logger
* @param LoggerInterface|null $logger
*/
public function __construct(LoggerInterface $logger = null)
{
@@ -31,8 +31,7 @@ class Logger
/**
* Logs the given event.
*
* @param mixed $event
*
* @param mixed $event
* @return void
*/
public function log($event)
@@ -53,8 +52,7 @@ class Logger
/**
* Logs an authentication event.
*
* @param AuthEvent $event
*
* @param AuthEvent $event
* @return void
*/
public function auth(AuthEvent $event)
@@ -81,8 +79,7 @@ class Logger
/**
* Logs a model event.
*
* @param ModelEvent $event
*
* @param ModelEvent $event
* @return void
*/
public function model(ModelEvent $event)
@@ -106,8 +103,7 @@ class Logger
/**
* Logs a query event.
*
* @param QueryEvent $event
*
* @param QueryEvent $event
* @return void
*/
public function query(QueryEvent $event)
@@ -133,8 +129,7 @@ class Logger
/**
* Returns the operational name of the given event.
*
* @param mixed $event
*
* @param mixed $event
* @return string
*/
protected function getOperationName($event)

View File

@@ -14,7 +14,7 @@ class NullDispatcher implements DispatcherInterface
/**
* Constructor.
*
* @param DispatcherInterface $dispatcher
* @param DispatcherInterface $dispatcher
*/
public function __construct(DispatcherInterface $dispatcher)
{
@@ -24,9 +24,8 @@ class NullDispatcher implements DispatcherInterface
/**
* Register an event listener with the dispatcher.
*
* @param string|array $events
* @param mixed $listener
*
* @param string|array $events
* @param mixed $listener
* @return void
*/
public function listen($events, $listener)
@@ -37,8 +36,7 @@ class NullDispatcher implements DispatcherInterface
/**
* Determine if a given event has listeners.
*
* @param string $eventName
*
* @param string $eventName
* @return bool
*/
public function hasListeners($eventName)
@@ -49,9 +47,8 @@ class NullDispatcher implements DispatcherInterface
/**
* Fire an event until the first non-null response is returned.
*
* @param string|object $event
* @param mixed $payload
*
* @param string|object $event
* @param mixed $payload
* @return null
*/
public function until($event, $payload = [])
@@ -62,10 +59,9 @@ class NullDispatcher implements DispatcherInterface
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return null
*/
public function fire($event, $payload = [], $halt = false)
@@ -76,10 +72,9 @@ class NullDispatcher implements DispatcherInterface
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
* @return null
*/
public function dispatch($event, $payload = [], $halt = false)
@@ -90,8 +85,7 @@ class NullDispatcher implements DispatcherInterface
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
*
* @param string $eventName
* @return array
*/
public function getListeners($eventName)
@@ -102,8 +96,7 @@ class NullDispatcher implements DispatcherInterface
/**
* Remove a set of listeners from the dispatcher.
*
* @param string $event
*
* @param string $event
* @return void
*/
public function forget($event)

View File

@@ -148,8 +148,7 @@ trait HandlesConnection
/**
* Convert warnings to exceptions for the given operation.
*
* @param Closure $operation
*
* @param Closure $operation
* @return mixed
*
* @throws LdapRecordException
@@ -190,8 +189,7 @@ trait HandlesConnection
/**
* Determine if the failed operation should be bypassed.
*
* @param string $method
*
* @param string $method
* @return bool
*/
protected function shouldBypassFailure($method)
@@ -202,8 +200,7 @@ trait HandlesConnection
/**
* Determine if the error should be bypassed.
*
* @param string $error
*
* @param string $error
* @return bool
*/
protected function shouldBypassError($error)
@@ -226,9 +223,8 @@ trait HandlesConnection
/**
* Generates an LDAP connection string for each host given.
*
* @param string|array $hosts
* @param string $port
*
* @param string|array $hosts
* @param string $port
* @return string
*/
protected function makeConnectionUris($hosts, $port)
@@ -249,9 +245,8 @@ trait HandlesConnection
/**
* Assemble the host URI strings.
*
* @param array|string $hosts
* @param string $port
*
* @param array|string $hosts
* @param string $port
* @return array
*/
protected function assembleHostUris($hosts, $port)

View File

@@ -4,7 +4,6 @@ namespace LdapRecord;
use LDAP\Connection as RawLdapConnection;
/** @psalm-suppress UndefinedClass */
class Ldap implements LdapInterface
{
use HandlesConnection, DetectsErrors;
@@ -24,8 +23,7 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-first-entry.php
*
* @param resource $searchResults
*
* @param resource $searchResults
* @return resource
*/
public function getFirstEntry($searchResults)
@@ -40,8 +38,7 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-next-entry.php
*
* @param resource $entry
*
* @param resource $entry
* @return resource
*/
public function getNextEntry($entry)
@@ -56,8 +53,7 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-get-attributes.php
*
* @param resource $entry
*
* @param resource $entry
* @return array|false
*/
public function getAttributes($entry)
@@ -72,8 +68,7 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-count-entries.php
*
* @param resource $searchResults
*
* @param resource $searchResults
* @return int
*/
public function countEntries($searchResults)
@@ -88,10 +83,9 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-compare.php
*
* @param string $dn
* @param string $attribute
* @param string $value
*
* @param string $dn
* @param string $attribute
* @param string $value
* @return mixed
*/
public function compare($dn, $attribute, $value)
@@ -132,9 +126,8 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-get-values-len.php
*
* @param $entry
* @param $attribute
*
* @param $entry
* @param $attribute
* @return array
*/
public function getValuesLen($entry, $attribute)
@@ -167,8 +160,7 @@ class Ldap implements LdapInterface
*
* @see http://php.net/manual/en/function.ldap-set-rebind-proc.php
*
* @param callable $callback
*
* @param callable $callback
* @return bool
*/
public function setRebindCallback(callable $callback)
@@ -304,7 +296,7 @@ class Ldap implements LdapInterface
public function bind($username, $password)
{
return $this->bound = $this->executeFailableOperation(function () use ($username, $password) {
return ldap_bind($this->connection, $username, html_entity_decode($password));
return ldap_bind($this->connection, $username, $password ? html_entity_decode($password) : null);
});
}
@@ -462,8 +454,7 @@ class Ldap implements LdapInterface
/**
* Extract the diagnostic code from the message.
*
* @param string $message
*
* @param string $message
* @return string|bool
*/
public function extractDiagnosticCode($message)

View File

@@ -9,28 +9,28 @@ interface LdapInterface
*
* @var string
*/
const PROTOCOL_SSL = 'ldaps://';
public const PROTOCOL_SSL = 'ldaps://';
/**
* The standard LDAP protocol string.
*
* @var string
*/
const PROTOCOL = 'ldap://';
public const PROTOCOL = 'ldap://';
/**
* The LDAP SSL port number.
*
* @var string
*/
const PORT_SSL = 636;
public const PORT_SSL = 636;
/**
* The standard LDAP port number.
*
* @var string
*/
const PORT = 389;
public const PORT = 389;
/**
* Various useful server control OID's.
@@ -38,37 +38,36 @@ interface LdapInterface
* @see https://ldap.com/ldap-oid-reference-guide/
* @see http://msdn.microsoft.com/en-us/library/cc223359.aspx
*/
const OID_SERVER_START_TLS = '1.3.6.1.4.1.1466.20037';
const OID_SERVER_PAGED_RESULTS = '1.2.840.113556.1.4.319';
const OID_SERVER_SHOW_DELETED = '1.2.840.113556.1.4.417';
const OID_SERVER_SORT = '1.2.840.113556.1.4.473';
const OID_SERVER_CROSSDOM_MOVE_TARGET = '1.2.840.113556.1.4.521';
const OID_SERVER_NOTIFICATION = '1.2.840.113556.1.4.528';
const OID_SERVER_EXTENDED_DN = '1.2.840.113556.1.4.529';
const OID_SERVER_LAZY_COMMIT = '1.2.840.113556.1.4.619';
const OID_SERVER_SD_FLAGS = '1.2.840.113556.1.4.801';
const OID_SERVER_TREE_DELETE = '1.2.840.113556.1.4.805';
const OID_SERVER_DIRSYNC = '1.2.840.113556.1.4.841';
const OID_SERVER_VERIFY_NAME = '1.2.840.113556.1.4.1338';
const OID_SERVER_DOMAIN_SCOPE = '1.2.840.113556.1.4.1339';
const OID_SERVER_SEARCH_OPTIONS = '1.2.840.113556.1.4.1340';
const OID_SERVER_PERMISSIVE_MODIFY = '1.2.840.113556.1.4.1413';
const OID_SERVER_ASQ = '1.2.840.113556.1.4.1504';
const OID_SERVER_FAST_BIND = '1.2.840.113556.1.4.1781';
const OID_SERVER_CONTROL_VLVREQUEST = '2.16.840.1.113730.3.4.9';
public const OID_SERVER_START_TLS = '1.3.6.1.4.1.1466.20037';
public const OID_SERVER_PAGED_RESULTS = '1.2.840.113556.1.4.319';
public const OID_SERVER_SHOW_DELETED = '1.2.840.113556.1.4.417';
public const OID_SERVER_SORT = '1.2.840.113556.1.4.473';
public const OID_SERVER_CROSSDOM_MOVE_TARGET = '1.2.840.113556.1.4.521';
public const OID_SERVER_NOTIFICATION = '1.2.840.113556.1.4.528';
public const OID_SERVER_EXTENDED_DN = '1.2.840.113556.1.4.529';
public const OID_SERVER_LAZY_COMMIT = '1.2.840.113556.1.4.619';
public const OID_SERVER_SD_FLAGS = '1.2.840.113556.1.4.801';
public const OID_SERVER_TREE_DELETE = '1.2.840.113556.1.4.805';
public const OID_SERVER_DIRSYNC = '1.2.840.113556.1.4.841';
public const OID_SERVER_VERIFY_NAME = '1.2.840.113556.1.4.1338';
public const OID_SERVER_DOMAIN_SCOPE = '1.2.840.113556.1.4.1339';
public const OID_SERVER_SEARCH_OPTIONS = '1.2.840.113556.1.4.1340';
public const OID_SERVER_PERMISSIVE_MODIFY = '1.2.840.113556.1.4.1413';
public const OID_SERVER_ASQ = '1.2.840.113556.1.4.1504';
public const OID_SERVER_FAST_BIND = '1.2.840.113556.1.4.1781';
public const OID_SERVER_CONTROL_VLVREQUEST = '2.16.840.1.113730.3.4.9';
/**
* Query OID's.
*
* @see https://ldapwiki.com/wiki/LDAP_MATCHING_RULE_IN_CHAIN
*/
const OID_MATCHING_RULE_IN_CHAIN = '1.2.840.113556.1.4.1941';
public const OID_MATCHING_RULE_IN_CHAIN = '1.2.840.113556.1.4.1941';
/**
* Set the current connection to use SSL.
*
* @param bool $enabled
*
* @param bool $enabled
* @return $this
*/
public function ssl();
@@ -83,8 +82,7 @@ interface LdapInterface
/**
* Set the current connection to use TLS.
*
* @param bool $enabled
*
* @param bool $enabled
* @return $this
*/
public function tls();
@@ -138,8 +136,7 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-get-entries.php
*
* @param resource $searchResults
*
* @param resource $searchResults
* @return array
*/
public function getEntries($searchResults);
@@ -169,9 +166,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-set-option.php
*
* @param int $option
* @param mixed $value
*
* @param int $option
* @param mixed $value
* @return bool
*/
public function setOption($option, $value);
@@ -179,8 +175,7 @@ interface LdapInterface
/**
* Set options on the current connection.
*
* @param array $options
*
* @param array $options
* @return void
*/
public function setOptions(array $options = []);
@@ -190,9 +185,8 @@ interface LdapInterface
*
* @see https://www.php.net/manual/en/function.ldap-get-option.php
*
* @param int $option
* @param mixed $value
*
* @param int $option
* @param mixed $value
* @return mixed
*/
public function getOption($option, &$value = null);
@@ -213,9 +207,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-start-tls.php
*
* @param string|array $hosts
* @param int $port
*
* @param string|array $hosts
* @param int $port
* @return resource|false
*/
public function connect($hosts = [], $port = 389);
@@ -236,15 +229,14 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-search.php
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
* @return resource
*/
public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []);
@@ -254,15 +246,14 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-list.php
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
* @return resource
*/
public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []);
@@ -272,15 +263,14 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-read.php
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
*
* @param string $dn
* @param string $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
* @param int $deref
* @param array $serverControls
* @return resource
*/
public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0, $deref = LDAP_DEREF_NEVER, $serverControls = []);
@@ -290,13 +280,12 @@ interface LdapInterface
*
* @see https://www.php.net/manual/en/function.ldap-parse-result.php
*
* @param resource $result
* @param int $errorCode
* @param ?string $dn
* @param ?string $errorMessage
* @param ?array $referrals
* @param ?array $serverControls
*
* @param resource $result
* @param int $errorCode
* @param ?string $dn
* @param ?string $errorMessage
* @param ?array $referrals
* @param ?array $serverControls
* @return bool
*/
public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = []);
@@ -307,9 +296,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-bind.php
*
* @param string $username
* @param string $password
*
* @param string $username
* @param string $password
* @return bool
*
* @throws LdapRecordException
@@ -321,9 +309,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-add.php
*
* @param string $dn
* @param array $entry
*
* @param string $dn
* @param array $entry
* @return bool
*
* @throws LdapRecordException
@@ -335,8 +322,7 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-delete.php
*
* @param string $dn
*
* @param string $dn
* @return bool
*
* @throws LdapRecordException
@@ -348,11 +334,10 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-rename.php
*
* @param string $dn
* @param string $newRdn
* @param string $newParent
* @param bool $deleteOldRdn
*
* @param string $dn
* @param string $newRdn
* @param string $newParent
* @param bool $deleteOldRdn
* @return bool
*
* @throws LdapRecordException
@@ -364,9 +349,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-modify.php
*
* @param string $dn
* @param array $entry
*
* @param string $dn
* @param array $entry
* @return bool
*
* @throws LdapRecordException
@@ -378,9 +362,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-modify-batch.php
*
* @param string $dn
* @param array $values
*
* @param string $dn
* @param array $values
* @return bool
*
* @throws LdapRecordException
@@ -392,9 +375,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-mod-add.php
*
* @param string $dn
* @param array $entry
*
* @param string $dn
* @param array $entry
* @return bool
*
* @throws LdapRecordException
@@ -406,9 +388,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-mod-replace.php
*
* @param string $dn
* @param array $entry
*
* @param string $dn
* @param array $entry
* @return bool
*
* @throws LdapRecordException
@@ -420,9 +401,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-mod-del.php
*
* @param string $dn
* @param array $entry
*
* @param string $dn
* @param array $entry
* @return bool
*
* @throws LdapRecordException
@@ -434,10 +414,9 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-control-paged-result.php
*
* @param int $pageSize
* @param bool $isCritical
* @param string $cookie
*
* @param int $pageSize
* @param bool $isCritical
* @param string $cookie
* @return bool
*/
public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '');
@@ -447,9 +426,8 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-control-paged-result-response.php
*
* @param resource $result
* @param string $cookie
*
* @param resource $result
* @param string $cookie
* @return bool
*/
public function controlPagedResultResponse($result, &$cookie);
@@ -459,8 +437,7 @@ interface LdapInterface
*
* @see https://www.php.net/manual/en/function.ldap-free-result.php
*
* @param resource $result
*
* @param resource $result
* @return bool
*/
public function freeResult($result);
@@ -479,8 +456,7 @@ interface LdapInterface
*
* @see http://php.net/manual/en/function.ldap-err2str.php
*
* @param int $number
*
* @param int $number
* @return string
*/
public function err2Str($number);

View File

@@ -16,9 +16,8 @@ class LdapRecordException extends Exception
/**
* Create a new Bind Exception with a detailed connection error.
*
* @param Exception $e
* @param DetailedError|null $error
*
* @param Exception $e
* @param DetailedError|null $error
* @return $this
*/
public static function withDetailedError(Exception $e, DetailedError $error = null)
@@ -29,8 +28,7 @@ class LdapRecordException extends Exception
/**
* Set the detailed error.
*
* @param DetailedError|null $error
*
* @param DetailedError|null $error
* @return $this
*/
public function setDetailedError(DetailedError $error = null)

View File

@@ -9,10 +9,9 @@ trait HasPrimaryGroup
/**
* Returns a new has one primary group relationship.
*
* @param mixed $related
* @param string $relationKey
* @param string $foreignKey
*
* @param mixed $related
* @param string $relationKey
* @param string $foreignKey
* @return HasOnePrimaryGroup
*/
public function hasOnePrimaryGroup($related, $relationKey, $foreignKey = 'primarygroupid')

View File

@@ -9,6 +9,7 @@ use LdapRecord\Models\Entry as BaseEntry;
use LdapRecord\Models\Events\Updated;
use LdapRecord\Models\Types\ActiveDirectory;
use LdapRecord\Query\Model\ActiveDirectoryBuilder;
use LdapRecord\Support\Arr;
/** @mixin ActiveDirectoryBuilder */
class Entry extends BaseEntry implements ActiveDirectory
@@ -50,20 +51,46 @@ class Entry extends BaseEntry implements ActiveDirectory
/**
* @inheritdoc
*/
public function getConvertedSid()
public function getConvertedSid($sid = null)
{
try {
return (string) new Sid($this->getObjectSid());
return (string) $this->newObjectSid(
$sid ?? $this->getObjectSid()
);
} catch (InvalidArgumentException $e) {
return;
}
}
/**
* @inheritdoc
*/
public function getBinarySid($sid = null)
{
try {
return $this->newObjectSid(
$sid ?? $this->getObjectSid()
)->getBinary();
} catch (InvalidArgumentException $e) {
return;
}
}
/**
* Make a new object Sid instance.
*
* @param string $value
* @return Sid
*/
protected function newObjectSid($value)
{
return new Sid($value);
}
/**
* Create a new query builder.
*
* @param Connection $connection
*
* @param Connection $connection
* @return ActiveDirectoryBuilder
*/
public function newQueryBuilder(Connection $connection)
@@ -84,8 +111,7 @@ class Entry extends BaseEntry implements ActiveDirectory
/**
* Restore a deleted object.
*
* @param string|null $newParentDn
*
* @param string|null $newParentDn
* @return bool
*
* @throws \LdapRecord\LdapRecordException
@@ -114,24 +140,6 @@ class Entry extends BaseEntry implements ActiveDirectory
$this->save(['isDeleted' => null]);
}
/**
* Get the RootDSE (AD schema) record from the directory.
*
* @param string|null $connection
*
* @return static
*
* @throws \LdapRecord\Models\ModelNotFoundException
*/
public static function getRootDse($connection = null)
{
return static::on($connection ?? (new static())->getConnectionName())
->in(null)
->read()
->whereHas('objectclass')
->firstOrFail();
}
/**
* Get the objects restore location.
*
@@ -143,22 +151,50 @@ class Entry extends BaseEntry implements ActiveDirectory
}
/**
* Converts attributes for JSON serialization.
*
* @param array $attributes
* Convert the attributes for JSON serialization.
*
* @param array $attributes
* @return array
*/
protected function convertAttributesForJson(array $attributes = [])
{
$attributes = parent::convertAttributesForJson($attributes);
if ($this->hasAttribute($this->sidKey)) {
// If the model has a SID set, we need to convert it due to it being in
// binary. Otherwise we will receive a JSON serialization exception.
return array_replace($attributes, [
$this->sidKey => [$this->getConvertedSid()],
]);
// If the model has a SID set, we need to convert it to its
// string format, due to it being in binary. Otherwise
// we will receive a JSON serialization exception.
if (isset($attributes[$this->sidKey])) {
$attributes[$this->sidKey] = [$this->getConvertedSid(
Arr::first($attributes[$this->sidKey])
)];
}
return $attributes;
}
/**
* Convert the attributes from JSON serialization.
*
* @param array $attributes
* @return array
*/
protected function convertAttributesFromJson(array $attributes = [])
{
$attributes = parent::convertAttributesFromJson($attributes);
// Here we are converting the model's GUID and SID attributes
// back to their original values from serialization, so that
// their original value may be used and compared against.
if (isset($attributes[$this->guidKey])) {
$attributes[$this->guidKey] = [$this->getBinaryGuid(
Arr::first($attributes[$this->guidKey])
)];
}
if (isset($attributes[$this->sidKey])) {
$attributes[$this->sidKey] = [$this->getBinarySid(
Arr::first($attributes[$this->sidKey])
)];
}
return $attributes;

View File

@@ -10,8 +10,7 @@ class HasOnePrimaryGroup extends HasOne
/**
* Get the foreign model by the given value.
*
* @param string $value
*
* @param string $value
* @return Model|null
*/
protected function getForeignModelByValue($value)
@@ -26,8 +25,7 @@ class HasOnePrimaryGroup extends HasOne
*
* Retrieves the last RID from the models Object SID.
*
* @param Model $model
*
* @param Model $model
* @return string
*/
protected function getForeignValueFromModel(Model $model)

View File

@@ -11,9 +11,8 @@ class HasServerRoleAttribute implements Scope
/**
* Includes condition of having a serverRole attribute.
*
* @param Builder $query
* @param Model $model
*
* @param Builder $query
* @param Model $model
* @return void
*/
public function apply(Builder $query, Model $model)

Some files were not shown because too many files have changed in this diff Show More