ismailelbery/laravel-ldap-sync
最新稳定版本:v1.0.0
Composer 安装命令:
composer require ismailelbery/laravel-ldap-sync
包简介
Sync users from Active Directory OUs into a local Eloquent model, with live LDAP search and manager resolution.
README 文档
README
An opinionated LDAP / Active Directory user synchronization package for Laravel. Sync users from multiple Organizational Units (OUs) into your local database on a schedule, with department mapping, attribute transformation, and full sync reporting — built on top of LdapRecord.
Designed for enterprise and government environments where Active Directory is the source of truth and your Laravel application needs a reliable, queryable local copy of users.
✨ Features
- 🔁 Scheduled synchronization — run full or incremental syncs via Laravel's scheduler
- 🏢 Multi-OU support — search and sync across multiple base DNs / OUs in a single run
- 🔍 User search — search LDAP directly by username, name (EN/AR), email, or department with a fluent API
- 👔 Manager resolution — get a user's direct manager or the full management chain up to the top
- 🗂️ Department mapping — map LDAP OUs or attributes to local departments/teams
- 🔧 Attribute mapping — declarative config to map LDAP attributes to Eloquent columns (with custom transformers)
- 🌐 Arabic / UTF-8 safe — correct handling of Arabic display names and attributes
- 🧹 Stale user handling — disable, soft-delete, or flag users removed from AD
- 📊 Sync reports — created / updated / disabled counts, with optional logging and notifications
- 🧪 Dry-run mode — preview changes before touching your database
- 🪝 Events — hook into
UserSynced,UserDisabled,SyncCompletedfor custom logic
📦 Installation
composer require ismailelbery/laravel-ldap-sync
Publish the config and migrations:
php artisan vendor:publish --tag="ldap-sync-config" php artisan vendor:publish --tag="ldap-sync-migrations" php artisan migrate
⚙️ Configuration
All connection settings live in your .env:
LDAP_HOST=ad.example.gov.sa LDAP_PORT=389 LDAP_USERNAME="cn=svc-laravel,ou=ServiceAccounts,dc=example,dc=gov,dc=sa" LDAP_PASSWORD=secret LDAP_BASE_DN="dc=example,dc=gov,dc=sa" # Semicolon-separated list of OUs to sync (relative or absolute DNs) LDAP_SYNC_OUS="ou=IT,ou=Departments;ou=HR,ou=Departments;ou=Digitization" LDAP_SYNC_SCHEDULE="0 2 * * *" # nightly at 2 AM LDAP_SYNC_STALE_STRATEGY=disable # disable | delete | flag | ignore
The published config/ldap-sync.php lets you define attribute mapping:
return [ 'model' => App\Models\User::class, 'unique_key' => [ 'ldap' => 'objectguid', 'local' => 'ldap_guid', ], 'attributes' => [ 'samaccountname' => 'username', 'mail' => 'email', 'displayname' => 'name', 'displayname_ar' => 'name_ar', // custom AD attribute 'telephonenumber' => 'phone', 'department' => 'department_name', 'title' => 'job_title', ], // Optional: transform values before saving 'transformers' => [ 'phone' => fn ($value) => preg_replace('/^\+?966/', '0', $value ?? ''), ], // Map OUs to local department IDs 'department_map' => [ 'ou=Digitization' => 'General Department of Digitization & AI', 'ou=HR' => 'Human Resources', ], 'stale_users' => [ 'strategy' => env('LDAP_SYNC_STALE_STRATEGY', 'disable'), 'column' => 'is_active', ], ];
🚀 Usage
Run a sync manually
php artisan ldap:sync
Sync a specific OU only
php artisan ldap:sync --ou="ou=IT,ou=Departments"
Preview changes without writing (dry run)
php artisan ldap:sync --dry-run
Example output:
Syncing 3 OUs from ad.example.gov.sa...
✔ ou=IT,ou=Departments ............ 142 found
✔ ou=HR,ou=Departments ............ 38 found
✔ ou=Digitization ................. 27 found
Created: 12
Updated: 189
Disabled: 6
Skipped: 0
Sync completed in 4.2s
Schedule it
In routes/console.php (Laravel 11):
use Illuminate\Support\Facades\Schedule; Schedule::command('ldap:sync')->dailyAt('02:00');
Listen to events
use IsmailElbery\LdapSync\Events\UserSynced; use IsmailElbery\LdapSync\Events\SyncCompleted; Event::listen(UserSynced::class, function (UserSynced $event) { // $event->user, $event->wasRecentlyCreated }); Event::listen(SyncCompleted::class, function (SyncCompleted $event) { // $event->report->created, ->updated, ->disabled });
🔍 User Search
Search Active Directory directly (live, not the local table) using the LdapUserSearch facade. All searches run across all configured OUs and merge + deduplicate results by objectguid.
use IsmailElbery\LdapSync\Facades\LdapUserSearch; // By username (sAMAccountName) — exact match $user = LdapUserSearch::byUsername('i.elbery'); // By email — exact match $user = LdapUserSearch::byEmail('i.elbery@example.gov.sa'); // By name — partial match, searches displayName (EN and AR attributes) $users = LdapUserSearch::byName('Ismail'); // returns Collection $users = LdapUserSearch::byName('إسماعيل'); // Arabic works too // By department $users = LdapUserSearch::byDepartment('Digitization'); // Smart search — detects whether the term looks like an email, // username, or name, and searches the right attributes $users = LdapUserSearch::search('i.elbery@example.gov.sa'); $users = LdapUserSearch::search('Ismail'); // Fluent combination $users = LdapUserSearch::query() ->inOu('ou=IT,ou=Departments') ->department('Infrastructure') ->name('Ahmed') ->limit(20) ->get();
Each result is returned as an LdapUserDto with a consistent shape regardless of AD schema quirks:
$user->username; // sAMAccountName $user->name; // displayName $user->nameAr; // Arabic display name (if configured) $user->email; $user->department; $user->title; $user->phone; $user->dn; // distinguished name $user->guid; // objectGUID $user->managerDn; // raw manager attribute
Search command
A quick CLI lookup is also included:
php artisan ldap:search "Ismail"
php artisan ldap:search --email=i.elbery@example.gov.sa
php artisan ldap:search --department=Digitization
👔 Manager Resolution
Resolve reporting lines straight from the AD manager attribute.
use IsmailElbery\LdapSync\Facades\LdapUserSearch; $user = LdapUserSearch::byUsername('i.elbery'); // Direct manager — single LdapUserDto (or null) $manager = LdapUserSearch::directManagerOf($user); $manager = LdapUserSearch::directManagerOf('i.elbery'); // username works too // Full management chain — ordered Collection from direct manager // up to the top of the hierarchy (cycle-safe) $chain = LdapUserSearch::managersOf('i.elbery'); foreach ($chain as $level => $manager) { echo "{$level}: {$manager->name} ({$manager->title})"; } // 0: Ahmed Saleh (Section Head) // 1: Khalid Omar (Department Director) // 2: Fahad Al-Otaibi (Deputy Governor) // Limit chain depth (e.g. for approval workflows needing only 2 levels) $chain = LdapUserSearch::managersOf('i.elbery', maxDepth: 2); // Inverse: who reports directly to this user? $reports = LdapUserSearch::directReportsOf('k.omar');
This is particularly useful for approval workflows — route a request to a user's direct manager, or escalate up the chain automatically:
$approver = LdapUserSearch::directManagerOf(auth()->user()->username); ApprovalRequest::create([ 'requester_id' => auth()->id(), 'approver_dn' => $approver?->dn, ]);
Note: Manager resolution requires the
managerattribute to be populated in your AD. The chain resolver guards against circular references and missing managers, returning the chain built so far rather than throwing.
🧱 How It Works
- Connects to your LDAP/AD server using LdapRecord with the credentials in
config/ldap.php. - Iterates each configured OU, paginating results to safely handle large directories.
- For each entry, resolves the local user via the configured unique key (
objectguidby default — immune to renames and OU moves). - Applies attribute mapping and transformers, then creates or updates the Eloquent model.
- After all OUs are processed, applies the stale-user strategy to local users no longer present in AD.
- Fires events and writes a sync report.
🧪 Testing
composer test
The test suite uses LdapRecord's built-in directory emulator, so no real LDAP server is required.
🗺️ Roadmap
- Group → role synchronization (Spatie permissions bridge)
- Incremental sync via
whenChanged/ USN tracking - Photo (
thumbnailPhoto) sync to local storage - Web dashboard for sync history
🤝 Contributing
Contributions are welcome! Please open an issue first to discuss what you would like to change, and make sure tests pass before submitting a PR.
🔒 Security
If you discover a security vulnerability, please email ismail.bery@gmail.com instead of using the issue tracker.
📄 License
The MIT License (MIT). See LICENSE.md for details.
Built with ☕ by Ismail Elbery — born from years of syncing Active Directory users in Laravel government systems.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-10