mailcow 集成 Adldap2 的 LDAP Contact 模型:创建、成员关系与 Schema 扩展实践 📅 发布时间:2026/9/15 16:16:27 👁 浏览次数: mailcow 集成 Adldap2 的 LDAP Contact 模型创建、成员关系与 Schema 扩展实践【免费下载链接】mailcow-dockerizedmailcow: dockerized - 项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized本篇技术指南聚焦 mailcow-dockerized 仓库中随附的 Adldap2 LDAP 库的 Contact联系人模型讲解如何在 Active Directory / OpenLDAP 目录中创建、读写联系人对象梳理其与 User 模型的区别与继承关系并结合 mailcow 的 LDAP 同步场景data/conf/phpfpm/crons/ldap-sync.php说明实际使用方式。读完本文你将掌握通过$provider-make()-contact()创建联系人、利用HasMemberOf处理组成员关系以及通过自定义 Schema 扩展 Contact 模型的完整套路。Contact 模型的定位Adldap2 模型体系中的轻量用户在 Adldap2 的模型体系中Adldap\Models\Contact是所有 LDAP 模型中最特殊的一类它没有属于自己的专属方法或专属属性而是全部继承自基类。官方文档data/web/inc/lib/vendor/adldap2/adldap2/docs/models/contact.md对此的表述是The Contact model extends from the baseAdldap\Models\Modelclass and contains no specific methods / attributes that are limited to it.从仓库源码看这一点得到了精确印证。Contact类的完整实现只有短短十几行data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Contact.phpnamespace Adldap\Models; /** * Class Contact. * * Represents an LDAP contact. */ class Contact extends Entry { use Concerns\HasMemberOf; use Concerns\HasUserProperties; }三个关键信息继承自Entry而非直接继承ModelEntry位于Adldap\Models命名空间下是目录中条目的通用抽象而Model是整个模型体系的基类。从源码结构看Contact通过Entry间接获得了基类Model的全部能力属性读写、save()、create()、update()、delete()、move()、rename()、DN 构建等。使用了HasMemberOftrait联系人可以像用户、组一样参与组成员关系能够查询、添加、移除所在组详见下文。使用了HasUserPropertiestrait联系人虽然不拥有账号属性密码、登录名等但可以直接复用用户资料类属性姓名、邮箱、电话、部门、地址等这也是它被称为轻量用户的原因。创建 Contact 对象Contact 模型本身不提供构造器级别的差异创建它的标准方式是通过 provider 的模型工厂make()并传入初始化属性数组// Adldap\Models\Contact $contact $provider-make()-contact([ cn Suzy Doe, ]);这里$provider是已连接 LDAP 服务器的 provider 实例Adldap\Connections\ProviderInterface。上述调用等价于构造一个 cn 为 Suzy Doe 的 Contact 模型实例但此时它尚未写入目录——需要后续调用$contact-save()才会真正持久化。从源码层面看工厂方法contact()data/web/inc/lib/vendor/adldap2/adldap2/src/Models/Factory.php实际做了三件事public function contact(array $attributes []) { $model $this-schema-contactModel(); return (new $model($attributes, $this-query)) -setAttribute($this-schema-objectClass(), [ $this-schema-top(), $this-schema-person(), $this-schema-organizationalPerson(), $this-schema-contact(), ]); }通过$this-schema-contactModel()获取当前 Schema 对应的 Contact 模型类默认Adldap\Models\ContactSchema.php实例化模型并绑定查询构建器$this-query自动写入objectClass属性值为四个对象类的组合top、person、organizationalPerson、contact。也就是说调用make()-contact()后对象已经被预置了完整的 LDAP 对象类链无需手动设置objectClass。这是 Contact 与 User 在目录 schema 层面最本质的区别Contact 使用contact对象类而 User 使用user对象类二者都继承自top - person - organizationalPerson。完整创建流程示例结合基类能力docs/models/model.md一个完整的创建并保存联系人流程如下// 1. 通过工厂创建 Contact 并填入初始属性 $contact $provider-make()-contact([ cn Suzy Doe, ]); // 2. 使用 DN 构建器指定联系人所在的组织单元 // 生成的 DN 形如CNSuzy Doe,OUContacts,DCacme,DCorg $dn $contact-getDnBuilder()-addOu(Contacts); $contact-setDn($dn); // 3. 补充联系人资料HasUserProperties 提供 $contact-setFirstName(Suzy); $contact-setLastName(Doe); $contact-setEmail(suzy.doeacme.org); $contact-setTelephoneNumber(1 555 0100); $contact-setDepartment(Public Relations); $contact-setTitle(PR Manager); // 4. 持久化到 LDAP 目录 if ($contact-save()) { // 保存成功后模型属性会从服务器重新同步 echo $contact-exists; // true } else { // 保存失败处理 }要点说明save()返回布尔值保存成功后模型属性会自动与 LDAP 服务器重新同步见 model.md 的 Note因此同一请求内可以立刻继续基于该模型做其他操作若确定对象尚不存在可改用create()若确定对象已存在可改用update()与 User 模型不同创建 Contact 不需要设置密码因此不要求连接启用 SSL/TLSUser 文档中明确提示设置密码时 SSL/TLS 必须开启见 user.mdContact 无此约束。Contact 可用的属性与方法由于 Contact 继承自Entry/Model并组合了HasUserPropertiestrait它可以使用的 API 分为两大类。1. 全部模型通用的基类方法来自Model/Entry以下是文档列出的通用 gettermodel.mdContact 全部可用$contact-getName(); // name 属性 $contact-getCommonName(); // cn 属性 $contact-getDisplayName(); // displayname 属性 $contact-getAccountName(); // samaccountnameContact 上通常为空 $contact-getCreatedAt(); // whencreated 属性 $contact-getCreatedAtDate(); // MySQL 时间戳格式 $contact-getCreatedAtTimestamp();// Unix 时间戳格式 $contact-getUpdatedAt(); // whenchanged 属性 $contact-getObjectClass(); // objectclass 属性 $contact-getObjectCategory(); // 根对象类别字符串 $contact-getObjectSid(); // 二进制 SID $contact-getObjectGuid(); // 二进制 GUID $contact-getConvertedSid(); // 字符串 SID $contact-getConvertedGuid(); // 字符串 GUID $contact-getPrimaryGroupId(); // 主组 ID通用的属性读写、增删改查能力同样继承自基类// 读取 $contact-getAttributes(); // 全部属性数组 $contact-getAttribute(mail); // 邮箱数组无则 null $contact-getFirstAttribute(mail); // 第一个邮箱 $contact-mail; // 属性方式访问 $contact-mail[0]; // 第一个邮箱 // 写入 $contact-setAttribute(cn, New Name); // 方法方式 $contact-setFirstAttribute(mail, ab.c); // 覆盖第一个值 $contact-cn New Name; // 属性方式 $contact-fill([cn New Name, mail ab.c]); // 批量填充 // 判断 $contact-hasAttribute(mail); // 是否含某属性 $contact-countAttributes(); // 属性总数 $contact-inOu(Contacts); // 是否位于某 OU $contact-isWritable(); // 是否可写 $contact-getDirty(); // 已修改属性 $contact-getOriginal(); // 原始属性特别提醒设置布尔型 LDAP 属性时不能直接使用0/1/true/false保存时会被转成整数导致 LDAP 服务器报错必须使用字符串TRUE/FALSE。这是 Adldap2 在多值属性模型下的一个经典坑例如$contact-setFirstAttribute(showInAddressBook, TRUE); $contact-save();删除属性时将属性设为null或调用deleteAttribute()创建新属性时直接给不存在的属性赋值并在save()时会自动创建也可以调用createAttribute()/updateAttribute()单独操作。2. 用户资料类属性来自HasUserPropertiestraitHasUserProperties为 Contact 提供了丰富的通讯录字段读写方法HasUserProperties.php方法getter / setter对应 LDAP 属性说明getEmail()/setEmail()mail主邮箱set 会清空其余邮箱getFirstName()/setFirstName()givenName名getLastName()/setLastName()sn姓getTitle()/setTitle()title职位getDepartment()/setDepartment()department部门getCountry()/setCountry()c国家getStreetAddress()/setStreetAddress()streetAddress街道地址getPostalCode()/setPostalCode()postalCode邮政编码getPostOfficeBox()/setPostOfficeBox()postOfficeBox邮箱号PO BoxgetTelephoneNumber()/setTelephoneNumber()telephoneNumber电话getFacsimileNumber()/setFacsimileNumber()facsimileTelephoneNumber传真getMobileNumber()/setMobileNumber()mobile主手机号getOtherMobileNumber()/setOtherMobileNumber()otherMobile备用手机号getInitials()/setInitials()initials姓名缩写getIpPhone()/setIpPhone()ipPhoneIP 电话getManager()/setManager()manager上级存 DNgetMailNickname()mailNickname邮件昵称getProxyAddresses()/setProxyAddresses()proxyAddresses代理地址数组addProxyAddress()proxyAddresses追加一个代理地址getOtherMailbox()/setOtherMailbox()otherMailbox其他邮箱这些方法内部都通过$this-schema-xxx()将属性名映射为当前目录类型Active Directory / OpenLDAP / FreeIPA 等的真实 LDAP 属性名例如getEmail()实现为$this-getFirstAttribute($this-schema-email())而schema-email()在 Active Directory Schema 中返回mail。组与成员关系HasMemberOf 的实战用法Contact 通过HasMemberOftrait 获得完整的组成员关系能力源码见 HasMemberOf.php配套文档见 docs/models/traits/has-member-of.md。查询所在组// 获取联系人所在的所有组返回 Adldap\Query\Collection元素为 Group 模型 $groups $contact-getGroups(); foreach ($groups as $group) { echo $group-getCommonName(); // 如 PR-Team } // 只取需要的字段以加速查询 $groups $contact-getGroups([cn]); // 递归获取嵌套组 $groups $contact-getGroups([], true); // 只要组名一维数组 $names $contact-getGroupNames(); $names $contact-getGroupNames(true); // 递归注意getGroups()默认只返回直接所属组要包含嵌套组需传第二个参数true。递归实现中通过$visited数组记录已访问 DN 来防止环形组依赖造成死循环见 HasMemberOf.php。判断成员资格// 传入 Group 模型 $group $provider-search()-groups()-find(PR-Team); if ($contact-inGroup($group)) { /* ... */ } // 传入多个组数组 / Collection $groups $provider-search()-findManyBy(cn, [PR-Team, Office]); if ($contact-inGroup($groups-toArray())) { /* ... */ } // 传入 DN 数组 $dns [cnPR-Team,ouGroups,dcacme,dcorg]; if ($contact-inGroup($dns, true)) { /* ... */ } // 传入组名数组 if ($contact-inGroup([PR-Team, Office], true)) { /* ... */ }inGroup()的第三参数$recursive控制是否包含嵌套组。从源码看其判定逻辑支持三种输入形式Group模型实例比较 DN、可解析的 DN 字符串比较 DN、普通字符串比较组cn见 groupIsParent()。添加 / 移除组// 添加传 Group 模型或组的 DN 字符串均可 $group $provider-search()-groups()-find(PR-Team); $contact-addGroup($group); $contact-addGroup(cnPR-Team,ouGroups,dcacme,dcorg); // 移除 $contact-removeGroup($group); $contact-removeGroup(cnPR-Team,ouGroups,dcacme,dcorg); // 移除所有组返回成功移除的组 DN 数组 $removed $contact-removeAllGroups();重要addGroup()/removeGroup()内部直接操作目录不需要再调用save()方法返回布尔值可直接用于if判断。Contact 与 User 的区别及查询过滤HasUserProperties让 Contact 拥有和 User 相似的外表但两者在目录中本质不同维度ContactUser对象类top person organizationalPerson contacttop person organizationalPerson user账号能力无登录、无密码、无 UAC有登录名、密码、UAC 控制创建约束无需 SSL/TLS设置密码时要求 SSL/TLS主组无有主组getPrimaryGroup()常用场景通讯录、外部联系人、资源对象真实用户账号Adldap2 的查询构建器也针对这一区别做了处理。在Query\Factory::users()[data/web/inc/lib/vendor/adldap2/adldap2/src/Query/Factory.php#L143-L158]中查询用户时会额外追加一个过滤条件排除objectClasscontact的对象而且该条件仅在 Active Directory Schema 下才加入OpenLDAP 不支持对 contact 对象类的不等于过滤写法// OpenLDAP doesnt like specifying the omission of user objectclasses // equal to contact. Well make sure were working with // ActiveDirectory before adding this filter. if (is_a($this-schema, ActiveDirectory::class)) { $wheres[] [$this-schema-objectClass(), Operator::$doesNotEqual, $this-schema-objectClassContact()]; }相应地查询构建器也提供了专门的contacts()方法Query/Factory.php返回限定在 contact 对象类范围内的查询构建器可用于批量检索联系人。结合 Schema 扩展 Contact自定义模型与自定义目录类型Adldap2 从 v8.0.0 起支持通过自定义 Schema 替换默认模型。如果你的业务需要给 Contact 增加自定义方法可以按以下步骤流程见 model.md第一步创建继承自Adldap\Models\Contact的自定义模型namespace App\Ldap\Models; use Adldap\Models\Contact as Model; class Contact extends Model { public function getFullName() { return trim($this-getFirstName() . . $this-getLastName()); } }约束自定义模型必须继承自现有 Adldap2 模型因为很多方法与属性只存在于这些类上。第二步创建自定义 Schema 并返回你的模型类名namespace App\Ldap\Schemas; use App\Ldap\Models\Contact; class LdapSchema extends ActiveDirectory { public function contactModel() { return Contact::class; } }第三步在连接配置中指定 Schema$config [ hosts [ldap.corp.local], username admin, password Pssword, schema App\Ldap\Schemas\LdapSchema::class, ]; $ad new Adldap($config); $provider $ad-connect(); // 此后所有 contact 结果都会返回你的自定义模型 $contact $provider-search()-contacts()-find(Suzy Doe);Schema 是 Adldap2 属性映射的核心默认ActiveDirectorySchema 定义了所有 LDAP 属性名contact、contactModel、email、memberOf等见 Schema.php仓库同时提供了OpenLDAP、FreeIPA、Directory389、EDirectory等实现Schemas 目录。如果你连接的是 OpenLDAP / FreeIPA 等非 Active Directory 服务器必须在配置中显式切换 schema否则查询结果可能无法映射到正确的模型实例setup.md。在 mailcow 项目中的实际用途mailcow-dockerized 将 Adldap2 以 vendor 依赖的形式内置于 Web 前端代码中位于data/web/inc/lib/vendor/adldap2/adldap2其 LDAP 相关功能集中在 cron 脚本 data/conf/phpfpm/crons/ldap-sync.php 与 admin 配置页data/web/templates/admin/tab-ldap.twig。在实际部署中Contact 模型主要用于两类场景目录联系人同步将 AD/LDAP 目录中的 Contact 条目作为通讯录来源同步到邮件系统供用户在 Webmail / SOGo 中检索外部联系人成员资格驱动的权限派生利用HasMemberOf的能力把联系人所在的 AD 组映射为邮件系统的别名/转发规则实现按组管理收件人。需要强调的是mailcow 的 LDAP 同步主流程以User 模型为核心账号登录、邮箱归属Contact 模型主要用于补充通讯录数据具体同步哪些对象类、映射哪些属性取决于你在管理界面 LDAP 配置中的设置请以实际配置为准。小结Adldap2 的 Contact 模型是一个零专属逻辑但能力完整的 LDAP 模型通过$provider-make()-contact([...])创建工厂方法自动写入top/person/organizationalPerson/contact对象类组合HasMemberOf与HasUserProperties同时获得组成员管理与通讯录字段读写能力全部属性读写、增删改查、DN 操作继承自基类Model/Entry在 Active Directory 下查询用户时会被显式排除需用contacts()方法单独检索需要扩展时可通过自定义 Schema 自定义模型无缝替换默认Contact实现。掌握这些要点后无论是为 mailcow 补充 LDAP 通讯录同步还是在其他 PHP 项目中对接 AD/OpenLDAP 联系人数据都能直接复用上述模式。延伸阅读Contact 模型官方文档模型基类创建 / 更新 / 属性操作 / 移动 / 删除User 模型含密码创建约束HasMemberOf Trait 文档Contact 源码实现模型工厂 contact() 方法Schema 属性映射定义连接配置与 Schema 选择mailcow LDAP 同步 cron 脚本【免费下载链接】mailcow-dockerizedmailcow: dockerized - 项目地址: https://gitcode.com/GitHub_Trending/ma/mailcow-dockerized创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考