[Web] Fix mailbox editing when password is unchanged, fix adding new administrator (fixes #4054, fixes #4053); [Web] Update libs, add LDAP for future admin/domain admin authentication

master
andryyy 2021-04-13 21:34:47 +02:00
parent 75c313ca92
commit 19843cc786
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
1623 changed files with 131949 additions and 2288 deletions

View File

@ -48,7 +48,7 @@ function admin($_action, $_data = null) {
return false; return false;
} }
} }
if (password_check($password_new, $password_new2) !== true) { if (password_check($password2, $password2) !== true) {
return false; return false;
} }
// support pre hashed passwords // support pre hashed passwords
@ -127,7 +127,7 @@ function admin($_action, $_data = null) {
continue; continue;
} }
} }
if (!empty($password) && !empty($password2)) { if (!empty($password)) {
if (password_check($password, $password2) !== true) { if (password_check($password, $password2) !== true) {
return false; return false;
} }

View File

@ -201,7 +201,7 @@ function domain_admin($_action, $_data = null) {
)); ));
} }
} }
if (!empty($password) && !empty($password2)) { if (!empty($password)) {
if (password_check($password, $password2) !== true) { if (password_check($password, $password2) !== true) {
return false; return false;
} }

View File

@ -2582,23 +2582,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
)); ));
} }
} }
if (password_check($password, $password2) !== true) { if (!empty($password)) {
continue; if (password_check($password, $password2) !== true) {
continue;
}
// support pre hashed passwords
if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
$password_hashed = $password;
}
else {
$password_hashed = hash_password($password);
}
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
} }
// support pre hashed passwords
if (preg_match('/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i', $password)) {
$password_hashed = $password;
}
else {
$password_hashed = hash_password($password);
}
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
// We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: to it the other way) // We could either set alias = 1 if alias = 2 or tune the Postfix alias table (that's what we did, TODO: to it the other way)
$stmt = $pdo->prepare("UPDATE `alias` SET $stmt = $pdo->prepare("UPDATE `alias` SET
`active` = :active `active` = :active

View File

@ -8,6 +8,7 @@
"ddeboer/imap": "^1.5", "ddeboer/imap": "^1.5",
"matthiasmullie/minify": "^1.3", "matthiasmullie/minify": "^1.3",
"bshaffer/oauth2-server-php": "^1.11", "bshaffer/oauth2-server-php": "^1.11",
"mustangostang/spyc": "^0.6.3" "mustangostang/spyc": "^0.6.3",
"directorytree/ldaprecord": "^2.4"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
examples/ export-ignore
tests/ export-ignore

View File

@ -0,0 +1,10 @@
- Adldap2 Version: #.#
- LDAP Type: <!-- Active Directory / OpenLDAP / FreeIPA / Sun Directory Server? -->
- PHP Version: #.#
<!-- **ISSUES WITHOUT THE ABOVE INFORMATION WILL BE CLOSED!** -->
### Description:
### Steps To Reproduce:

View File

@ -0,0 +1,3 @@
/.idea
/vendor
composer.lock

View File

@ -0,0 +1,10 @@
filter:
excluded_paths:
- tests/*
- src/Schemas/*
build:
nodes:
analysis:
tests:
override:
- command: php-scrutinizer-run

View File

@ -0,0 +1,7 @@
preset: recommended
enabled:
- length_ordered_imports
disabled:
- alpha_ordered_imports

View File

@ -0,0 +1,19 @@
language: php
php:
- 7.1
- 7.2
- 7.3
- 7.4
before_script:
- travis_retry composer self-update
- travis_retry composer install --prefer-source --no-interaction
script: ./vendor/bin/phpunit
branches:
only:
- master
- v9.0
- v8.0

View File

@ -0,0 +1,57 @@
{
"name": "adldap2/adldap2",
"type": "library",
"description": "A PHP LDAP Package for humans.",
"keywords": [
"active directory",
"directory",
"ad",
"ldap",
"windows",
"adldap",
"adldap2"
],
"license": "MIT",
"support": {
"docs": "https://github.com/Adldap2/Adldap2/blob/master/readme.md",
"issues": "https://github.com/Adldap2/Adldap2/issues",
"source": "https://github.com/Adldap2/Adldap2",
"email": "steven_bauman@outlook.com"
},
"authors": [
{
"name": "Steve Bauman",
"email": "steven_bauman@outlook.com",
"role": "Developer"
}
],
"require": {
"php": ">=7.0",
"ext-ldap": "*",
"ext-json": "*",
"psr/log": "~1.0",
"psr/simple-cache": "~1.0",
"tightenco/collect": "~5.0|~6.0|~7.0|~8.0",
"illuminate/contracts": "~5.0|~6.0|~7.0|~8.0"
},
"require-dev": {
"phpunit/phpunit": "~6.0|~7.0|~8.0",
"mockery/mockery": "~1.0"
},
"suggest": {
"ext-fileinfo": "fileinfo is required when retrieving user encoded thumbnails"
},
"archive": {
"exclude": ["/examples", "/tests"]
},
"autoload": {
"psr-4": {
"Adldap\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Adldap\\Tests\\": "tests/"
}
}
}

View File

@ -0,0 +1,16 @@
<!-- _coverpage.md -->
# Adldap2
<p align="center">
<a href="https://travis-ci.org/Adldap2/Adldap2"><img src="https://img.shields.io/travis/Adldap2/Adldap2.svg?style=flat-square"/></a>
<a href="https://scrutinizer-ci.com/g/Adldap2/Adldap2/?branch=master"><img src="https://img.shields.io/scrutinizer/g/adLDAP2/adLDAP2/master.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/dt/adldap2/adldap2.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/v/adldap2/adldap2.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/l/adldap2/adldap2.svg?style=flat-square"/></a>
</p>
> Working with LDAP doesn't need to be hard.
<!-- background image -->
![](media/bg.svg)

View File

@ -0,0 +1,27 @@
<!-- _sidebar.md -->
* Getting Started
* [Introduction](/)
* [Installation](installation.md)
* [Setup](setup.md)
* Usage
* [Searching](searching.md)
* [Creating & Updating](models/model.md)
* [Events](events.md)
* [Logging](logging.md)
* [Working With Distiguished Names](distinguished-names.md)
* [Troubleshooting](troubleshooting.md)
* Models
* [Model (Base)](models/model.md)
* [Computer](models/computer.md)
* [Contact](models/contact.md)
* [Container](models/container.md)
* [Group](models/group.md)
* [Organizational Unit](models/ou.md)
* [Printer](models/printer.md)
* [RootDse](models/root-dse.md)
* [User](models/user.md)

View File

@ -0,0 +1,167 @@
## Working With Distinguished Names
Working with DN strings are a pain, but they're about to get easier. Adldap includes a DN builder for easily modifying and
creating DN strings.
> **Note**: All values inserted into DN methods are escaped. You do not need to escape **any** values before hand.
#### Creating a New DN
To create a new DN, construct a new `Adldap\Models\Attributes\DistinguishedName` instance:
```php
$dn = new Adldap\Models\Attributes\DistinguishedName();
```
You can also pass in a current DN string and start modifying it:
```php
$currentDn = 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org';
$dn = new Adldap\Models\Attributes\DistinguishedName($currentDn);
```
#### Adding / Removing a Domain Component
```php
// Add Domain Component
$dn->addDc('corp');
// Remove Domain Component
$dn->removeDc('corp');
```
#### Adding / Removing an Organizational Unit
```php
// Add Organizational Unit
$dn->addOu('Accounting');
// Remove Organizational Unit
$dn->removeOu('Accounting');
```
#### Adding / Removing Common Names
```php
// Add Common Name
$dn->addCn('John Doe');
// Remove Common Name
$dn->removeCn('John Doe');
```
#### Setting a base
If you'd like to set the base DN, such as a domain component RDN, use the `setBase()` method:
```php
$base = 'dc=corp,dc=acme,dc=org';
$dn->setBase($base);
```
#### Creating a DN From A Model
When you're creating a new LDAP record, you'll need to create a distinguished name as well. Let's go through an example of
creating a new user.
```php
$user = $provider->make()->user();
$user->setCommonName('John Doe');
$user->setFirstName('John');
$user->setLastName('Doe');
```
So we've set the basic information on the user, but we run into trouble when we want to put the user into a certain container
(such as 'Accounting') which is done through the DN. Let's go through this example:
```php
$dn = $user->getDnBuilder();
$dn->addCn($user->getCommonName());
$dn->addOu('Accounting');
$dn->addDc('corp');
$dn->addDc('acme');
$dn->addDc('org');
// Returns 'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
echo $dn->get();
// The DistinguishedName object also contains the __toString() magic method
// so you can also just echo the object itself
echo $dn;
```
Now we've built a DN, and all we have to do is set it on the new user:
```php
$user->setDn($dn);
$user->save();
```
#### Modifying a DN From A Model
When you've received a model from a search result, you can build and modify the models DN like so:
```php
$user = $ad->users()->find('jdoe');
$dn = $user->getDnBuilder();
$dn->addOu('Users');
$user->setDn($dn)->save();
```
#### Retrieving the RDN components
To retrieve all of the RDN components of a Distinguished Name, call `getComponents()`:
```php
$dn = new Adldap\Models\Attributes\DistinguishedName(
'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
);
$components = $dn->getComponents();
var_dump($components);
// Output:
// array:5 [▼
// "cn" => array:1 [▼
// 0 => "John Doe"
// ]
// "uid" => []
// "ou" => array:1 [▼
// 0 => "Accounting"
// ]
// "dc" => array:3 [▼
// 0 => "corp"
// 1 => "acme"
// 2 => "org"
// ]
// "o" => []
// ]
```
You can also specify a component you would like returned by supplying it as an argument:
```php
$dn = new Adldap\Models\Attributes\DistinguishedName(
'cn=John Doe,ou=Accounting,dc=corp,dc=acme,dc=org'
);
$dcs = $dn->getComponents('dc');
var_dump($dcs);
// Output:
// array:3 [▼
// 0 => "corp"
// 1 => "acme"
// 2 => "org"
// ]
```

View File

@ -0,0 +1,175 @@
# Events
Adldap2 events provide a method of listening for certain LDAP actions
that are called and execute tasks for that specific event.
> **Note**: The Adldap2 event dispatcher was actually derived from the
> [Laravel Framework](https://github.com/laravel/framework) with
> Broadcasting & Queuing omitted to remove extra dependencies
> that would be required with implementing those features.
>
> If you've utilized Laravel's events before, this will feel very familiar.
## Registering Listeners
> **Note**: Before we get to registering listeners, it's crucial to know that events throughout
> Adldap2 are fired irrespective of the current connection or provider in use.
>
> This means that when using multiple LDAP connections, the same events will be fired.
>
> This allows you to set listeners on events that occur for all LDAP connections you utilize.
>
> If you are required to determine which events are fired from alternate connections, see [below](#determining-the-connection).
To register a listener on an event, retrieve the event dispatcher and call the `listen()` method:
```php
use Adldap\Auth\Events\Binding;
$dispatcher = \Adldap\Adldap::getEventDispatcher();
$dispatcher->listen(Binding::class, function (Binding $event) {
// Do something with the Binding event information:
$event->connection; // Adldap\Connections\Ldap instance
$event->username; // 'jdoe@acme.org'
$event->password; // 'super-secret'
});
```
The first argument is the event name you would like to listen for, and the
second is either a closure or class name that should handle the event:
Using a class:
> **Note**: When using just a class name, the class must contain a public `handle()` method that will handle the event.
```php
use Adldap\Adldap;
use Adldap\Auth\Events\Binding;
$dispatcher = Adldap::getEventDispatcher();
$dispatcher->listen(Binding::class, MyApp\BindingEventHandler::class);
```
```php
namespace MyApp;
use Adldap\Auth\Events\Binding;
class BindingEventHandler
{
public function handle(Binding $event)
{
// Handle the event...
}
}
```
## Model Events
Model events are handled the same way as authentication events.
Simply call the event dispatcher `listen()` method with the model event you are wanting to listen for:
```php
use Adldap\Models\Events\Saving;
$dispatcher = \Adldap\Adldap::getEventDispatcher();
$dispatcher->listen(Saving::class, function (Saving $event) {
// Do something with the Saving event information:
// Returns the model instance being saved eg. `Adldap\Models\Entry`
$event->getModel();
});
```
## Wildcard Event Listeners
You can register listeners using the `*` as a wildcard parameter to catch multiple events with the same listener.
Wildcard listeners will receive the event name as their first argument, and the entire event data array as their second argument:
```php
$dispatcher = Adldap::getEventDispatcher();
// Listen for all model events.
$dispatcher->listen('Adldap\Models\Events\*', function ($eventName, array $data) {
echo $eventName; // Returns 'Adldap\Models\Events\Updating'
var_dump($data); // Returns [0] => (object) Adldap\Models\Events\Updating;
});
$user = $provider->search()->users()->find('jdoe');
$user->setTelephoneNumber('555 555-5555');
$user->save();
```
## Determining the Connection
If you're using multiple LDAP connections and you require the ability to determine which events belong
to a certain connection, you can do so by verifying the host of the LDAP connection.
Here's an example:
```php
$dispatcher = Adldap::getEventDispatcher();
$dispatcher->listen(\Adldap\Models\Events\Creating::class, function ($event) {
$connection = $event->model->getConnection();
$host = $connection->getHost();
echo $host; // Displays 'ldap://192.168.1.1:386'
});
```
Another example with auth events:
```php
$dispatcher = Adldap::getEventDispatcher();
$dispatcher->listen(\Adldap\Auth\Events\Binding::class, function ($event) {
$connection = $event->connection;
$host = $connection->getHost();
echo $host; // Displays 'ldap://192.168.1.1:386'
});
```
## List of Events
### Authentication Events
There are several events that are fired during initial and subsequent binds to your configured LDAP server.
Here is a list of all events that are fired:
| Event| Description |
|---|---|
| Adldap\Auth\Events\Attempting | When any authentication attempt is called via: `$provider->auth()->attempt()` |
| Adldap\Auth\Events\Passed | When any authentication attempts pass via: `$provider->auth()->attempt()` |
| Adldap\Auth\Events\Failed | When any authentication attempts fail via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
| Adldap\Auth\Events\Binding | When any LDAP bind attempts occur via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
| Adldap\Auth\Events\Bound | When any LDAP bind attempts are successful via: `$provider->auth()->attempt()` *Or* `$provider->auth()->bind()` |
### Model Events
There are several events that are fired during the creation, updating and deleting of all models.
Here is a list of all events that are fired:
| Event | Description |
|---|---|
| Adldap\Models\Events\Saving | When a model is in the process of being saved via: `$model->save()` |
| Adldap\Models\Events\Saved | When a model has been successfully saved via: `$model->save()` |
| Adldap\Models\Events\Creating | When a model is being created via: `$model->save()` *Or* `$model->create()` |
| Adldap\Models\Events\Created | When a model has been successfully created via: `$model->save()` *Or* `$model->create()` |
| Adldap\Models\Events\Updating | When a model is being updated via: `$model->save()` *Or* `$model->update()` |
| Adldap\Models\Events\Updated | When a model has been successfully updated via: `$model->save()` *Or* `$model->update()` |
| Adldap\Models\Events\Deleting | When a model is being deleted via: `$model->delete()` |
| Adldap\Models\Events\Deleted | When a model has been successfully deleted via: `$model->delete()` |

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Adldap2 Documentation</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Adldap2 Documentation">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'Adldap2',
repo: 'https://github.com/Adldap2/Adldap2',
autoHeader: true,
auto2top: true,
homepage: 'readme.md',
coverpage: true,
search: 'auto',
loadSidebar: true,
subMaxLevel: 3
}
</script>
<script src="https://unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="https://unpkg.com/prismjs/components/prism-php.min.js"></script>
<script src="https://unpkg.com/docsify/lib/plugins/search.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,29 @@
# Requirements
Adldap2 requires the following:
- PHP 7.0 or greater
- LDAP extension enabled in PHP
- An LDAP server (ActiveDirectory, OpenLDAP, FreeIPA etc.)
# Composer
Adldap2 uses [Composer](https://getcomposer.org) for installation.
Once you have composer installed, run the following command in the root directory of your project:
```bash
composer require adldap2/adldap2
```
Then, if your application doesn't already require Composer's autoload, you will need to do it manually.
Insert this line at the top of your projects PHP script (usually `index.php`):
```php
require __DIR__ . '/vendor/autoload.php';
```
You're all set!
Now, head over to the [setup guide](setup.md) to get up and running.

View File

@ -0,0 +1,74 @@
# Logging
Adldap2 includes an implementation of PSR's widely supported [Logger](https://github.com/php-fig/log) interface.
By default, all of Adldap2's [events](events.md) will call the logger you have set to utilize.
> **Note**: Adldap2 does not include a file / text logger. You must implement your own.
## Registering & Enabling a Logger
To register a logger call `Adldap::setLogger()`. The logger must implement the `Psr\Log\LoggerInterface`.
>**Note**: Be sure to set the logger prior to creating a new `Adldap` instance. This
> ensures all events throughout the lifecycle of the request use your logger.
```php
use Adldap\Adldap;
Adldap::setLogger($myLogger);
$config = ['...'];
$ad = new Adldap();
$ad->addProvider($config);
```
## Disabling Logging
If you need to disable the event logger after a certain set of operations, simply pass in `null` and logging will be disabled:
```php
use Adldap\Adldap;
Adldap::setLogger($myLogger);
$config = ['...'];
$ad = new Adldap();
$ad->addProvider($config);
try {
$ad->connect();
// Disable logging anything else.
Adldap::setLogger(null);
} catch (\Adldap\Connections\BindException $e) {
//
}
```
## Logged Information
Here is a list of events that are logged along with the information included:
| Authentication Events | Logged |
|---|---|
| `Adldap\Auth\Events\Attempting` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Attempting - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
| `Adldap\Auth\Events\Binding` |` LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Binding - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
| `Adldap\Auth\Events\Bound` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Bound - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
| `Adldap\Auth\Events\Passed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Passed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org` |
| `Adldap\Auth\Events\Failed` | `LDAP (ldap://192.168.1.1:389) - Operation: Adldap\Auth\Events\Failed - Username: CN=Steve Bauman,OU=Users,DC=corp,DC=acme,DC=org - Result: Invalid Credentials` |
| Model Events | Logged |
|---|---|
| `Adldap\Models\Events\Saving` | `LDAP (ldap://192.168.1.1:389) - Operation: Saving - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Saved` | `LDAP (ldap://192.168.1.1:389) - Operation: Saved - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Creating` | `LDAP (ldap://192.168.1.1:389) - Operation: Creating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Created` | `LDAP (ldap://192.168.1.1:389) - Operation: Created - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Updating` | `LDAP (ldap://192.168.1.1:389) - Operation: Updating - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Updated` | `LDAP (ldap://192.168.1.1:389) - Operation: Updated - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Deleting` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleting - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |
| `Adldap\Models\Events\Deleted` | `LDAP (ldap://192.168.1.1:389) - Operation: Deleted - On: Adldap\Models\User - Distinguished Name: cn=John Doe,dc=acme,dc=org` |

View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1600 800'><rect fill='#46ff55' width='1600' height='800'/><g ><path fill='#51ff76' d='M486 705.8c-109.3-21.8-223.4-32.2-335.3-19.4C99.5 692.1 49 703 0 719.8V800h843.8c-115.9-33.2-230.8-68.1-347.6-92.2C492.8 707.1 489.4 706.5 486 705.8z'/><path fill='#57ff94' d='M1600 0H0v719.8c49-16.8 99.5-27.8 150.7-33.5c111.9-12.7 226-2.4 335.3 19.4c3.4 0.7 6.8 1.4 10.2 2c116.8 24 231.7 59 347.6 92.2H1600V0z'/><path fill='#5affb1' d='M478.4 581c3.2 0.8 6.4 1.7 9.5 2.5c196.2 52.5 388.7 133.5 593.5 176.6c174.2 36.6 349.5 29.2 518.6-10.2V0H0v574.9c52.3-17.6 106.5-27.7 161.1-30.9C268.4 537.4 375.7 554.2 478.4 581z'/><path fill='#57ffcd' d='M0 0v429.4c55.6-18.4 113.5-27.3 171.4-27.7c102.8-0.8 203.2 22.7 299.3 54.5c3 1 5.9 2 8.9 3c183.6 62 365.7 146.1 562.4 192.1c186.7 43.7 376.3 34.4 557.9-12.6V0H0z'/><path fill='#50ffe8' d='M181.8 259.4c98.2 6 191.9 35.2 281.3 72.1c2.8 1.1 5.5 2.3 8.3 3.4c171 71.6 342.7 158.5 531.3 207.7c198.8 51.8 403.4 40.8 597.3-14.8V0H0v283.2C59 263.6 120.6 255.7 181.8 259.4z'/><path fill='#7dffe9' d='M1600 0H0v136.3c62.3-20.9 127.7-27.5 192.2-19.2c93.6 12.1 180.5 47.7 263.3 89.6c2.6 1.3 5.1 2.6 7.7 3.9c158.4 81.1 319.7 170.9 500.3 223.2c210.5 61 430.8 49 636.6-16.6V0z'/><path fill='#9effe9' d='M454.9 86.3C600.7 177 751.6 269.3 924.1 325c208.6 67.4 431.3 60.8 637.9-5.3c12.8-4.1 25.4-8.4 38.1-12.9V0H288.1c56 21.3 108.7 50.6 159.7 82C450.2 83.4 452.5 84.9 454.9 86.3z'/><path fill='#baffea' d='M1600 0H498c118.1 85.8 243.5 164.5 386.8 216.2c191.8 69.2 400 74.7 595 21.1c40.8-11.2 81.1-25.2 120.3-41.7V0z'/><path fill='#d2ffea' d='M1397.5 154.8c47.2-10.6 93.6-25.3 138.6-43.8c21.7-8.9 43-18.8 63.9-29.5V0H643.4c62.9 41.7 129.7 78.2 202.1 107.4C1020.4 178.1 1214.2 196.1 1397.5 154.8z'/><path fill='#e9ffeb' d='M1315.3 72.4c75.3-12.6 148.9-37.1 216.8-72.4h-723C966.8 71 1144.7 101 1315.3 72.4z'/></g></svg>

View File

@ -0,0 +1,32 @@
# The Computer Model
> **Note**: This model contains the traits `HasDescription`, `HasLastLogonAndLogOff` & `HasCriticalSystemObject`.
> For more information, visit the documentation:
>
> [HasDescription](/models/traits/has-description.md),
> [HasLastLogonAndLogOff](/models/traits/has-last-login-last-logoff.md),
> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md)
## Methods
```php
$computer = $provider->search()->computers()->find('ACME-EXCHANGE');
// Returns 'Windows Server 2003'
$computer->getOperatingSystem();
// Returns '5.2 (3790)';
$computer->getOperatingSystemVersion();
// Returns 'Service Pack 1';
$computer->getOperatingSystemServicePack();
// Returns 'ACME-DESKTOP001.corp.acme.org'
$computer->getDnsHostName();
$computer->getLastLogOff();
$computer->getLastLogon();
$computer->getLastLogonTimestamp();
```

View File

@ -0,0 +1,13 @@
# The Contact Model
The Contact model extends from the base `Adldap\Models\Model` class and contains
no specific methods / attributes that are limited to it.
## Creation
```php
// Adldap\Models\Contact
$contact = $provider->make()->contact([
'cn' => 'Suzy Doe',
]);
```

View File

@ -0,0 +1,24 @@
# The Container Model
> **Note**: This model contains the trait `HasDescription` & `HasCriticalSystemObject`.
> For more information, visit the documentation:
>
> [HasDescription](/models/traits/has-description.md),
> [HasCriticalSystemObject](/models/traits/has-critical-system-object.md),
## Creation
```php
// Adldap\Models\Container
$container = $provider->make()->container([
'cn' => 'VPN Users',
]);
```
## Methods
The `Container` model contains only one unique method.
```php
$flags = $container->getSystemFlags();
```

View File

@ -0,0 +1,253 @@
# The Group Model
> **Note**: This model contains the trait `HasMemberOf`.
> For more information, visit the documentation:
>
> [HasMemberOf](/models/traits/has-member-of.md)
## Creation
```php
// Adldap\Models\Group
$group = $provider->make()->group([
'cn' => 'Managers',
]);
// Create group's DN through the DN Builder:
$group = $provider->make()->group();
$dn = $group->getDnBuilder();
$dn->addOu('Workstation Computers');
$dn->addCn("Managers");
$group->setDn($dn);
// Or set the DN manually:
$ou->setDn('cn=Managers,ou=Workstation Computers,dc=test,dc=local,dc=com');
$group->save();
```
## Getting a groups members
When you receive a `Group` model instance, it will contain a `member`
attribute which contains the distinguished names of all
the members inside the group.
```php
$group = $provider->search()->groups()->first();
foreach ($group->members as $member) {
echo $member; // 'cn=John Doe,dc=corp,dc=acme,dc=org'
}
```
But this might not be useful, since we might actually want the models for each member.
This can be easily done with the `getMembers()` method on the group.
```php
$group = $provider->search()->groups()->first();
foreach ($group->getMembers() as $member) {
echo get_class($member); // Instance of `Adldap\Models\Model`
echo $member->getCommonName();
}
```
> **Note**: You should be aware however, that calling the `getMembers()` method will
> query your `AD` server for **every** member contained in the group to retrieve
> its model. For larger group sets it may be worth paginating them.
### Paginating Group Members
The group you're looking for might contain hundreds / thousands of members.
In this case, your server might only return you a portion of the groups members.
To get around this limit, you need to ask your server to paginate the groups members through a select:
```php
$group = $provider->search()->groups()->select('member;range=0-500')->first();
foreach ($group->members as $member) {
// We'll only have 500 members in this query.
}
```
Now, when we have the group instance, we'll only have the first `500` members inside this group.
However, calling the `getMembers()` method will automatically retrieve the rest of the members for you:
```php
$group = $provider->search()->groups()->select('member;range=0-500')->first();
foreach ($group->getMembers() as $member) {
// Adldap will automatically retrieve the next 500
// records until it's retrieved all records.
$member->getCommonName();
}
```
> **Note**: Groups containing large amounts of users (1000+) will require
> more memory assigned to PHP. Your mileage will vary.
#### Paginating large sets of Group Members
When requesting group members from groups that contain a large amount of members
(typically over 1000), you may receive PHP memory limit errors due to
the large amount of the objects being created in the request.
To resolve this, you will need to retrieve the members manually. However using
this route you will only be able to retrieve the members distinguished names.
```php
$from = 0;
$to = 500;
$range = "member;range=$from-$to";
// Retrieve the group.
$group = $provider->search()->select($range)->raw()->find('Accounting');
// Remove the count from the member array.
unset($group[$range]['count']);
// The array of group members distinguished names.
$members = $group[$range];
foreach ($members as $member) {
echo $member; // 'cn=John Doe,dc=acme,dc=org'
}
```
You can then encapsulate the above example into a recursive function to retrieve the remaining group members.
## Getting only a groups member names
To retrieve only the names of the members contained in a group, call the `getMemberNames()` method:
```php
foreach ($group->getMemberNames() as $name) {
// Returns 'John Doe'
echo $name;
}
```
> **Note**: This method does not query your server for each member to retrieve its name. It
> only parses the distinguished names from the groups `member` attribute. This means that
> if you have paginated group members, you will need to perform another query yourself
> to retrieve the rest of the member names (or just call the `getMembers()` method).
## Setting Group Members
To set members that are apart of the group, you can perform this in two ways:
> **Note**: Remember, this will remove **all** pre-existing members, and set the new given members on the group.
```php
$members = [
'cn=John Doe,dc=corp,dc=acme,dc=org',
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
];
$group->setMembers($members);
$group->save();
```
Or manually:
```php
$group->member = [
'cn=John Doe,dc=corp,dc=acme,dc=org',
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
];
$group->save();
```
## Adding One Member
To add a single member to a group, use the `addMember()` method:
> **Note**: You do not need to call the `save()` method after adding a
> member. It's automatically called so you can determine
> if the member was successfully added.
```php
// We can provide a model, or just a plain DN of the new member
$user = $provider->search()->users()->first();
if ($group->addMember($user)) {
// User was successfully added to the group!
}
// Or
$user = 'cn=John Doe,dc=corp,dc=acme,dc=org';
if ($group->addMember($user)) {
//
}
```
## Adding Multiple Group Members
To add multiple members to a group, use the `addMembers()` method:
> **Note**: You do not need to call the `save()` method after adding
> members. It's automatically called so you can determine
> if the members were successfully added.
```php
$members = [
'cn=John Doe,dc=corp,dc=acme,dc=org',
'cn=Jane Doe,dc=corp,dc=acme,dc=org',
];
$group->addMembers($members);
// Or
$user = $provider->search()->users()->first();
if ($group->addMembers($user)) {
//
}
```
## Removing One Member
To remove a single member to a group, use the `removeMember()` method:
```php
// We can provide a model, or just a plain DN of the existing member
$group = $provider->search()->groups()->first();
$member = $group->getMembers()->first();
if ($group->removeMember($member)) {
// Member was successfully removed from the group!
}
// Or
$user = 'cn=John Doe,dc=corp,dc=acme,dc=org';
if ($group->removeMember($user)) {
//
}
```
## Removing All Members
To remove all members, use the `removeMembers()` method:
```php
if ($group->removeMembers()) {
// All members were successfully removed!
}
```

View File

@ -0,0 +1,655 @@
# Creating / Updating
## Introduction
Adldap2 implements the [ActiveRecord](https://en.wikipedia.org/wiki/Active_record_pattern) pattern.
This means that each LDAP record in your directory is represented as it's own model instance.
## Creating
Creating LDAP entries manually is always a pain, but Adldap2 makes it effortless. Let's get started.
When you have a provider instance, call the `make()` method. This returns an `Adldap\Models\Factory` instance:
```php
$factory = $provider->make();
```
Or you can chain all methods if you'd prefer:
```php
$user = $provider->make()->user();
```
### Available Make Methods
When calling a make method, all of them accept an `$attributes` parameter
to fill the model with your specified attributes.
```php
// Adldap\Models\User
$user = $provider->make()->user([
'cn' => 'John Doe',
]);
// Adldap\Models\Computer
$computer = $provider->make()->computer([
'cn' => 'COMP-101',
]);
// Adldap\Models\Contact
$contact = $provider->make()->contact([
'cn' => 'Suzy Doe',
]);
// Adldap\Models\Container
$container = $provider->make()->container([
'cn' => 'VPN Users',
]);
// Adldap\Models\Group
$group = $provider->make()->group([
'cn' => 'Managers',
]);
// Adldap\Models\OrganizationalUnit
$ou = $provider->make()->ou([
'name' => 'Acme',
]);
```
## Saving
When you have any model instance, you can call the `save()` method to persist the
changes to your server. This method returns a `boolean`. For example:
```php
$user = $provider->make()->user([
'cn' => 'New User',
]);
if ($user->save()) {
// User was saved.
} else {
// There was an issue saving this user.
}
```
> **Note**: When a model is saved successfully (whether created or updated), the
> models attributes are re-synced in the background from your LDAP server.
>
> This allows you to perform other operations during the same
> request that require an existing model.
### Creating (Manually)
If you are sure the model **does not exist** already inside your LDAP directory, you can use the `create()` method:
```php
$user = $provider->make()->user([
'cn' => 'New User',
]);
if ($user->create()) {
// User was created.
} else {
// There was an issue creating this user.
}
```
> **Note**: When you call the create method, if the model does not have a
> distinguished name, one will automatically be generated for you using your
> `base_dn` set in your configuration and the models common name.
### Updating (Manually)
If you are sure the model **does exist** already inside your LDAP directory, you can use the `update()` method:
```php
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
$user->displayName = 'Suzy Doe';
if ($user->update()) {
// User was updated.
} else {
// There was an issue updating this user.
}
```
## Checking Existence
If you need to check the existence of a model, use the property `exists`.
How does it know if the model exists in your LDAP directory? Well, when models are constructed from
search results, the `exists` property on the model is set to `true`.
```php
$user = $provider->search()->find('jdoe');
$user->exists; // Returns true.
if ($user->delete()) {
$user->exists; // Returns false.
}
```
If a model is created successfully, the `exists` property is set to `true`:
```php
$user = $provider->make()->user([
'cn' => 'John Doe',
]);
$user->exists; // Returns false.
if ($user->save()) {
$user->exists; // Returns true.
}
```
## Attributes
Due to LDAPs multi-valued nature, all LDAP attributes inside a model have their own array.
For example, a models attributes may contain the following:
```php
var_dump($user->getAttributes());
// Returns:
/*
[
'cn' => [
0 => 'John Doe',
],
'sn' => [
0 => 'Doe',
],
'givenname' => [
0 => 'John'
],
'useraccountcontrol' => [
0 => 512
],
'mail' => [
0 => 'jdoe@acme.org',
1 => 'john-doe@acme.org',
],
'memberof' => [
0 => 'cn=Accountants,ou=Groups,dc=acme,dc=org',
1 => 'cn=Employees,ou=Groups,dc=acme,dc=org',
2 => 'cn=Users,ou=Groups,dc=acme,dc=org',
],
]
*/
```
You can notice in the above dumped array that each attribute contains
its own array with a value assigned to the first key.
Since all models extend from the base class `Adldap\Models\Model`, there
are many useful methods that you can use on every model to easily
retrieve these attributes you're looking for.
### Getting Attributes
You can get attributes in a few ways:
```php
// Returns an array all of the users attributes.
$user->getAttributes();
// Returns an array of all the users email addresses.
// Returns `null` if non-existent.
$user->getAttribute('mail');
// Returns the users first email address.
// Returns `null` if non-existent.
$user->getAttribute('mail', 0);
// Returns the users first email address.
// Returns `null` if non-existent.
$user->getFirstAttribute('mail');
// Returns an array of all the users email addresses.
$user->mail;
// Returns the users first email address.
$user->mail[0];
```
#### Using a Getter
Some attributes have methods for easier retrieval so you don't need to look up the LDAP attribute name.
For example, to retrieve a users email address, use the method `getEmail()`:
```php
$user->getEmail();
```
##### Other Methods
The following methods are available on all returned models:
```php
// Returns the model's 'name' attribute.
$model->getName();
// Returns the model's 'cn' attribute.
$model->getCommonName();
// Returns the model's 'displayname' attribute.
$model->getDisplayName();
// Returns the model's 'samaccountname' attriubte.
$model->getAccountName();
// Returns the model's 'samaccounttype` attribute.
$model->getAccountType();
// Returns the model's 'whencreated` attribute.
$model->getCreatedAt();
// Returns the model's 'whencreated` attribute in a MySQL timestamp format.
$model->getCreatedAtDate();
// Returns the model's 'whencreated' attribute in unix time.
$model->getCreatedAtTimestamp();
// Returns the model's 'whenchanged` attribute.
$model->getUpdatedAt();
// Returns the model's 'whenchanged` attribute in a MySQL timestamp format.
$model->getUpdatedAtDate();
// Returns the model's 'whenchanged` attribute in unix time.
$model->getUpdatedAtTimestamp();
// Returns the model's 'objectclass' attribute.
$model->getObjectClass();
// Returns the model's root object category string.
$model->getObjectCategory();
// Returns the model's object category in an array.
$model->getObjectCategoryArray();
// Returns the model's object category distinguished name.
$model->getObjectCategoryDn();
// Returns the model's SID in binary.
$model->getObjectSid();
// Returns the model's GUID in binary.
$model->getObjectGuid();
// Returns the model's SID in a string.
$model->getConvertedSid();
// Returns the model's GUID in a string.
$model->getConvertedGuid();
// Returns the model's primary group ID.
$model->getPrimaryGroupId();
// Returns the model's 'instancetype' attribute.
$model->getInstanceType();
// Returns the model's 'maxpwdage' attribute.
$model->getMaxPasswordAge();
```
For more documentation on specific getters, please take a look at the relevant model documentation.
#### Getting Dirty (Modified) Attributes
You can get a models modified attributes using the `getDirty()` method:
```php
$user = $provider->search()->users()->find('john');
// Returns array [0 => 'John Doe']
var_dump($user->cn);
$user->setAttribute('cn', 'Jane Doe');
// Returns array ['cn' => [0 => 'Jane Doe']]
var_dump($user->getDirty());
// The attribute has been modified - returns array [0 => 'Jane Doe']
var_dump($user->cn);
```
The method returns an array with the key being the modified attribute,
and the array being the new values of the attribute.
#### Getting Original (Unmodified) Attributes
You can get a models original attributes using the `getOriginal()` method:
```php
$user = $provider->search()->users()->find('john');
// Returns array [0 => 'John Doe']
var_dump($user->cn);
$user->setAttribute('cn', 'Jane Doe');
// The attribute has been modified - returns array [0 => 'Jane Doe']
var_dump($user->cn);
// Retrieving the original value - returns array [0 => 'John Doe']
var_dump($user->getOriginal()['cn']);
```
> **Note**: Keep in mind, when you `save()` a model, the models original
> attributes will be re-synchronized to the models new attributes.
### Setting Attributes
Just like getting model attributes, there's multiple ways of setting attributes as well:
```php
// Setting via method:
$user->setAttribute('cn', 'John Doe');
// Specifying a subkey for overwriting specific attributes:
$user->setAttribute('mail', 'other-mail@mail.com', 0);
// Setting the first attribute:
$user->setFirstAttribute('mail', 'jdoe@mail.com');
// Setting via property:
$user->cn = 'John Doe';
// Mass setting attributes:
$user->fill([
'cn' => 'John Doe',
'mail' => 'jdoe@mail.com',
]);
```
#### Setting Boolean Attributes
When setting boolean attribute values, you cannot use `0` / `1` / `true` / `false` as these
are simply converted to integer values when saving and your LDAP server will
likely return an error for doing so on certain attributes.
You will need to use the string versions of the boolean (`'TRUE'` / `'FALSE'`) for the
boolean attribute to be set properly on your LDAP server.
Here's an example:
```php
$user->setFirstAttribute('msExchHideFromAddressLists', 'TRUE');
$user->save();
```
### Creating Attributes
To create an attribute that does not exist on the model, you can set it like a regular property:
```php
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
$user->new = 'New Attribute';
$user->save();
```
If the set attribute does not exist on the model already,
it will automatically be created when you call the `save()` method.
If you'd like manually create new attributes individually, call the `createAttribute($attribute, $value)` method:
```php
if ($user->createAttribute('new', 'New Attribute')) {
// Attribute created.
}
```
### Updating Attributes
To modify an attribute you can either use a setter method, or by setting it manually:
> **Note**: You can also utilize setters to create new attributes if your model does not already have the attribute.
```php
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
$user->cn = 'New Name';
// Or use a setter:
$user->setCommonName('New Name');
$user->save();
```
If you'd like to update attributes individually, call the `updateAttribute($attribute, $value)` method:
```php
if ($user->updateAttribute('cn', 'New Name')) {
// Successfully updated attribute.
}
```
### Removing Attributes
To remove attributes, set the attribute to `NULL`:
```php
$user->cn = null;
$user->save();
```
Or, you can call the `deleteAttribute($attribute)` method:
```php
if ($user->deleteAttribute('cn')) {
// Attribute has been deleted.
}
```
### Checking Attributes
#### Checking Existence of Attributes
To see if a model contains an attribute, use the method `hasAttribute()`:
```php
// Checking if a base attribute exists:
if ($user->hasAttribute('mail')) {
// This user contains an email address.
}
// Checking if a sub attribute exists, by key:
if ($user->hasAttribute('mail', 1)) {
// This user contains a second email address.
}
```
#### Counting the Models Attributes
To retrieve the total number of attributes, use the method `countAttributes()`:
```php
$count = $user->countAttributes();
var_dump($count); // Returns int
```
#### Checking if a Model is contained in an OU
To check if a model is located inside an OU, use the `inOu()` method:
```php
if ($model->inOu('User Accounts')) {
// This model is inside the 'User Accounts' OU.
}
```
You can also use an OU model instance:
```php
$serviceAccounts = $provider->search()->ous()->find('Service Accounts');
if ($model->inOu($serviceAccounts)) {
// This model is inside the 'Service Accounts' OU.
}
```
#### Checking if a Model is Writable
To check if the model can be written to, use the method `isWritable()`:
```php
if ($model->isWritable()) {
// You can modify this model.
}
```
### Force Re-Syncing A Models Attributes
If you need to forcefully re-sync a models attributes, use the method `syncRaw()`:
```php
$user->syncRaw();
```
> **Note**: This will query your LDAP server for the current model, and re-synchronize
> it's attributes. This is only recommended if your creating / updating / deleting
> attributes manually through your LDAP connection.
## Moving / Renaming
To move a user from one DN or OU to another, use the `move()` method:
> **Note**: The `move()` method is actually an alias for the `rename()` method.
```php
// New parent distiguished name.
$newParentDn = 'OU=New Ou,DC=corp,DC=local';
if ($user->move($newParentDn)) {
// User was successfully moved to the new OU.
}
```
You can also provide a model to move the child model into:
```php
// New parent OU.
$newParentOu = $provider->search()->ous()->find('Accounting');
if ($user->move($newParentOu)) {
// User was successfully moved to the new OU.
}
```
If you would like to keep the models old RDN along side their new RDN, pass in false in the second parameter:
```php
// New parent distiguished name.
$newParentDn = 'OU=New Ou,DC=corp,DC=local';
if ($user->move($newParentDn, $deleteOldRdn = false)) {
// User was successfully moved to the new OU,
// and their old RDN has been left in-tact.
}
```
To rename a users DN, just pass in their new relative distinguished name in the `rename()` method:
```php
$newRdn = 'cn=New Name';
if ($user->rename($newRdn)) {
// User was successfully renamed.
}
```
## Deleting
To delete a model, just call the `delete()` method:
```php
$user = $provider->search()->whereEquals('cn', 'John Doe')->firstOrFail();
echo $user->exists; // Returns true.
if ($user->delete()) {
// Successfully deleted user.
echo $user->exists; // Returns false.
}
```
## Extending
> **Note**: This feature was introduced in `v8.0.0`.
To use your own models, you will need to create a new [Schema](../schema.md).
Once you have created your own schema, you must insert it inside the construct of your provider.
Let's walk through this process.
First we'll create our model we'd like to extend / override:
> **Note**: Your custom model **must** extend from an existing Adldap2 model.
> This is due to methods and attributes that only exist on these classes.
```php
namespace App\Ldap\Models;
use Adldap\Models\User as Model;
class User extends Model
{
public function getCommonName()
{
// Overriding model method.
}
}
```
Now, we'll create our custom schema and return our models class name:
```php
namespace App\Ldap\Schemas;
use App\Ldap\Models\User;
class LdapSchema extends ActiveDirectory
{
public function userModel()
{
return User::class;
}
}
```
Finally, when we create a provider, we need to insert our Schema into the configuration:
```php
$config = [
'hosts' => ['...'],
'username' => 'admin',
'password' => 'P@ssword',
'schema' => MyApp\LdapSchema::class,
];
$ad = new Adldap($config);
$provider = $ad->connect();
// If `jdoe` exists, your custom model will be returned.
$user = $provider->search()->users()->find('jdoe');
```

View File

@ -0,0 +1,19 @@
# The Organization Model
The Organization model extends from the base `Adldap\Models\Model` class and contains
no specific methods / attributes that are limited to it.
## Creation
```php
// Adldap\Models\Organization
$org = $provider->make()->organization([
'o' => 'Some Company',
]);
// Set the DN manually:
$org->setDn('o=Some Company,dc=test,dc=local,dc=com');
$org->save();
```

View File

@ -0,0 +1,27 @@
# The OrganizationalUnit Model
The OrganizationalUnit model extends from the base `Adldap\Models\Model` class and contains
no specific methods / attributes that are limited to it.
## Creation
```php
// Adldap\Models\OrganizationalUnit
$ou = $provider->make()->ou([
'name' => 'Workstation Computers',
]);
// Generate the OU's DN through the DN Builder:
$dn = $ou->getDnBuilder();
$dn->addOu('Workstation Computers');
$ou->setDn($dn);
// Or set the DN manually:
$ou->setDn('ou=Workstation Computers,dc=test,dc=local,dc=com');
$ou->save();
```

View File

@ -0,0 +1,49 @@
# The Printer Model
## Methods
```php
$printer->getPrinterName();
$printer->getPrinterShareName();
$printer->getMemory();
$printer->getUrl();
$printer->getLocation();
$printer->getServerName();
$printer->getColorSupported();
$printer->getDuplexSupported();
$printer->getMediaSupported();
$printer->getStaplingSupported();
$printer->getPrintBinNames();
$printer->getPrintMaxResolution();
$printer->getPrintOrientations();
$printer->getDriverName();
$printer->getDriverVersion();
$printer->getPriority();
$printer->getPrintStartTime();
$printer->getPrintEndTime();
$printer->getPortName();
$printer->getVersionNumber();
$printer->getPrintRate();
$printer->getPrintRateUnit();
```

View File

@ -0,0 +1,33 @@
# The RootDse Model
## Getting the Root DSE
To get the Root DSE of your LDAP server, call the `getRootDse()` method off a new search:
```php
$rootDse = $provider->search()->getRootDse();
```
## Getting the schema naming context
To get the Root DSE schema naming context, call the `getSchemaNamingContext()`:
```php
$rootDse = $provider->search()->getRootDse();
$context = $rootDse->getSchemaNamingContext();
// Returns 'cn=Schema,cn=Configuration,dc=corp,dc=acme,dc=org'
echo $context;
```
## Getting the root domain naming context
To get the Root DSE domain naming context, call the `getRootDomainNamingContext()`:
```php
$context = $rootDse->getRootDomainNamingContext();
// Returns 'dc=corp,dc=acme,dc=org'
echo $context;
```

View File

@ -0,0 +1,13 @@
# HasCriticalSystemObject Trait
Models that contain this trait, have the `isCriticalSystemObject` attribute.
There is only one method that accompanies this trait:
```php
if ($model->isCriticalSystemObject()) {
//
}
```

View File

@ -0,0 +1,11 @@
# HasDescription Trait
Models that contain this trait, have the `description` attribute.
There are only two methods that accompany this trait:
```php
$model->getDescription();
$model->setDescription('The models description');
```

View File

@ -0,0 +1,16 @@
# HasLastLoginAndLastLogoff Trait
Models that contain this trait have the `lastlogoff`, `lastlogon` and `lastlogontimestamp` attributes.
## Methods
```php
// Returns the models's last log off attribute.
$computer->getLastLogOff();
// Returns the models's last log on attribute.
$computer->getLastLogon();
// Returns the models's last log on timestamp attribute.
$computer->getLastLogonTimestamp();
```

View File

@ -0,0 +1,166 @@
# HasMemberOf Trait
Models that contain this trait, have the ability to be apart of a group.
There's many helpful methods to assist you in all of the operations related to group membership, let's get started!
## Retrieving Groups
To retrieve the groups that a model is apart of, call the `getGroups()` method:
```php
$user = $provider->search()->users()->find('jdoe');
$groups = $user->getGroups();
foreach ($groups as $group) {
$group->getCommonName(); // ex. 'Accounting'
}
```
We can also pass in specific fields we need from the returned groups to speed up our queries.
For example, if we only need the groups common name:
```php
// Group models will be returned with only their common name.
$groups = $user->getGroups(['cn']);
```
However, calling `getGroups()` will only retrieve the models immediate groups (non-recursive).
To retrieve nested groups, pass in `true` into the second parameter:
```php
$groups = $user->getGroups([], $recursive = true);
```
## Retrieve Group Names
If you only want the models group names, call the `getGroupNames()` method:
```php
$names = $user->getGroupNames();
foreach ($names as $name) {
echo $name; // ex. 'Accounting'
}
```
However, this method will also retrieve only the immediate groups names
much like the `getGroups()` method. You'll need to pass in `true` in
the first parameter to retrieve results recursively.
```php
$names = $user->getGroupNames($recursive = true);
```
## Checking if the Model is apart of a Group
To check if a model is apart of a certain group, use the `inGroup()` method:
```php
$group = $provider->search()->groups()->find('Office');
if ($user->inGroup($group)) {
//
}
```
You can also check for multiple memberships by passing in an array of groups:
```php
$groups = $provider->search()->findManyBy('cn', ['Accounting', 'Office']));
if ($user->inGroup($groups->toArray()) {
// This user is apart of the 'Accounting' and 'Office' group!
}
```
> **Note**: Much like the other methods above, you'll need to provide a `$recursive`
> flag to the `inGroup()` method if you'd like recursive results included.
We can also provide distinguished names instead of Group model instances:
```php
$dns = [
'cn=Accounting,ou=Groups,dc=acme,dc=org',
'cn=Office,ou=Groups,dc=acme,dc=org',
];
if ($user->inGroup($dns, $recursive = true)) {
//
}
```
Or, we can also just provide the name(s) of the group(s).
```php
$names = [
'Accounting',
'Office',
];
if ($user->inGroup($names, $recursive = true)) {
//
}
```
## Adding a Group
To add the model to a specific group, call the `addGroup()` method:
```php
$group = $provider->search()->groups()->find('Accounting');
// You can either provide a Group model:
if ($user->addGroup($group)) {
//
}
// Or a Groups DN:
if ($user->addGroup('cn=Accounting,ou=Groups,dc=acme,dc=org')) {
//
}
```
> **Note**: You do not need to call the `save()` method for adding / removing groups.
> This is done automatically so you can perform clean `if` statements on the method.
## Removing a Group
To remove the model from a specific group, call the `removeGroup()` method:
```php
$group = $user->getGroups()->first();
// You can either provide a Group model:
if ($user->removeGroup($group)) {
//
}
// Or the groups DN:
if ($user->removeGroup('cn=Accounting,ou=Office Groups,dc=acme,dc=org')) {
//
}
```

View File

@ -0,0 +1,180 @@
# The User Model
> **Note**: This model contains the trait `HasMemberOf`. For more information, visit the documentation:
> [HasMemberOfTrait](/models/traits/has-member-of.md)
## Creating
> **Note**: If you need to create users with passwords, SSL or TLS **must** be enabled on your configured connection.
>
> The password you enter for the user **must** also obey your LDAP servers password requirements,
> otherwise you will receive a "Server is unwilling to perform" LDAP exception upon saving.
```php
// Construct a new User model instance.
$user = $provider->make()->user();
// Create the users distinguished name.
// We're adding an OU onto the users base DN to have it be saved in the specified OU.
$dn = $user->getDnBuilder()->addOu('Users'); // Built DN will be: "CN=John Doe,OU=Users,DC=acme,DC=org";
// Set the users DN, account name.
$user->setDn($dn);
$user->setAccountName('jdoe');
$user->setCommonName('John Doe');
// Set the users password.
// NOTE: This password must obey your AD servers password requirements
// (including password history, length, special characters etc.)
// otherwise saving will fail and you will receive an
// "LDAP Server is unwilling to perform" message.
$user->setPassword('correct-horse-battery-staple');
// Get a new account control object for the user.
$ac = $user->getUserAccountControlObject();
// Mark the account as enabled (normal).
$ac->accountIsNormal();
// Set the account control on the user and save it.
$user->setUserAccountControl($ac);
// Save the user.
$user->save();
// All done! An enabled user will be created and is ready for use.
```
## Methods
There's a ton of available methods for the User model. Below is a list for a quick reference.
> **Note**: Don't see a method for an LDAP attribute? Create an issue and let us know!
```php
// Get the users display name.
$user->getDisplayName();
// Get the users first email address.
$user->getEmail();
// Get the users title.
$user->getTitle();
// Get the users department.
$user->getDepartment();
// Get the users first name.
$user->getFirstName();
// Get the users last name.
$user->getLastName();
// Get the users info.
$user->getInfo();
// Get the users initials.
$user->getInitials();
// Get the users country.
$user->getCountry();
// Get the users street address.
$user->getStreetAddress();
// Get the users postal code.
$user->getPostalCode();
// Get the users physical delivery office name.
$user->getPhysicalDeliveryOfficeName();
// Get the users phone number.
$user->getTelephoneNumber();
// Get the users locale.
$user->getLocale();
// Get the users company.
$user->getCompany();
// Get the users other email addresses.
$user->getOtherMailbox();
// Get the users home mailbox database location (stored as a distinguished name).
$user->getHomeMdb();
// Get the users email nickname.
$user->getMailNickname();
// Get the users principal name.
$user->getUserPrincipalName();
// Get the users proxy email addresses.
$user->getProxyAddresses();
// Get the users failed login attempts.
$user->getBadPasswordCount();
// Get the users last failed login attempt timestamp.
$user->getBadPasswordTime();
// Get the users last password change timestamp.
$user->getPasswordLastSet();
// Get the users last password change timestamp in unix time.
$user->getPasswordLastSetTimestamp();
// Get the users last password change timestamp in MySQL date format.
$user->getPasswordLastSetDate();
// Get the users lockout time.
$user->getLockoutTime();
// Get the users user account control integer.
$user->getUserAccountControl();
// Get the users roaming profile path.
$user->getProfilePath();
// Get the users legacy exchange distinguished name.
$user->getLegacyExchangeDn();
// Get the users account expiry timestamp.
$user->getAccountExpiry();
// Get the boolean that determines whether to show this user in the global address book.
$user->getShowInAddressBook();
// Get the users thumbnail photo.
$user->getThumbnail();
// Get the users thumbnail photo (base64 encoded for HTML <img src=""> tags).
$user->getThumbnailEncoded();
// Get the users jpeg photo.
$user->getJpegPhoto();
// Get the users jpeg photo (base64 encoded for HTML <img src=""> tags).
$user->getJpegPhotoEncoded();
// Get the users manager.
$user->getManager();
// Get the users employee ID.
$user->getEmployeeId();
// Get the users employee number.
$user->getEmployeeNumber();
// Get the users employee type
$user->getEmployeeType();
// Get the users room number.
$user->getRoomNumber();
// Get the users department number.
$user->getDepartmentNumber();
// Get the users personal title.
$user->getPersonalTitle();
```

View File

@ -0,0 +1,115 @@
# Introduction
## What is Adldap2?
Adldap2 is a PHP LDAP package that allows you to:
1. Easily manage multiple LDAP connections at once
2. Perform authentication
3. Search your LDAP directory with a fluent and easy to use query builder
4. Create / Update / Delete LDAP entities with ease
5. And more
## History of Adldap2
Adldap2 was originally created as a fork of the original LDAP library [adLDAP](https://github.com/adldap/adLDAP) due to bugs, and it being completely abandoned.
Adldap2 contains absolutely no similarities to the original repository, and was built to be as easily accessible as possible, with great documentation, and easily understandable syntax.
Much of the API was constructed with Ruby's ActiveRecord and Laravel's Eloquent in mind, and to be an answer to the question:
> _Why can't we use LDAP like we use a database?_
## Why should you use Adldap2?
Working with LDAP in PHP can be a messy and confusing endeavor, especially when using multiple connections, creating and managing entities, performing moves, resetting passwords, and performing ACL modifications to user accounts.
Wrapper classes for LDAP are usually always created in PHP applications.
Adldap2 allows you to easily manage the above problems without reinventing the wheel for every project.
## Implementations
- [Laravel](https://github.com/Adldap2/Adldap2-Laravel)
## Quick Start
Install the package via `composer`:
```
composer require adldap2/adldap2
```
Use Adldap2:
```php
// Construct new Adldap instance.
$ad = new \Adldap\Adldap();
// Create a configuration array.
$config = [
// An array of your LDAP hosts. You can use either
// the host name or the IP address of your host.
'hosts' => ['ACME-DC01.corp.acme.org', '192.168.1.1'],
// The base distinguished name of your domain to perform searches upon.
'base_dn' => 'dc=corp,dc=acme,dc=org',
// The account to use for querying / modifying LDAP records. This
// does not need to be an admin account. This can also
// be a full distinguished name of the user account.
'username' => 'admin@corp.acme.org',
'password' => 'password',
];
// Add a connection provider to Adldap.
$ad->addProvider($config);
try {
// If a successful connection is made to your server, the provider will be returned.
$provider = $ad->connect();
// Performing a query.
$results = $provider->search()->where('cn', '=', 'John Doe')->get();
// Finding a record.
$user = $provider->search()->find('jdoe');
// Creating a new LDAP entry. You can pass in attributes into the make methods.
$user = $provider->make()->user([
'cn' => 'John Doe',
'title' => 'Accountant',
'description' => 'User Account',
]);
// Setting a model's attribute.
$user->cn = 'John Doe';
// Saving the changes to your LDAP server.
if ($user->save()) {
// User was saved!
}
} catch (\Adldap\Auth\BindException $e) {
// There was an issue binding / connecting to the server.
}
```
## Versioning
Adldap2 is versioned under the [Semantic Versioning](http://semver.org/) guidelines as much as possible.
Releases will be numbered with the following format:
`<major>.<minor>.<patch>`
And constructed with the following guidelines:
* Breaking backward compatibility bumps the major and resets the minor and patch.
* New additions without breaking backward compatibility bumps the minor and resets the patch.
* Bug fixes and misc changes bumps the patch.
Minor versions are not maintained individually, and you're encouraged to upgrade through to the next minor version.
Major versions are maintained individually through separate branches.

View File

@ -0,0 +1,662 @@
# Searching
## Introduction
Using the Adldap2 query builder makes building LDAP queries feel effortless.
It allows you to generate LDAP filters using a fluent and
convenient interface, similar to Eloquent in Laravel.
> **Note:** The Adldap2 query builder escapes all fields & values
> given to its `where()` methods. There is no need to clean or
> escape strings before passing them into the query builder.
## Creating a new Query
To create a new search query, call the `search()` method on your connection provider instance:
```php
$search = $provider->search();
```
Or you can chain all your methods if you'd prefer:
```php
$results = $provider->search()->where('cn', '=', 'John Doe')->get();
```
## Selects
> **Note:** Fields are case in-sensitive. For example, you can
> insert `CN`, `cn` or `cN`, they will return the same result.
#### Selecting attributes
Selecting only the LDAP attributes you need will increase the speed of your queries.
```php
// Passing in an array of attributes
$search->select(['cn', 'samaccountname', 'telephone', 'mail']);
// Passing in each attribute as an argument
$search->select('cn', 'samaccountname', 'telephone', 'mail');
```
## Executing Searches
#### Finding a specific record
If you're trying to find a single record, but not sure what the record might be, use the `find()` method:
```php
$record = $search->find('John Doe');
if ($record) {
// Record was found!
} else {
// Hmm, looks like we couldn't find anything...
}
```
> **Note**: Using the `find()` method will search for LDAP records using ANR
> (ambiguous name resolution) and return the first result.
>
> Since ActiveDirectory is the only LDAP distribution that supports ANR,
> an equivalent query will be created for other LDAP distributions
> that are not compatible.
>
> For a more fine-tuned search, use the `findBy()` method below.
##### Finding a record (or failing)
If you'd like to try and find a single record and throw an exception when it hasn't been
found, use the `findOrFail()` method:
```php
try {
$record = $search->findOrFail('John Doe');
} catch (Adldap\Models\ModelNotFoundException $e) {
// Record wasn't found!
}
```
#### Finding a record by a specific attribute
If you're looking for a single record with a specific attribute, use the `findBy()` method:
```php
// We're looking for a record with the 'samaccountname' of 'jdoe'.
$record = $search->findBy('samaccountname', 'jdoe');
```
##### Finding a record by a specific attribute (or failing)
If you'd like to try and find a single record by a specific attribute and throw
an exception when it cannot be found, use the `findByOrFail()` method:
```php
try {
$record = $search->findByOrFail('samaccountname', 'jdoe');
} catch (Adldap\Models\ModelNotFoundException $e) {
// Record wasn't found!
}
```
#### Finding a record by its distinguished name
If you're looking for a single record with a specific DN, use the `findByDn()` method:
```php
$record = $search->findByDn('cn=John Doe,dc=corp,dc=org');
```
###### Finding a record by its distinguished name (or failing)
If you'd like to try and find a single record by a specific DN and throw
an exception when it hasn't been found, use the `findByDnOrFail()` method:
```php
try {
$record = $search->findByDnOrFail('cn=John Doe,dc=corp,dc=org');
} catch (Adldap\Models\ModelNotFoundException $e) {
// Record wasn't found!
}
```
#### Retrieving results
To get the results from a search, simply call the `get()` method:
```php
$results = $search->select(['cn', 'samaccountname'])->get();
```
> **Note**: Executed searches via the `get()` method will return them inside an
> `Illuminate\Support\Collection` instance (a glorified array), with allows
> you to utilize [some extremely handy methods](https://laravel.com/docs/collections).
>
> Executed searches via the `first()` method will return **a model instance only**.
##### Retrieving the first record
To retrieve the first record of a search, call the `first()` method:
```php
$record = $search->first();
```
> **Note**: If you are using `sortBy()`, calling `first()` will not take this into account. Sorts
> are performed **after** retrieving query results. If you would like the first record of
> a sorted result set, call `first()` on a `Collection` of returned models.
###### Retrieving the first record (or failing)
To retrieve the first record of a search or throw an exception when one isn't found, call the `firstOrFail()` method:
```php
try {
$record = $search->firstOrFail();
} catch (Adldap\Models\ModelNotFoundException $e) {
// Record wasn't found!
}
```
## Limit
To limit the results records returned from your LDAP server and increase the
speed of your queries, you can use the `limit()` method:
```php
// This will only return 5 records that contain the name of 'John':
$records = $search->where('cn', 'contains', 'John')->limit(5)->get();
```
## Wheres
To perform a where clause on the search object, use the `where()` function:
```php
$search->where('cn', '=', 'John Doe');
```
This query would look for a record with the common name of 'John Doe' and return the results.
We can also perform a 'where equals' without including the operator:
```php
$search->whereEquals('cn', 'John Doe');
```
We can also supply an array of key - value pairs to quickly add multiple wheres:
```php
$wheres = [
'cn' => 'John Doe',
'samaccountname' => 'jdoe',
];
$search->where($wheres);
```
Or, if you require conditionals, you can quickly add multiple wheres with nested arrays:
```php
$search->where([
['cn', '=', 'John Doe'],
['manager', '!', 'Suzy Doe'],
]);
```
#### Where Starts With
We could also perform a search for all objects beginning with the common name of 'John' using the `starts_with` operator:
```php
$results = $provider->search()->where('cn', 'starts_with', 'John')->get();
// Or use the method whereStartsWith($attribute, $value):
$results = $provider->search()->whereStartsWith('cn', 'John')->get();
```
#### Where Ends With
We can also search for all objects that end with the common name of `Doe` using the `ends_with` operator:
```php
$results = $provider->search()->where('cn', 'ends_with', 'Doe')->get();
// Or use the method whereEndsWith($attribute, $value):
$results = $provider->search()->whereEndsWith('cn', 'Doe')->get();
```
#### Where Between
To search for records between two values, use the `whereBetween` method.
For the example below, we'll retrieve all users who were created between two dates:
```php
$from = (new DateTime('October 1st 2016'))->format('YmdHis.0\Z');
$to = (new DateTime('January 1st 2017'))->format('YmdHis.0\Z');
$users = $provider->search()
->users()
->whereBetween('whencreated', [$from, $to])
->get();
```
#### Where Contains
We can also search for all objects with a common name that contains `John Doe` using the `contains` operator:
```php
$results = $provider->search()->where('cn', 'contains', 'John Doe')->get();
// Or use the method whereContains($attribute, $value):
$results = $provider->search()->whereContains('cn', 'John Doe')->get();
```
##### Where Not Contains
You can use a 'where not contains' to perform the inverse of a 'where contains':
```php
$results = $provider->search()->where('cn', 'not_contains', 'John Doe')->get();
// Or use the method whereNotContains($attribute, $value):
$results = $provider->search()->whereNotContains('cn', 'John Doe');
```
#### Where Has
Or we can retrieve all objects that have a common name attribute using the wildcard operator (`*`):
```php
$results = $provider->search()->where('cn', '*')->get();
// Or use the method whereHas($field):
$results = $provider->search()->whereHas('cn')->get();
```
This type of filter syntax allows you to clearly see what your searching for.
##### Where Not Has
You can use a 'where not has' to perform the inverse of a 'where has':
```php
$results = $provider->search->where('cn', '!*')->get();
// Or use the method whereNotHas($field):
$results = $provider->search()->whereNotHas($field)->get();
```
## Or Wheres
To perform an `or where` clause on the search object, use the `orWhere()` method. However,
please be aware this function performs differently than it would on a database.
For example:
```php
$results = $search
->where('cn', '=', 'John Doe')
->orWhere('cn', '=', 'Suzy Doe')
->get();
```
This query would return no results. Since we're already defining that the common name (`cn`) must equal `John Doe`, applying
the `orWhere()` does not amount to 'Look for an object with the common name as "John Doe" OR "Suzy Doe"'. This query would
actually amount to 'Look for an object with the common name that <b>equals</b> "John Doe" OR "Suzy Doe"
To solve the above problem, we would use `orWhere()` for both fields. For example:
```php
$results = $search
->orWhere('cn', '=', 'John Doe')
->orWhere('cn', '=', 'Suzy Doe')
->get();
```
Now, we'll retrieve both John and Suzy's LDAP records, because the common name can equal either.
> **Note**: You can also use all `where` methods as an or where, for example:
> `orWhereHas()`, `orWhereContains()`, `orWhereStartsWith()`, `orWhereEndsWith()`
## Dynamic Wheres
To perform a dynamic where, simply suffix a `where` with the field you're looking for.
This feature was directly ported from Laravel's Eloquent.
Here's an example:
```php
// This query:
$result = $search->where('cn', '=', 'John Doe')->first();
// Can be converted to:
$result = $search->whereCn('John Doe')->first();
```
You can perform this on **any** attribute:
```php
$result = $search->whereTelephonenumber('555-555-5555')->first();
```
You can also chain them:
```php
$result = $search
->whereTelephonenumber('555-555-5555')
->whereGivenname('John Doe')
->whereSn('Doe')
->first();
```
You can even perform multiple dynamic wheres by separating your fields by an `And`:
```php
// This would perform a search for a user with the
// first name of 'John' and last name of 'Doe'.
$result = $search->whereGivennameAndSn('John', 'Doe')->first();
```
## Nested Filters
By default, the Adldap2 query builder automatically wraps your queries in `and` / `or` filters for you.
However, if any further complexity is required, nested filters allow you
to construct any query fluently and easily.
#### andFilter
The `andFilter` method accepts a closure which allows you to construct a query inside of an `and` LDAP filter:
```php
$query = $provider->search()->newQuery();
// Creates the filter: (&(givenname=John)(sn=Doe))
$results = $query->andFilter(function (Adldap\Query\Builder $q) {
$q->where('givenname', '=', 'John')
->where('sn', '=', 'Doe');
})->get();
```
The above query would return records that contain the first name `John` **and** the last name `Doe`.
#### orFilter
The `orFilter` method accepts a closure which allows you to construct a query inside of an `or` LDAP filter:
```php
$query = $provider->search()->newQuery();
// Creates the filter: (|(givenname=John)(sn=Doe))
$results = $query->orFilter(function (Adldap\Query\Builder $q) {
$q->where('givenname', '=', 'John')
->where('sn', '=', 'Doe');
})->get();
```
The above query would return records that contain the first name `John` **or** the last name `Doe`.
#### notFilter
The `notFilter` method accepts a closure which allows you to construct a query inside a `not` LDAP filter:
```php
$query = $provider->search()->newQuery();
// Creates the filter: (!(givenname=John)(sn=Doe))
$results = $query->notFilter(function (Adldap\Query\Builder $q) {
$q->where('givenname', '=', 'John')
->where('sn', '=', 'Doe');
})->get();
```
The above query would return records that **do not** contain the first name `John` **or** the last name `Doe`.
#### Complex Nesting
The above methods `andFilter` / `orFilter` can be chained together and nested
as many times as you'd like for larger complex queries:
```php
$query = $provider->search()->newQuery();
$query = $query->orFilter(function (Adldap\Query\Builder $q) {
$q->where('givenname', '=', 'John')->where('sn', '=', 'Doe');
})->andFilter(function (Adldap\Query\Builder $q) {
$q->where('department', '=', 'Accounting')->where('title', '=', 'Manager');
})->getUnescapedQuery();
echo $query; // Returns '(&(|(givenname=John)(sn=Doe))(&(department=Accounting)(title=Manager)))'
```
## Raw Filters
> **Note**: Raw filters are not escaped. **Do not** accept user input into the raw filter method.
Sometimes you might just want to add a raw filter without using the query builder.
You can do so by using the `rawFilter()` method:
```php
$filter = '(samaccountname=jdoe)';
$results = $search->rawFilter($filter)->get();
// Or use an array
$filters = [
'(samaccountname=jdoe)',
'(surname=Doe)',
];
$results = $search->rawFilter($filters)->get();
// Or use multiple arguments
$results = $search->rawFilter($filters[0], $filters[1])->get();
// Multiple raw filters will be automatically wrapped into an `and` filter:
$query = $search->getUnescapedQuery();
echo $query; // Returns (&(samaccountname=jdoe)(surname=Doe))
```
## Sorting
Sorting is really useful when your displaying tabular LDAP results. You can
easily perform sorts on any LDAP attribute by using the `sortBy()` method:
```php
$results = $search->whereHas('cn')->sortBy('cn', 'asc')->get();
```
You can also sort paginated results:
```php
$results = $search->whereHas('cn')->sortBy('cn', 'asc')->paginate(25);
```
> **Note**: Sorting occurs *after* results are returned. This is due
> to PHP not having the functionality of sorting records on
> the server side before they are returned.
## Paginating
Paginating your search results will allow you to return more results than
your LDAP cap (usually 1000) and display your results in pages.
> **Note**: Calling `paginate()` will retrieve **all** records from your LDAP server for the current query.
>
> This **does not** operate the same way pagination occurs in a database. Pagination of
> an LDAP query simply allows you to return a larger result set than your
> LDAP servers configured maximum (usually 1000).
>
> The pagination object is simply a collection that allows you to iterate
> through all the resulting records easily and intuitively.
To perform this, call the `paginate()` method instead of the `get()` method:
```php
$recordsPerPage = 50;
$currentPage = $_GET['page'];
// This would retrieve all records from your LDAP server inside a new Adldap\Objects\Paginator instance.
$paginator = $search->paginate($recordsPerPage, $currentPage);
// Returns total number of pages, int
$paginator->getPages();
// Returns current page number, int
$paginator->getCurrentPage();
// Returns the amount of entries allowed per page, int
$paginator->getPerPage();
// Returns all of the results in the entire paginated result
$paginator->getResults();
// Returns the total amount of retrieved entries, int
$paginator->count();
// Iterate over the results like normal
foreach($paginator as $result)
{
echo $result->getCommonName();
}
```
## Scopes
Search scopes allow you to easily retrieve common models of a particular 'scope'.
Each scope simply applies the required filters to the search object
that (when executed) will only return the relevant models.
Here is a list of all available scopes:
```php
// Retrieve all users (Adldap\Models\User).
$results = $search->users()->get();
// Retrieve all printers (Adldap\Models\Printer).
$results = $search->printers()->get();
// Retrieve all organizational units (Adldap\Models\OrganizationalUnit).
$results = $search->ous()->get();
// Retrieve all organizational units (Adldap\Models\OrganizationalUnit).
$results = $search->organizations()->get();
// Retrieve all groups (Adldap\Models\Group).
$results = $search->groups()->get();
// Retrieve all containers (Adldap\Models\Container).
$results = $search->containers()->get();
// Retrieve all contacts (Adldap\Models\Contact).
$results = $search->contacts()->get();
// Retrieve all computers (Adldap\Models\Computer).
$results = $search->computers()->get();
```
## Base DN
To set the base DN of your search you can use one of two methods:
```php
// Using the `in()` method:
$results = $provider->search()->in('ou=Accounting,dc=acme,dc=org')->get();
// Using the `setDn()` method:
$results = $provider->search()->setDn('ou=Accounting,dc=acme,dc=org')->get();
// You can also include `in()` with the scope
$results = $provider->search()->organizations()->in('ou=Accounting,dc=acme,dc=org')->get()
```
Either option will return the same results. Use which ever method you prefer to be more readable.
## Search Options
#### Recursive
By default, all searches performed are recursive.
If you'd like to disable recursive search and perform a single level search, use the `listing()` method:
```php
$result = $provider->search()->listing()->get();
```
This would perform an `ldap_listing()` instead of an `ldap_search()`.
#### Read
If you'd like to perform a read instead of a listing or a recursive search, use the `read()` method:
```php
$result = $provider->search()->read()->where('objectClass', '*')->get();
```
This would perform an `ldap_read()` instead of an `ldap_listing()` or an `ldap_search()`.
> **Note**: Performing a `read()` will always return *one* record in your result.
#### Raw
If you'd like to retrieve the raw LDAP results, use the `raw()` method:
```php
$rawResults = $provider->search()->raw()->where('cn', '=', 'John Doe')->get();
var_dump($rawResults); // Returns an array
```
## Retrieving the ran query
If you'd like to retrieve the current query to save or run it at another
time, use the `getQuery()` method on the query builder.
This will return the escaped filter.
```php
$query = $provider->search()->where('cn', '=', 'John Doe')->getQuery();
echo $query; // Returns '(cn=\4a\6f\68\6e\20\44\6f\65)'
```
To retrieve the unescaped filter, call the `getUnescapedQuery()` method:
```php
$query = $provider->search()->where('cn', '=', 'John Doe')->getUnescapedQuery();
echo $query; // Returns '(cn=John Doe)'
```
Now that you know how to search your directory, lets move onto [creating / modifying LDAP records](models/model.md).

View File

@ -0,0 +1,552 @@
# Setup
## Configuration
To configure your LDAP connections, you can use two methods:
1. Using an array
2. Using a `Adldap\Configuration\DomainConfiguration` object
Either or will produce the same results. Use whichever you feel most comfortable with.
### Using an array
```php
$config = [
'hosts' => [
'DC-01.corp.acme.org',
],
'...'
];
```
### Using a `DomainConfiguration` object
```php
// Setting options via first argument:
$config = new Adldap\Configuration\DomainConfiguration([
'hosts' => [
'DC-01.corp.acme.org',
],
]);
// Setting via the `set()` method:
$config->set('hosts', [
'DC-01.corp.acme.org',
]);
```
### Options
#### Array Example With All Options
```php
// Create the configuration array.
$config = [
// Mandatory Configuration Options
'hosts' => ['corp-dc1.corp.acme.org', 'corp-dc2.corp.acme.org'],
'base_dn' => 'dc=corp,dc=acme,dc=org',
'username' => 'admin',
'password' => 'password',
// Optional Configuration Options
'schema' => Adldap\Schemas\ActiveDirectory::class,
'account_prefix' => 'ACME-',
'account_suffix' => '@acme.org',
'port' => 389,
'follow_referrals' => false,
'use_ssl' => false,
'use_tls' => false,
'version' => 3,
'timeout' => 5,
// Custom LDAP Options
'custom_options' => [
// See: http://php.net/ldap_set_option
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD
]
];
```
#### Required Options
##### Hosts
The hosts option is an array of IP addresses or hostnames located
on your network that serve Active Directory.
You insert as many servers or as little as you'd like depending on your forest (with the minimum of one of course).
> **Note:** Do not append your port to your IP addresses or hostnames. Use the `port` configuration option instead.
##### Base Distinguished Name
The base distinguished name is the base distinguished name you'd like to perform operations on.
An example base DN would be `DC=corp,DC=acme,DC=org`.
If one is not defined, you will not retrieve any search results.
> **Note**: Your base DN is **case insensitive**. You do not need to worry about incorrect casing.
##### Username & Password
To connect to your LDAP server, a username and password is required to be able to query and run operations on your server(s).
You can use any account that has these permissions.
> **Note**: To run administration level operations, such as resetting passwords,
> this account **must** have permissions to do so on your directory.
#### Optional Options
##### Schema
The schema option allows you to configure which directory you're connecting to.
This is a somewhat optional, however this **must** be changed if you're connecting
to an alternate LDAP variant such as OpenLDAP or FreeIPA.
Below are available schemas:
- `Adldap\Schemas\ActiveDirectory`
- `Adldap\Schemas\OpenLDAP`
- `Adldap\Schemas\FreeIPA`
By default, this option is set to the `Adldap\Schemas\ActiveDirectory` schema.
##### Account Prefix
The account prefix option is a string to *prepend* to all usernames that go through the `Guard::attempt()` method.
This option is just for convenience.
It is usually not needed (if utilizing the account suffix), however the functionality is
in place if you would like to only allow certain users with the specified prefix
to login, or add a domain so your users do not have to specify one.
##### Account Suffix
The account suffix option is a string to *append* to all usernames that go
through the `Adldap\Auth\Guard::attempt()` method.
This option is just for convenience.
An example use case for this would be inserting your LDAP users `userPrincipalName` suffix so you don't need to append it manually.
For example, with a `account_suffix` in your configuration set to `@corp.acme.org`:
```php
$username = 'jdoe';
$password = 'password';
// Here, an `ldap_bind()` will be called with a username of 'jdoe@corp.acme.org`
$provider->auth()->attempt($username, $password);
```
##### Port
The port option is used for authenticating and binding to your LDAP server.
The default ports are already used for non SSL and SSL connections (389 and 636).
Only insert a port if your LDAP server uses a unique port.
##### Follow Referrals
The follow referrals option is a boolean to tell active directory to follow a referral to another server on your network if the server queried knows the information your asking for exists, but does not yet contain a copy of it locally.
This option is defaulted to false.
Disable this option if you're experiencing search / connectivity issues.
For more information, visit: https://technet.microsoft.com/en-us/library/cc978014.aspx
##### SSL & TLS
These Boolean options enable an SSL or TLS connection to your LDAP server.
Only **one** can be set to `true`. You must chose either or.
> **Note**: You **must** enable SSL or TLS to reset passwords in ActiveDirectory.
These options are definitely recommended if you have the ability to connect to your server securely.
> **Note**: TLS is recommended over SSL, as SSL is now labelled as a depreciated mechanism for securely running LDAP operations.
##### Version
The LDAP version to use for your connection.
Must be an integer and can either be `2` or `3`.
##### Timeout
The timeout option allows you to configure the amount of seconds to wait until
your application receives a response from your LDAP server.
The default is 5 seconds.
##### Custom Options
Arbitrary options can be set for the connection to fine-tune TLS and connection behavior.
Please note that `LDAP_OPT_PROTOCOL_VERSION`, `LDAP_OPT_NETWORK_TIMEOUT` and `LDAP_OPT_REFERRALS` will be ignored if set.
These are set above with the `version`, `timeout` and `follow_referrals` keys respectively.
Valid options are listed in the [PHP documentation for ldap_set_option](http://php.net/ldap_set_option).
## Getting Started
Each LDAP connection you have will be contained inside the `Adldap` instance as its own **connection provider**.
There are a couple of ways you can easily add each of your LDAP connections. Let's walk through them:
**Using a configuration array:**
```php
$config = ['...'];
$ad = new Adldap\Adldap();
$ad->addProvider($config);
// You can also specify the name of the
// connection as the second argument:
$ad->addProvider($config, 'connection-one');
```
**Using a DomainConfiguration object:**
```php
$ad = new Adldap\Adldap();
$config = new Adldap\Configuration\DomainConfiguration(['...']);
$ad->addProvider($config, 'connection-one');
```
**Using the constructor:**
> **Note**: When inserting your configuration into a new `Adldap` instance, you
> need to set a key for each connection. **This will be its connection name**.
```php
$connections = [
'connection1' => [
'hosts' => ['...'],
],
'connection2' => [
'hosts' => ['...'],
],
];
$ad = new Adldap\Adldap($connections);
```
## Connecting
The easiest way to get connected is to call the `connect($name)` method on your `Adldap` instance.
Its first argument accepts the name of your configured connection.
This method will return you a connected **connection provider** when
successful, and throw an exception when unsuccessful:
```php
$ad = new Adldap\Adldap();
$config = ['...'];
$connectionName = 'my-connection';
$ad->addProvider($config, $connectionName);
try {
$provider = $ad->connect($connectionName);
// Great, we're connected!
} catch (Adldap\Auth\BindException $e) {
// Failed to connect.
}
```
### Using an alternate username / password
If you'd like to connect to your configured connection using a different username and password than your configuration, then simply provide them in the second and third arguments:
```php
$username = 'server-admin';
$password = 'my-super-secret-password';
$provider = $ad->connect($connectionName, $username, $password);
```
### Dynamically Connecting
If you're like me and like chainable (fluent) API's in PHP, then dynamically connecting is a nice option to have.
To dynamically connect, simply call any connection provider method on your `Adldap` instance.
> **Note**: Your default connection will be used when dynamically connecting.
> More on this below.
Here's an example:
```php
$ad = new Adldap\Adldap();
$ad->addProvider($config = ['...']);
try {
$users = $ad->search()->users()->get();
} catch (Adldap\Auth\BindException $e) {
// Failed to connect.
}
```
### Anonymously Binding
If you'd like to anonymously bind, set your `username` and `password` configuration to `null`:
```php
$ad = new Adldap\Adldap();
$config = [
'username' => null,
'password' => null,
];
$ad->addProvider($config);
try {
$provider = $ad->connect();
// ...
} catch (BindException $e) {
// Failed.
}
```
Or, manually bind your provider and don't pass in a `username` or `password` parameter:
```php
$config = [
'hosts' => ['...'],
];
$ad->addProvider($config);
$provider = $ad->getDefaultProvider();
try {
$provider->auth()->bind();
// Successfully bound.
} catch (BindException $e) {
// Failed.
}
```
### Setting a Default Connection
Setting a default LDAP connection is used for dynamically connecting.
To set your default connection, call the `setDefaultProvider($name)` method:
```php
$ad->setDefaultProvider('my-connection');
$computers = $ad->search()->computers()->get();
```
## Authenticating
If you're looking to authenticate (bind) users using your LDAP connection, call
the `auth()->attempt()` method on your provider instance:
```php
$username = 'jdoe';
$password = 'Password@1';
try {
if ($provider->auth()->attempt($username, $password)) {
// Passed.
} else {
// Failed.
}
} catch (Adldap\Auth\UsernameRequiredException $e) {
// The user didn't supply a username.
} catch (Adldap\Auth\PasswordRequiredException $e) {
// The user didn't supply a password.
}
```
If you'd like all LDAP operations during the same request to be ran under the
authenticated user, pass in `true` into the last paramter:
```php
if ($provider->auth()->attempt($username, $password, $bindAsUser = true)) {
// Passed.
} else {
// Failed.
}
```
---
Now that you've learned the basics of configuration and
getting yourself connected, continue on to learn
[how to search your LDAP directory](searching.md).
## Using Other LDAP Servers (OpenLDAP / FreeIPA / etc.)
Alternate LDAP server variants such as OpenLDAP or FreeIPA contain
some different attribute names than ActiveDirectory.
The Adldap2 schema offers an attribute map for each available LDAP attribute, and
is completely configurable and customizable.
If you're using an alternate LDAP server variant such as OpenLDAP or FreeIPA, you **must** change the default schema inside your configuration array. If you do not, you won't receive the correct model instances for results, and you won't be
able to utilize some standard methods available on these models.
By default, Adldap2 is configured to be used with **Microsoft ActiveDirectory**.
When creating your configuration array, set your schema using the `schema` key:
**Using configuration array:**
```php
$ad = new Adldap\Adldap();
$config = [
'...',
'schema' => Adldap\Schemas\OpenLDAP::class
];
$ad->addProvider($config);
```
**Using configuration object:**
```php
$ad = new Adldap\Adldap();
$config = new Adldap\Configuration\DomainConfiguration();
$config->set('schema', Adldap\Schemas\OpenLDAP::class);
$ad->addProvider($config);
```
Once you've set the schema of your connection provider, you can use the same API interacting with different LDAP servers.
Continue onto the [searching](searching.md) documentation to learn how to begin querying your LDAP server(s).
## Using G-Suite Secure LDAP Service
G-Suite LDAP service only uses client certificates and no username + password, make sure yo match base_dn with your domian.
```php
$ad = new \Adldap\Adldap();
// Create a configuration array.
$config = [
'hosts' => ['ldap.google.com'],
'base_dn' => 'dc=your-domain,dc=com',
'use_tls' => true,
'version' => 3,
'schema' => Adldap\Schemas\GSuite::class,
'custom_options' => [
LDAP_OPT_X_TLS_CERTFILE => 'Google_2023_02_05_35779.crt',
LDAP_OPT_X_TLS_KEYFILE => 'Google_2023_02_05_35779.key',
]
];
$ad->addProvider($config);
try {
$provider = $ad->connect();
$results = $provider->search()->ous()->get();
echo 'OUs:'."\r\n";
echo '==============='."\r\n";
foreach($results as $ou) {
echo $ou->getDn()."\r\n";
}
echo "\r\n";
$results = $provider->search()->users()->get();
echo 'Users:'."\r\n";
echo '==============='."\r\n";
foreach($results as $user) {
echo $user->getAccountName()."\r\n";
}
echo "\r\n";
$results = $provider->search()->groups()->get();
echo 'Groups:'."\r\n";
echo '==============='."\r\n";
foreach($results as $group) {
echo $group->getCommonName().' | '.$group->getDisplayName()."\r\n";
}
} catch (\Adldap\Auth\BindException $e) {
echo 'Error: '.$e->getMessage()."\r\n";
}
```
## Raw Operations
### Introduction
If you want to connect to your LDAP server without utilizing Adldap's models (old fashion way), and want to get back the data in a raw format you can easily do so.
If you call `getConnection()` on your connected provider instance, you can perform all LDAP functions on a container class that encapsulates all of PHP's LDAP methods.
You can view all methods avaialble by browsing the LDAP class [here](https://github.com/Adldap2/Adldap2/blob/master/src/Connections/Ldap.php).
Now for some examples:
### Examples
```php
$ad = new Adldap\Adldap();
$config = ['...'];
$ad->addProvider($config);
$provider = $ad->connect();
$rawConnection = $provider->getConnection();
// Performing a raw search.
$result = $rawConnection->search($basedn = 'dc=corp,dc=acme,dc=org', $filter = "cn=johndoe", $selectedAttributes = ['cn', 'department']);
$dn = "cn=John Smith,ou=Wizards,dc=example,dc=com";
// Adding a new LDAP record.
$result = $rawConnection->add($dn, $entry);
// Batch modifying an LDAP record.
$modifs = [
[
"attrib" => "telephoneNumber",
"modtype" => LDAP_MODIFY_BATCH_ADD,
"values" => ["+1 555 555 1717"],
],
];
$result = $rawConnection->modifyBatch($dn, $modifs);
// Deleting an LDAP record.
$result = $rawConnection->delete($dn);
// .. etc
```

View File

@ -0,0 +1,122 @@
# Troubleshooting
#### Creating and Setting a Users Password
To set a users password when you've created a new one, you need to enable their account, **then** set their password.
For example:
```php
// Construct a new user instance.
$user = $provider->make()->user();
// Set the user profile details.
$user->setAccountName('jdoe');
$user->setFirstName('John');
$user->setLastName('Doe');
$user->setCompany('ACME');
$user->setEmail('jdoe@acme.com');
// Save the new user.
if ($user->save()) {
// Enable the new user (using user account control).
$user->setUserAccountControl(512);
// Set new user password
$user->setPassword('Password123');
// Save the user.
if($user->save()) {
// The password was saved successfully.
}
}
```
#### Determining and Troubleshooting a Binding Failure
> **Note**: The below guide is using ActiveDirectory. Your mileage will vary using other LDAP distributions.
To determine the reason why a bind attempt failed, you can use the event dispatcher to listen for
the `Failed` event, and retrieve the errors that were returned from your LDAP server:
```php
use Adldap\Adldap;
use Adldap\Auth\Events\Failed;
$d = Adldap::getEventDispatcher();
$d->listen(Failed::class, function (Failed $event) {
$conn = $event->connection;
echo $conn->getLastError(); // 'Invalid credentials'
echo $conn->getDiagnosticMessage(); // '80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 532, v3839'
if ($error = $conn->getDetailedError()) {
$error->getErrorCode(); // 49
$error->getErrorMessage(); // 'Invalid credentials'
$error->getDiagnosticMessage(); // '80090308: LdapErr: DSID-0C09042A, comment: AcceptSecurityContext error, data 532, v3839'
}
});
```
The above diagnostic message can be parsed down further if needed. The error code after the 'data' string
in the above message indicates several things about the bind failure. Here is a list:
- 525 - user not found
- 52e - invalid credentials
- 530 - not permitted to logon at this time
- 531 - not permitted to logon at this workstation
- 532 - password expired
- 533 - account disabled
- 701 - account expired
- 773 - user must reset password
- 775 - user account locked
From the example above, you can see that the authenticating account has their password expired, due to "532" error code.
#### Retrieving All Records Inside a Group
To retrieve all records inside a particular group (including nested groups), use the `rawFilter()` method:
```php
// The `memberof:1.2.840.113556.1.4.1941:` string indicates
// that we want all nested group records as well.
$filter = '(memberof:1.2.840.113556.1.4.1941:=CN=MyGroup,DC=example,DC=com)';
$users = $provider->search()->rawFilter($filter)->get();
```
#### I'm connected but not getting any search results!
The first thing you need to ensure is your `base_dn` in your configuration.
Your `base_dn` needs to identical to the base DN on your domain. Even one mistyped character will result in no search results.
If you also include an `ou` in your base DN (ex. `ou=Accounting,dc=corp,dc=acme,dc=org`), you will only receive results inside the `Accounting` OU.
Once you're connected to your LDAP server, retrieve the Root DSE record.
Here's a full example:
```php
$providers = [
'default' => [
'base_dn' => '',
'...',
]
];
$ad = new Adldap\Adldap($providers);
try {
$provider = $ad->connect();
$root = $provider->search()->getRootDse();
// ex. Returns 'dc=corp,dc=acme,dc=org'
die($root->getRootDomainNamingContext());
} catch (Adldap\Auth\BindException $e) {
//
}
```

View File

@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright © Steve Bauman
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,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
<testsuites>
<testsuite name="Adldap2 Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,47 @@
<p align="center">
<strong>:wave: Hey there! Looking for something even easier to use for LDAP integration in your PHP applications?</strong>
</br>
<h3 align="center">
🎉 Introducing <a href="https://github.com/DirectoryTree/LdapRecord" target="_blank" title="LdapRecord GitHub Repository">LdapRecord</a> 🎉
</h3>
</p>
<p align="center">
<strong>
<a href="https://ldaprecord.com">LdapRecord</a> is the successor to Adldap2 - and comes with a ton of new features.
</strong> </br> Adldap2 will continue to be supported with bug fixes, <i>but will not receive new features.</i>
</p>
<p align="center">
<strong>
<a href="https://stevebauman.ca/why-ldap-record/">Read Why</a>
</strong>
</p>
<hr/>
<h1 align="center">Adldap2</h1>
<p align="center">
<a href="https://travis-ci.org/Adldap2/Adldap2"><img src="https://img.shields.io/travis/Adldap2/Adldap2.svg?style=flat-square"/></a>
<a href="https://scrutinizer-ci.com/g/Adldap2/Adldap2/?branch=master"><img src="https://img.shields.io/scrutinizer/g/adLDAP2/adLDAP2/master.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/dt/adldap2/adldap2.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/v/adldap2/adldap2.svg?style=flat-square"/></a>
<a href="https://packagist.org/packages/adldap2/adldap2"><img src="https://img.shields.io/packagist/l/adldap2/adldap2.svg?style=flat-square"/></a>
</p>
<p align="center">
Adldap2 is a PHP package that provides LDAP authentication and directory management tools using the <a href="https://en.wikipedia.org/wiki/Active_record_pattern">Active Record pattern</a>.
</p>
<h4 align="center">
<a href="http://adldap2.github.io/Adldap2/#/?id=quick-start">Quickstart</a>
<span> · </span>
<a href="http://adldap2.github.io/Adldap2/">Documentation</a>
</h4>
- **Up and running in minutes.** Effortlessly connect to your LDAP servers and start running queries & operations in a matter of minutes.
- **Fluent query builder.** Building LDAP queries has never been so easy. Find the records you're looking for in a couple lines or less with a fluent interface.
- **Supercharged Active Record.** Create and modify LDAP records with ease. All LDAP records are individual models. Simply modify the attributes on the model and save it to persist the changes to your LDAP server.

View File

@ -0,0 +1,194 @@
<?php
namespace Adldap;
use Adldap\Log\EventLogger;
use Adldap\Connections\Ldap;
use InvalidArgumentException;
use Adldap\Log\LogsInformation;
use Adldap\Connections\Provider;
use Adldap\Events\DispatchesEvents;
use Adldap\Connections\ProviderInterface;
use Adldap\Connections\ConnectionInterface;
use Adldap\Configuration\DomainConfiguration;
class Adldap implements AdldapInterface
{
use DispatchesEvents;
use LogsInformation;
/**
* The default provider name.
*
* @var string
*/
protected $default = 'default';
/**
* The connection providers.
*
* @var array
*/
protected $providers = [];
/**
* The events to register listeners for during initialization.
*
* @var array
*/
protected $listen = [
'Adldap\Auth\Events\*',
'Adldap\Query\Events\*',
'Adldap\Models\Events\*',
];
/**
* {@inheritdoc}
*/
public function __construct(array $providers = [])
{
foreach ($providers as $name => $config) {
$this->addProvider($config, $name);
}
if ($default = key($providers)) {
$this->setDefaultProvider($default);
}
$this->initEventLogger();
}
/**
* {@inheritdoc}
*/
public function addProvider($config, $name = 'default', ConnectionInterface $connection = null)
{
if ($this->isValidConfig($config)) {
$config = new Provider($config, $connection ?? new Ldap($name));
}
if ($config instanceof ProviderInterface) {
$this->providers[$name] = $config;
return $this;
}
throw new InvalidArgumentException(
"You must provide a configuration array or an instance of Adldap\Connections\ProviderInterface."
);
}
/**
* Determines if the given config is valid.
*
* @param mixed $config
*
* @return bool
*/
protected function isValidConfig($config)
{
return is_array($config) || $config instanceof DomainConfiguration;
}
/**
* {@inheritdoc}
*/
public function getProviders()
{
return $this->providers;
}
/**
* {@inheritdoc}
*/
public function getProvider($name)
{
if (array_key_exists($name, $this->providers)) {
return $this->providers[$name];
}
throw new AdldapException("The connection provider '$name' does not exist.");
}
/**
* {@inheritdoc}
*/
public function setDefaultProvider($name = 'default')
{
if ($this->getProvider($name) instanceof ProviderInterface) {
$this->default = $name;
}
}
/**
* {@inheritdoc}
*/
public function getDefaultProvider()
{
return $this->getProvider($this->default);
}
/**
* {@inheritdoc}
*/
public function removeProvider($name)
{
unset($this->providers[$name]);
return $this;
}
/**
* {@inheritdoc}
*/
public function connect($name = null, $username = null, $password = null)
{
$provider = $name ? $this->getProvider($name) : $this->getDefaultProvider();
return $provider->connect($username, $password);
}
/**
* {@inheritdoc}
*/
public function __call($method, $parameters)
{
$provider = $this->getDefaultProvider();
if (! $provider->getConnection()->isBound()) {
$provider->connect();
}
return call_user_func_array([$provider, $method], $parameters);
}
/**
* Initializes the event logger.
*
* @return void
*/
public function initEventLogger()
{
$dispatcher = static::getEventDispatcher();
$logger = $this->newEventLogger();
// We will go through each of our event wildcards and register their listener.
foreach ($this->listen as $event) {
$dispatcher->listen($event, function ($eventName, $events) use ($logger) {
foreach ($events as $event) {
$logger->log($event);
}
});
}
}
/**
* Returns a new event logger instance.
*
* @return EventLogger
*/
protected function newEventLogger()
{
return new EventLogger(static::getLogger());
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap;
class AdldapException extends \Exception
{
//
}

View File

@ -0,0 +1,91 @@
<?php
namespace Adldap;
use Adldap\Connections\ProviderInterface;
use Adldap\Connections\ConnectionInterface;
interface AdldapInterface
{
/**
* Add a provider by the specified name.
*
* @param mixed $configuration
* @param string $name
* @param ConnectionInterface $connection
*
* @throws \InvalidArgumentException When an invalid type is given as the configuration argument.
*
* @return $this
*/
public function addProvider($configuration, $name, ConnectionInterface $connection = null);
/**
* Returns all of the connection providers.
*
* @return array
*/
public function getProviders();
/**
* Retrieves a Provider using its specified name.
*
* @param string $name
*
* @throws AdldapException When the specified provider does not exist.
*
* @return ProviderInterface
*/
public function getProvider($name);
/**
* Sets the default provider.
*
* @param string $name
*
* @throws AdldapException When the specified provider does not exist.
*/
public function setDefaultProvider($name);
/**
* Retrieves the first default provider.
*
* @throws AdldapException When no default provider exists.
*
* @return ProviderInterface
*/
public function getDefaultProvider();
/**
* Removes a provider by the specified name.
*
* @param string $name
*
* @return $this
*/
public function removeProvider($name);
/**
* Connects to the specified provider.
*
* If no username and password is given, then the providers
* configured admin credentials are used.
*
* @param string|null $name
* @param string|null $username
* @param string|null $password
*
* @return ProviderInterface
*/
public function connect($name = null, $username = null, $password = null);
/**
* Call methods upon the default provider dynamically.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*/
public function __call($method, $parameters);
}

View File

@ -0,0 +1,45 @@
<?php
namespace Adldap\Auth;
use Adldap\AdldapException;
use Adldap\Connections\DetailedError;
/**
* Class BindException.
*
* Thrown when binding to an LDAP connection fails.
*/
class BindException extends AdldapException
{
/**
* The detailed LDAP error.
*
* @var DetailedError
*/
protected $detailedError;
/**
* Sets the detailed error.
*
* @param DetailedError|null $error
*
* @return $this
*/
public function setDetailedError(DetailedError $error = null)
{
$this->detailedError = $error;
return $this;
}
/**
* Returns the detailed error.
*
* @return DetailedError|null
*/
public function getDetailedError()
{
return $this->detailedError;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Auth\Events;
class Attempting extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Auth\Events;
class Binding extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Auth\Events;
class Bound extends Event
{
//
}

View File

@ -0,0 +1,73 @@
<?php
namespace Adldap\Auth\Events;
use Adldap\Connections\ConnectionInterface;
abstract class Event
{
/**
* The connection that the username and password is being bound on.
*
* @var ConnectionInterface
*/
protected $connection;
/**
* The username that is being used for binding.
*
* @var string
*/
protected $username;
/**
* The password that is being used for binding.
*
* @var string
*/
protected $password;
/**
* Constructor.
*
* @param ConnectionInterface $connection
* @param string $username
* @param string $password
*/
public function __construct(ConnectionInterface $connection, $username, $password)
{
$this->connection = $connection;
$this->username = $username;
$this->password = $password;
}
/**
* Returns the events connection.
*
* @return ConnectionInterface
*/
public function getConnection()
{
return $this->connection;
}
/**
* Returns the authentication events username.
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Returns the authentication events password.
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Auth\Events;
class Failed extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Auth\Events;
class Passed extends Event
{
//
}

View File

@ -0,0 +1,259 @@
<?php
namespace Adldap\Auth;
use Exception;
use Throwable;
use Adldap\Auth\Events\Bound;
use Adldap\Auth\Events\Failed;
use Adldap\Auth\Events\Passed;
use Adldap\Auth\Events\Binding;
use Adldap\Auth\Events\Attempting;
use Adldap\Events\DispatcherInterface;
use Adldap\Connections\ConnectionInterface;
use Adldap\Configuration\DomainConfiguration;
/**
* Class Guard.
*
* Binds users to the current connection.
*/
class Guard implements GuardInterface
{
/**
* The connection to bind to.
*
* @var ConnectionInterface
*/
protected $connection;
/**
* The domain configuration to utilize.
*
* @var DomainConfiguration
*/
protected $configuration;
/**
* The event dispatcher.
*
* @var DispatcherInterface
*/
protected $events;
/**
* {@inheritdoc}
*/
public function __construct(ConnectionInterface $connection, DomainConfiguration $configuration)
{
$this->connection = $connection;
$this->configuration = $configuration;
}
/**
* {@inheritdoc}
*/
public function attempt($username, $password, $bindAsUser = false)
{
$this->validateCredentials($username, $password);
$this->fireAttemptingEvent($username, $password);
try {
$this->bind(
$this->applyPrefixAndSuffix($username),
$password
);
$result = true;
$this->firePassedEvent($username, $password);
} catch (BindException $e) {
// We'll catch the BindException here to allow
// developers to use a simple if / else
// using the attempt method.
$result = false;
}
// If we're not allowed to bind as the user,
// we'll rebind as administrator.
if ($bindAsUser === false) {
// We won't catch any BindException here so we can
// catch rebind failures. However this shouldn't
// occur if our credentials are correct
// in the first place.
$this->bindAsAdministrator();
}
return $result;
}
/**
* {@inheritdoc}
*/
public function bind($username = null, $password = null)
{
$this->fireBindingEvent($username, $password);
try {
if (@$this->connection->bind($username, $password) === true) {
$this->fireBoundEvent($username, $password);
} else {
throw new Exception($this->connection->getLastError(), $this->connection->errNo());
}
} catch (Throwable $e) {
$this->fireFailedEvent($username, $password);
throw (new BindException($e->getMessage(), $e->getCode(), $e))
->setDetailedError($this->connection->getDetailedError());
}
}
/**
* {@inheritdoc}
*/
public function bindAsAdministrator()
{
$this->bind(
$this->configuration->get('username'),
$this->configuration->get('password')
);
}
/**
* Get the event dispatcher instance.
*
* @return DispatcherInterface
*/
public function getDispatcher()
{
return $this->events;
}
/**
* Sets the event dispatcher instance.
*
* @param DispatcherInterface $dispatcher
*
* @return void
*/
public function setDispatcher(DispatcherInterface $dispatcher)
{
$this->events = $dispatcher;
}
/**
* Applies the prefix and suffix to the given username.
*
* @param string $username
*
* @throws \Adldap\Configuration\ConfigurationException If account_suffix or account_prefix do not
* exist in the providers domain configuration
*
* @return string
*/
protected function applyPrefixAndSuffix($username)
{
$prefix = $this->configuration->get('account_prefix');
$suffix = $this->configuration->get('account_suffix');
return $prefix.$username.$suffix;
}
/**
* Validates the specified username and password from being empty.
*
* @param string $username
* @param string $password
*
* @throws PasswordRequiredException When the given password is empty.
* @throws UsernameRequiredException When the given username is empty.
*/
protected function validateCredentials($username, $password)
{
if (empty($username)) {
// Check for an empty username.
throw new UsernameRequiredException('A username must be specified.');
}
if (empty($password)) {
// Check for an empty password.
throw new PasswordRequiredException('A password must be specified.');
}
}
/**
* Fire the attempting event.
*
* @param string $username
* @param string $password
*
* @return void
*/
protected function fireAttemptingEvent($username, $password)
{
if (isset($this->events)) {
$this->events->fire(new Attempting($this->connection, $username, $password));
}
}
/**
* Fire the passed event.
*
* @param string $username
* @param string $password
*
* @return void
*/
protected function firePassedEvent($username, $password)
{
if (isset($this->events)) {
$this->events->fire(new Passed($this->connection, $username, $password));
}
}
/**
* Fire the failed event.
*
* @param string $username
* @param string $password
*
* @return void
*/
protected function fireFailedEvent($username, $password)
{
if (isset($this->events)) {
$this->events->fire(new Failed($this->connection, $username, $password));
}
}
/**
* Fire the binding event.
*
* @param string $username
* @param string $password
*
* @return void
*/
protected function fireBindingEvent($username, $password)
{
if (isset($this->events)) {
$this->events->fire(new Binding($this->connection, $username, $password));
}
}
/**
* Fire the bound event.
*
* @param string $username
* @param string $password
*
* @return void
*/
protected function fireBoundEvent($username, $password)
{
if (isset($this->events)) {
$this->events->fire(new Bound($this->connection, $username, $password));
}
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Adldap\Auth;
use Adldap\Connections\ConnectionInterface;
use Adldap\Configuration\DomainConfiguration;
interface GuardInterface
{
/**
* Constructor.
*
* @param ConnectionInterface $connection
* @param DomainConfiguration $configuration
*/
public function __construct(ConnectionInterface $connection, DomainConfiguration $configuration);
/**
* Authenticates a user using the specified credentials.
*
* @param string $username The users LDAP username.
* @param string $password The users LDAP password.
* @param bool $bindAsUser Whether or not to bind as the user.
*
* @throws \Adldap\Auth\BindException When re-binding to your LDAP server fails.
* @throws \Adldap\Auth\UsernameRequiredException When username is empty.
* @throws \Adldap\Auth\PasswordRequiredException When password is empty.
*
* @return bool
*/
public function attempt($username, $password, $bindAsUser = false);
/**
* Binds to the current connection using the inserted credentials.
*
* @param string|null $username
* @param string|null $password
*
* @throws \Adldap\Auth\BindException If binding to the LDAP server fails.
* @throws \Adldap\Connections\ConnectionException If upgrading the connection to TLS fails
*
* @return void
*/
public function bind($username = null, $password = null);
/**
* Binds to the current LDAP server using the
* configuration administrator credentials.
*
* @throws \Adldap\Auth\BindException When binding as your administrator account fails.
*
* @return void
*/
public function bindAsAdministrator();
}

View File

@ -0,0 +1,10 @@
<?php
namespace Adldap\Auth;
use Adldap\AdldapException;
class PasswordRequiredException extends AdldapException
{
//
}

View File

@ -0,0 +1,10 @@
<?php
namespace Adldap\Auth;
use Adldap\AdldapException;
class UsernameRequiredException extends AdldapException
{
//
}

View File

@ -0,0 +1,16 @@
<?php
namespace Adldap\Configuration;
use Adldap\AdldapException;
/**
* Class ConfigurationException.
*
* Thrown when a configuration value does not exist, or a
* configuration value being set is not valid.
*/
class ConfigurationException extends AdldapException
{
//
}

View File

@ -0,0 +1,161 @@
<?php
namespace Adldap\Configuration;
use Adldap\Schemas\ActiveDirectory;
use Adldap\Connections\ConnectionInterface;
/**
* Class DomainConfiguration.
*
* Contains an array of configuration options for a single LDAP connection.
*/
class DomainConfiguration
{
/**
* The configuration options array.
*
* The default values for each key indicate the type of value it requires.
*
* @var array
*/
protected $options = [
// An array of LDAP hosts.
'hosts' => [],
// The global LDAP operation timeout limit in seconds.
'timeout' => 5,
// The LDAP version to utilize.
'version' => 3,
// The port to use for connecting to your hosts.
'port' => ConnectionInterface::PORT,
// The schema to use for your LDAP connection.
'schema' => ActiveDirectory::class,
// The base distinguished name of your domain.
'base_dn' => '',
// The username to connect to your hosts with.
'username' => '',
// The password that is utilized with the above user.
'password' => '',
// The account prefix to use when authenticating users.
'account_prefix' => null,
// The account suffix to use when authenticating users.
'account_suffix' => null,
// Whether or not to use SSL when connecting to your hosts.
'use_ssl' => false,
// Whether or not to use TLS when connecting to your hosts.
'use_tls' => false,
// Whether or not follow referrals is enabled when performing LDAP operations.
'follow_referrals' => false,
// Custom LDAP options that you'd like to utilize.
'custom_options' => [],
];
/**
* Constructor.
*
* @param array $options
*
* @throws ConfigurationException When an option value given is an invalid type.
*/
public function __construct(array $options = [])
{
foreach ($options as $key => $value) {
$this->set($key, $value);
}
}
/**
* Sets a configuration option.
*
* Throws an exception if the specified option does
* not exist, or if it's an invalid type.
*
* @param string $key
* @param mixed $value
*
* @throws ConfigurationException When an option value given is an invalid type.
*/
public function set($key, $value)
{
if ($this->validate($key, $value)) {
$this->options[$key] = $value;
}
}
/**
* Returns the value for the specified configuration options.
*
* Throws an exception if the specified option does not exist.
*
* @param string $key
*
* @throws ConfigurationException When the option specified does not exist.
*
* @return mixed
*/
public function get($key)
{
if ($this->has($key)) {
return $this->options[$key];
}
throw new ConfigurationException("Option {$key} does not exist.");
}
/**
* Checks if a configuration option exists.
*
* @param string $key
*
* @return bool
*/
public function has($key)
{
return array_key_exists($key, $this->options);
}
/**
* Validates the new configuration option against its
* default value to ensure it's the correct type.
*
* If an invalid type is given, an exception is thrown.
*
* @param string $key
* @param mixed $value
*
* @throws ConfigurationException When an option value given is an invalid type.
*
* @return bool
*/
protected function validate($key, $value)
{
$default = $this->get($key);
if (is_array($default)) {
$validator = new Validators\ArrayValidator($key, $value);
} elseif (is_int($default)) {
$validator = new Validators\IntegerValidator($key, $value);
} elseif (is_bool($default)) {
$validator = new Validators\BooleanValidator($key, $value);
} elseif (class_exists($default)) {
$validator = new Validators\ClassValidator($key, $value);
} else {
$validator = new Validators\StringOrNullValidator($key, $value);
}
return $validator->validate();
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Adldap\Configuration\Validators;
use Adldap\Configuration\ConfigurationException;
/**
* Class ArrayValidator.
*
* Validates that the configuration value is an array.
*/
class ArrayValidator extends Validator
{
/**
* {@inheritdoc}
*/
public function validate()
{
if (!is_array($this->value)) {
throw new ConfigurationException("Option {$this->key} must be an array.");
}
return true;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Adldap\Configuration\Validators;
use Adldap\Configuration\ConfigurationException;
/**
* Class BooleanValidator.
*
* Validates that the configuration value is a boolean.
*/
class BooleanValidator extends Validator
{
/**
* {@inheritdoc}
*/
public function validate()
{
if (!is_bool($this->value)) {
throw new ConfigurationException("Option {$this->key} must be a boolean.");
}
return true;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Adldap\Configuration\Validators;
use Adldap\Configuration\ConfigurationException;
class ClassValidator extends Validator
{
/**
* Validates the configuration value.
*
* @throws ConfigurationException When the value given fails validation.
*
* @return bool
*/
public function validate()
{
if (!class_exists($this->value)) {
throw new ConfigurationException("Option {$this->key} must be a valid class.");
}
return true;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Adldap\Configuration\Validators;
use Adldap\Configuration\ConfigurationException;
/**
* Class IntegerValidator.
*
* Validates that the configuration value is an integer / number.
*/
class IntegerValidator extends Validator
{
/**
* {@inheritdoc}
*/
public function validate()
{
if (!is_numeric($this->value)) {
throw new ConfigurationException("Option {$this->key} must be an integer.");
}
return true;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Adldap\Configuration\Validators;
use Adldap\Configuration\ConfigurationException;
/**
* Class StringOrNullValidator.
*
* Validates that the configuration value is a string or null.
*/
class StringOrNullValidator extends Validator
{
/**
* {@inheritdoc}
*/
public function validate()
{
if (is_string($this->value) || is_null($this->value)) {
return true;
}
throw new ConfigurationException("Option {$this->key} must be a string or null.");
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Adldap\Configuration\Validators;
/**
* Class Validator.
*
* Validates configuration values.
*/
abstract class Validator
{
/**
* The configuration key under validation.
*
* @var string
*/
protected $key;
/**
* The configuration value under validation.
*
* @var mixed
*/
protected $value;
/**
* Constructor.
*
* @param string $key
* @param mixed $value
*/
public function __construct($key, $value)
{
$this->key = $key;
$this->value = $value;
}
/**
* Validates the configuration value.
*
* @throws \Adldap\Configuration\ConfigurationException When the value given fails validation.
*
* @return bool
*/
abstract public function validate();
}

View File

@ -0,0 +1,10 @@
<?php
namespace Adldap\Connections;
use Adldap\AdldapException;
class ConnectionException extends AdldapException
{
//
}

View File

@ -0,0 +1,539 @@
<?php
namespace Adldap\Connections;
/**
* The Connection interface used for making connections. Implementing
* this interface on connection classes helps unit and functional
* test classes that require a connection.
*
* Interface ConnectionInterface
*/
interface ConnectionInterface
{
/**
* The SSL LDAP protocol string.
*
* @var string
*/
const PROTOCOL_SSL = 'ldaps://';
/**
* The standard LDAP protocol string.
*
* @var string
*/
const PROTOCOL = 'ldap://';
/**
* The LDAP SSL port number.
*
* @var string
*/
const PORT_SSL = 636;
/**
* The standard LDAP port number.
*
* @var string
*/
const PORT = 389;
/**
* Constructor.
*
* @param string|null $name The connection name.
*/
public function __construct($name = null);
/**
* Returns true / false if the current connection instance is using SSL.
*
* @return bool
*/
public function isUsingSSL();
/**
* Returns true / false if the current connection instance is using TLS.
*
* @return bool
*/
public function isUsingTLS();
/**
* Returns true / false if the current connection is able to modify passwords.
*
* @return bool
*/
public function canChangePasswords();
/**
* Returns true / false if the current connection is bound.
*
* @return bool
*/
public function isBound();
/**
* Sets the current connection to use SSL.
*
* @param bool $enabled
*
* @return ConnectionInterface
*/
public function ssl($enabled = true);
/**
* Sets the current connection to use TLS.
*
* @param bool $enabled
*
* @return ConnectionInterface
*/
public function tls($enabled = true);
/**
* Returns the full LDAP host URL.
*
* Ex: ldap://192.168.1.1:386
*
* @return string|null
*/
public function getHost();
/**
* Returns the connections name.
*
* @return string|null
*/
public function getName();
/**
* Get the current connection.
*
* @return mixed
*/
public function getConnection();
/**
* Retrieve the entries from a search result.
*
* @link http://php.net/manual/en/function.ldap-get-entries.php
*
* @param $searchResult
*
* @return mixed
*/
public function getEntries($searchResult);
/**
* Returns the number of entries from a search result.
*
* @link http://php.net/manual/en/function.ldap-count-entries.php
*
* @param $searchResult
*
* @return int
*/
public function countEntries($searchResult);
/**
* Compare value of attribute found in entry specified with DN.
*
* @link http://php.net/manual/en/function.ldap-compare.php
*
* @param string $dn
* @param string $attribute
* @param string $value
*
* @return mixed
*/
public function compare($dn, $attribute, $value);
/**
* Retrieves the first entry from a search result.
*
* @link http://php.net/manual/en/function.ldap-first-entry.php
*
* @param $searchResult
*
* @return mixed
*/
public function getFirstEntry($searchResult);
/**
* Retrieves the next entry from a search result.
*
* @link http://php.net/manual/en/function.ldap-next-entry.php
*
* @param $entry
*
* @return mixed
*/
public function getNextEntry($entry);
/**
* Retrieves the ldap entry's attributes.
*
* @link http://php.net/manual/en/function.ldap-get-attributes.php
*
* @param $entry
*
* @return mixed
*/
public function getAttributes($entry);
/**
* Retrieve the last error on the current connection.
*
* @link http://php.net/manual/en/function.ldap-error.php
*
* @return string
*/
public function getLastError();
/**
* Return detailed information about an error.
*
* Returns false when there was a successful last request.
*
* Returns DetailedError when there was an error.
*
* @return DetailedError|null
*/
public function getDetailedError();
/**
* Get all binary values from the specified result entry.
*
* @link http://php.net/manual/en/function.ldap-get-values-len.php
*
* @param $entry
* @param $attribute
*
* @return array
*/
public function getValuesLen($entry, $attribute);
/**
* Sets an option on the current connection.
*
* @link http://php.net/manual/en/function.ldap-set-option.php
*
* @param int $option
* @param mixed $value
*
* @return mixed
*/
public function setOption($option, $value);
/**
* Sets options on the current connection.
*
* @param array $options
*
* @return mixed
*/
public function setOptions(array $options = []);
/**
* Set a callback function to do re-binds on referral chasing.
*
* @link http://php.net/manual/en/function.ldap-set-rebind-proc.php
*
* @param callable $callback
*
* @return bool
*/
public function setRebindCallback(callable $callback);
/**
* Connects to the specified hostname using the specified port.
*
* @link http://php.net/manual/en/function.ldap-start-tls.php
*
* @param string|array $hostname
* @param int $port
*
* @return mixed
*/
public function connect($hostname = [], $port = 389);
/**
* Starts a connection using TLS.
*
* @link http://php.net/manual/en/function.ldap-start-tls.php
*
* @throws ConnectionException If starting TLS fails.
*
* @return mixed
*/
public function startTLS();
/**
* Binds to the current connection using the specified username and password.
* If sasl is true, the current connection is bound using SASL.
*
* @link http://php.net/manual/en/function.ldap-bind.php
*
* @param string $username
* @param string $password
* @param bool $sasl
*
* @throws ConnectionException If starting TLS fails.
*
* @return bool
*/
public function bind($username, $password, $sasl = false);
/**
* Closes the current connection.
*
* Returns false if no connection is present.
*
* @link http://php.net/manual/en/function.ldap-close.php
*
* @return bool
*/
public function close();
/**
* Performs a search on the current connection.
*
* @link 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
*
* @return mixed
*/
public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0);
/**
* Reads an entry on the current connection.
*
* @link http://php.net/manual/en/function.ldap-read.php
*
* @param string $dn
* @param $filter
* @param array $fields
* @param bool $onlyAttributes
* @param int $size
* @param int $time
*
* @return mixed
*/
public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0);
/**
* Performs a single level search on the current connection.
*
* @link http://php.net/manual/en/function.ldap-list.php
*
* @param string $dn
* @param string $filter
* @param array $attributes
* @param bool $onlyAttributes
* @param int $size
* @param int $time
*
* @return mixed
*/
public function listing($dn, $filter, array $attributes, $onlyAttributes = false, $size = 0, $time = 0);
/**
* Adds an entry to the current connection.
*
* @link http://php.net/manual/en/function.ldap-add.php
*
* @param string $dn
* @param array $entry
*
* @return bool
*/
public function add($dn, array $entry);
/**
* Deletes an entry on the current connection.
*
* @link http://php.net/manual/en/function.ldap-delete.php
*
* @param string $dn
*
* @return bool
*/
public function delete($dn);
/**
* Modify the name of an entry on the current connection.
*
* @link http://php.net/manual/en/function.ldap-rename.php
*
* @param string $dn
* @param string $newRdn
* @param string $newParent
* @param bool $deleteOldRdn
*
* @return bool
*/
public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false);
/**
* Modifies an existing entry on the current connection.
*
* @link http://php.net/manual/en/function.ldap-modify.php
*
* @param string $dn
* @param array $entry
*
* @return bool
*/
public function modify($dn, array $entry);
/**
* Batch modifies an existing entry on the current connection.
*
* @link http://php.net/manual/en/function.ldap-modify-batch.php
*
* @param string $dn
* @param array $values
*
* @return mixed
*/
public function modifyBatch($dn, array $values);
/**
* Add attribute values to current attributes.
*
* @link http://php.net/manual/en/function.ldap-mod-add.php
*
* @param string $dn
* @param array $entry
*
* @return mixed
*/
public function modAdd($dn, array $entry);
/**
* Replaces attribute values with new ones.
*
* @link http://php.net/manual/en/function.ldap-mod-replace.php
*
* @param string $dn
* @param array $entry
*
* @return mixed
*/
public function modReplace($dn, array $entry);
/**
* Delete attribute values from current attributes.
*
* @link http://php.net/manual/en/function.ldap-mod-del.php
*
* @param string $dn
* @param array $entry
*
* @return mixed
*/
public function modDelete($dn, array $entry);
/**
* Send LDAP pagination control.
*
* @link http://php.net/manual/en/function.ldap-control-paged-result.php
*
* @param int $pageSize
* @param bool $isCritical
* @param string $cookie
*
* @return mixed
*/
public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '');
/**
* Retrieve the LDAP pagination cookie.
*
* @link http://php.net/manual/en/function.ldap-control-paged-result-response.php
*
* @param $result
* @param string $cookie
*
* @return mixed
*/
public function controlPagedResultResponse($result, &$cookie);
/**
* Frees up the memory allocated internally to store the result.
*
* @link https://www.php.net/manual/en/function.ldap-free-result.php
*
* @param resource $result
*
* @return bool
*/
public function freeResult($result);
/**
* Returns the error number of the last command
* executed on the current connection.
*
* @link http://php.net/manual/en/function.ldap-errno.php
*
* @return int
*/
public function errNo();
/**
* Returns the extended error string of the last command.
*
* @return string
*/
public function getExtendedError();
/**
* Returns the extended error hex code of the last command.
*
* @return string|null
*/
public function getExtendedErrorHex();
/**
* Returns the extended error code of the last command.
*
* @return string
*/
public function getExtendedErrorCode();
/**
* Returns the error string of the specified
* error number.
*
* @link http://php.net/manual/en/function.ldap-err2str.php
*
* @param int $number
*
* @return string
*/
public function err2Str($number);
/**
* Return the diagnostic Message.
*
* @return string
*/
public function getDiagnosticMessage();
/**
* Extract the diagnostic code from the message.
*
* @param string $message
*
* @return string|bool
*/
public function extractDiagnosticCode($message);
}

View File

@ -0,0 +1,71 @@
<?php
namespace Adldap\Connections;
class DetailedError
{
/**
* The error code from ldap_errno.
*
* @var int|null
*/
protected $errorCode;
/**
* The error message from ldap_error.
*
* @var string|null
*/
protected $errorMessage;
/**
* The diagnostic message when retrieved after an ldap_error.
*
* @var string|null
*/
protected $diagnosticMessage;
/**
* Constructor.
*
* @param int $errorCode
* @param string $errorMessage
* @param string $diagnosticMessage
*/
public function __construct($errorCode, $errorMessage, $diagnosticMessage)
{
$this->errorCode = $errorCode;
$this->errorMessage = $errorMessage;
$this->diagnosticMessage = $diagnosticMessage;
}
/**
* Returns the LDAP error code.
*
* @return int
*/
public function getErrorCode()
{
return $this->errorCode;
}
/**
* Returns the LDAP error message.
*
* @return string
*/
public function getErrorMessage()
{
return $this->errorMessage;
}
/**
* Returns the LDAP diagnostic message.
*
* @return string
*/
public function getDiagnosticMessage()
{
return $this->diagnosticMessage;
}
}

View File

@ -0,0 +1,545 @@
<?php
namespace Adldap\Connections;
/**
* Class Ldap.
*
* A class that abstracts PHP's LDAP functions and stores the bound connection.
*/
class Ldap implements ConnectionInterface
{
/**
* The connection name.
*
* @var string|null
*/
protected $name;
/**
* The LDAP host that is currently connected.
*
* @var string|null
*/
protected $host;
/**
* The active LDAP connection.
*
* @var resource
*/
protected $connection;
/**
* The bound status of the connection.
*
* @var bool
*/
protected $bound = false;
/**
* Whether the connection must be bound over SSL.
*
* @var bool
*/
protected $useSSL = false;
/**
* Whether the connection must be bound over TLS.
*
* @var bool
*/
protected $useTLS = false;
/**
* {@inheritdoc}
*/
public function __construct($name = null)
{
$this->name = $name;
}
/**
* {@inheritdoc}
*/
public function isUsingSSL()
{
return $this->useSSL;
}
/**
* {@inheritdoc}
*/
public function isUsingTLS()
{
return $this->useTLS;
}
/**
* {@inheritdoc}
*/
public function isBound()
{
return $this->bound;
}
/**
* {@inheritdoc}
*/
public function canChangePasswords()
{
return $this->isUsingSSL() || $this->isUsingTLS();
}
/**
* {@inheritdoc}
*/
public function ssl($enabled = true)
{
$this->useSSL = $enabled;
return $this;
}
/**
* {@inheritdoc}
*/
public function tls($enabled = true)
{
$this->useTLS = $enabled;
return $this;
}
/**
* {@inheritdoc}
*/
public function getHost()
{
return $this->host;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->connection;
}
/**
* {@inheritdoc}
*/
public function getEntries($searchResults)
{
return ldap_get_entries($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function getFirstEntry($searchResults)
{
return ldap_first_entry($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function getNextEntry($entry)
{
return ldap_next_entry($this->connection, $entry);
}
/**
* {@inheritdoc}
*/
public function getAttributes($entry)
{
return ldap_get_attributes($this->connection, $entry);
}
/**
* {@inheritdoc}
*/
public function countEntries($searchResults)
{
return ldap_count_entries($this->connection, $searchResults);
}
/**
* {@inheritdoc}
*/
public function compare($dn, $attribute, $value)
{
return ldap_compare($this->connection, $dn, $attribute, $value);
}
/**
* {@inheritdoc}
*/
public function getLastError()
{
return ldap_error($this->connection);
}
/**
* {@inheritdoc}
*/
public function getDetailedError()
{
// If the returned error number is zero, the last LDAP operation
// succeeded. We won't return a detailed error.
if ($number = $this->errNo()) {
ldap_get_option($this->connection, LDAP_OPT_DIAGNOSTIC_MESSAGE, $message);
return new DetailedError($number, $this->err2Str($number), $message);
}
}
/**
* {@inheritdoc}
*/
public function getValuesLen($entry, $attribute)
{
return ldap_get_values_len($this->connection, $entry, $attribute);
}
/**
* {@inheritdoc}
*/
public function setOption($option, $value)
{
return ldap_set_option($this->connection, $option, $value);
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options = [])
{
foreach ($options as $option => $value) {
$this->setOption($option, $value);
}
}
/**
* {@inheritdoc}
*/
public function setRebindCallback(callable $callback)
{
return ldap_set_rebind_proc($this->connection, $callback);
}
/**
* {@inheritdoc}
*/
public function startTLS()
{
try {
return ldap_start_tls($this->connection);
} catch (\ErrorException $e) {
throw new ConnectionException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function connect($hosts = [], $port = 389)
{
$this->host = $this->getConnectionString($hosts, $this->getProtocol(), $port);
// Reset the bound status if reinitializing the connection.
$this->bound = false;
return $this->connection = ldap_connect($this->host);
}
/**
* {@inheritdoc}
*/
public function close()
{
$connection = $this->connection;
$result = is_resource($connection) ? ldap_close($connection) : false;
$this->bound = false;
return $result;
}
/**
* {@inheritdoc}
*/
public function search($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_search($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* {@inheritdoc}
*/
public function listing($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_list($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* {@inheritdoc}
*/
public function read($dn, $filter, array $fields, $onlyAttributes = false, $size = 0, $time = 0)
{
return ldap_read($this->connection, $dn, $filter, $fields, $onlyAttributes, $size, $time);
}
/**
* Extract information from an LDAP result.
*
* @link 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
*
* @return bool
*/
public function parseResult($result, &$errorCode, &$dn, &$errorMessage, &$referrals, &$serverControls = [])
{
return $this->supportsServerControlsInMethods() && !empty($serverControls) ?
ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals, $serverControls) :
ldap_parse_result($this->connection, $result, $errorCode, $dn, $errorMessage, $referrals);
}
/**
* {@inheritdoc}
*/
public function bind($username, $password, $sasl = false)
{
// Prior to binding, we will upgrade our connectivity to TLS on our current
// connection and ensure we are not already bound before upgrading.
// This is to prevent subsequent upgrading on several binds.
if ($this->isUsingTLS() && !$this->isBound()) {
$this->startTLS();
}
if ($sasl) {
return $this->bound = ldap_sasl_bind($this->connection, null, null, 'GSSAPI');
}
return $this->bound = ldap_bind(
$this->connection,
$username,
html_entity_decode($password)
);
}
/**
* {@inheritdoc}
*/
public function add($dn, array $entry)
{
return ldap_add($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function delete($dn)
{
return ldap_delete($this->connection, $dn);
}
/**
* {@inheritdoc}
*/
public function rename($dn, $newRdn, $newParent, $deleteOldRdn = false)
{
return ldap_rename($this->connection, $dn, $newRdn, $newParent, $deleteOldRdn);
}
/**
* {@inheritdoc}
*/
public function modify($dn, array $entry)
{
return ldap_modify($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modifyBatch($dn, array $values)
{
return ldap_modify_batch($this->connection, $dn, $values);
}
/**
* {@inheritdoc}
*/
public function modAdd($dn, array $entry)
{
return ldap_mod_add($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modReplace($dn, array $entry)
{
return ldap_mod_replace($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function modDelete($dn, array $entry)
{
return ldap_mod_del($this->connection, $dn, $entry);
}
/**
* {@inheritdoc}
*/
public function controlPagedResult($pageSize = 1000, $isCritical = false, $cookie = '')
{
return ldap_control_paged_result($this->connection, $pageSize, $isCritical, $cookie);
}
/**
* {@inheritdoc}
*/
public function controlPagedResultResponse($result, &$cookie)
{
return ldap_control_paged_result_response($this->connection, $result, $cookie);
}
/**
* {@inheritdoc}
*/
public function freeResult($result)
{
return ldap_free_result($result);
}
/**
* {@inheritdoc}
*/
public function errNo()
{
return ldap_errno($this->connection);
}
/**
* {@inheritdoc}
*/
public function getExtendedError()
{
return $this->getDiagnosticMessage();
}
/**
* {@inheritdoc}
*/
public function getExtendedErrorHex()
{
if (preg_match("/(?<=data\s).*?(?=\,)/", $this->getExtendedError(), $code)) {
return $code[0];
}
}
/**
* {@inheritdoc}
*/
public function getExtendedErrorCode()
{
return $this->extractDiagnosticCode($this->getExtendedError());
}
/**
* {@inheritdoc}
*/
public function err2Str($number)
{
return ldap_err2str($number);
}
/**
* {@inheritdoc}
*/
public function getDiagnosticMessage()
{
ldap_get_option($this->connection, LDAP_OPT_ERROR_STRING, $message);
return $message;
}
/**
* {@inheritdoc}
*/
public function extractDiagnosticCode($message)
{
preg_match('/^([\da-fA-F]+):/', $message, $matches);
return isset($matches[1]) ? $matches[1] : false;
}
/**
* Returns the LDAP protocol to utilize for the current connection.
*
* @return string
*/
public function getProtocol()
{
return $this->isUsingSSL() ? $this::PROTOCOL_SSL : $this::PROTOCOL;
}
/**
* Determine if the current PHP version supports server controls.
*
* @return bool
*/
public function supportsServerControlsInMethods()
{
return version_compare(PHP_VERSION, '7.3.0') >= 0;
}
/**
* Generates an LDAP connection string for each host given.
*
* @param string|array $hosts
* @param string $protocol
* @param string $port
*
* @return string
*/
protected function getConnectionString($hosts, $protocol, $port)
{
// If we are using SSL and using the default port, we
// will override it to use the default SSL port.
if ($this->isUsingSSL() && $port == 389) {
$port = self::PORT_SSL;
}
// Normalize hosts into an array.
$hosts = is_array($hosts) ? $hosts : [$hosts];
$hosts = array_map(function ($host) use ($protocol, $port) {
return "{$protocol}{$host}:{$port}";
}, $hosts);
return implode(' ', $hosts);
}
}

View File

@ -0,0 +1,291 @@
<?php
namespace Adldap\Connections;
use Adldap\Adldap;
use Adldap\Auth\Guard;
use Adldap\Query\Cache;
use InvalidArgumentException;
use Adldap\Auth\GuardInterface;
use Adldap\Schemas\ActiveDirectory;
use Adldap\Schemas\SchemaInterface;
use Psr\SimpleCache\CacheInterface;
use Adldap\Models\Factory as ModelFactory;
use Adldap\Query\Factory as SearchFactory;
use Adldap\Configuration\DomainConfiguration;
/**
* Class Provider.
*
* Contains the LDAP connection and domain configuration to
* instantiate factories for retrieving and creating
* LDAP records as well as authentication (binding).
*/
class Provider implements ProviderInterface
{
/**
* The providers connection.
*
* @var ConnectionInterface
*/
protected $connection;
/**
* The providers configuration.
*
* @var DomainConfiguration
*/
protected $configuration;
/**
* The providers schema.
*
* @var SchemaInterface
*/
protected $schema;
/**
* The providers auth guard instance.
*
* @var GuardInterface
*/
protected $guard;
/**
* The providers cache instance.
*
* @var Cache|null
*/
protected $cache;
/**
* {@inheritdoc}
*/
public function __construct($configuration = [], ConnectionInterface $connection = null)
{
$this->setConfiguration($configuration)
->setConnection($connection);
}
/**
* Does nothing. Implemented in order to remain backwards compatible.
*
* @deprecated since v10.3.0
*/
public function __destruct()
{
//
}
/**
* {@inheritdoc}
*/
public function setConfiguration($configuration = [])
{
if (is_array($configuration)) {
$configuration = new DomainConfiguration($configuration);
}
if ($configuration instanceof DomainConfiguration) {
$this->configuration = $configuration;
$schema = $configuration->get('schema');
// We will update our schema here when our configuration is set.
$this->setSchema(new $schema());
return $this;
}
$class = DomainConfiguration::class;
throw new InvalidArgumentException(
"Configuration must be array or instance of $class"
);
}
/**
* {@inheritdoc}
*/
public function setConnection(ConnectionInterface $connection = null)
{
// We will create a standard connection if one isn't given.
$this->connection = $connection ?: new Ldap();
// Prepare the connection.
$this->prepareConnection();
// Instantiate the LDAP connection.
$this->connection->connect(
$this->configuration->get('hosts'),
$this->configuration->get('port')
);
return $this;
}
/**
* {@inheritdoc}
*/
public function setSchema(SchemaInterface $schema = null)
{
$this->schema = $schema ?: new ActiveDirectory();
return $this;
}
/**
* {@inheritdoc}
*/
public function setGuard(GuardInterface $guard)
{
$this->guard = $guard;
return $this;
}
/**
* Sets the cache store.
*
* @param CacheInterface $store
*
* @return $this
*/
public function setCache(CacheInterface $store)
{
$this->cache = new Cache($store);
return $this;
}
/**
* {@inheritdoc}
*/
public function getConfiguration()
{
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function getConnection()
{
return $this->connection;
}
/**
* {@inheritdoc}
*/
public function getSchema()
{
return $this->schema;
}
/**
* {@inheritdoc}
*/
public function getGuard()
{
if (!$this->guard instanceof GuardInterface) {
$this->setGuard($this->getDefaultGuard($this->connection, $this->configuration));
}
return $this->guard;
}
/**
* {@inheritdoc}
*/
public function getDefaultGuard(ConnectionInterface $connection, DomainConfiguration $configuration)
{
$guard = new Guard($connection, $configuration);
$guard->setDispatcher(Adldap::getEventDispatcher());
return $guard;
}
/**
* {@inheritdoc}
*/
public function make()
{
return new ModelFactory(
$this->search()->newQuery()
);
}
/**
* {@inheritdoc}
*/
public function search()
{
$factory = new SearchFactory(
$this->connection,
$this->schema,
$this->configuration->get('base_dn')
);
if ($this->cache) {
$factory->setCache($this->cache);
}
return $factory;
}
/**
* {@inheritdoc}
*/
public function auth()
{
return $this->getGuard();
}
/**
* {@inheritdoc}
*/
public function connect($username = null, $password = null)
{
// Get the default guard instance.
$guard = $this->getGuard();
if (is_null($username) && is_null($password)) {
// If both the username and password are null, we'll connect to the server
// using the configured administrator username and password.
$guard->bindAsAdministrator();
} else {
// Bind to the server with the specified username and password otherwise.
$guard->bind($username, $password);
}
return $this;
}
/**
* Prepares the connection by setting configured parameters.
*
* @throws \Adldap\Configuration\ConfigurationException When configuration options requested do not exist
*
* @return void
*/
protected function prepareConnection()
{
if ($this->configuration->get('use_ssl')) {
$this->connection->ssl();
} elseif ($this->configuration->get('use_tls')) {
$this->connection->tls();
}
$options = array_replace(
$this->configuration->get('custom_options'),
[
LDAP_OPT_PROTOCOL_VERSION => $this->configuration->get('version'),
LDAP_OPT_NETWORK_TIMEOUT => $this->configuration->get('timeout'),
LDAP_OPT_REFERRALS => $this->configuration->get('follow_referrals'),
]
);
$this->connection->setOptions($options);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace Adldap\Connections;
use Adldap\Auth\GuardInterface;
use Adldap\Schemas\SchemaInterface;
use Adldap\Configuration\DomainConfiguration;
interface ProviderInterface
{
/**
* Constructor.
*
* @param array|DomainConfiguration $configuration
* @param ConnectionInterface $connection
*/
public function __construct($configuration, ConnectionInterface $connection);
/**
* Returns the current connection instance.
*
* @return ConnectionInterface
*/
public function getConnection();
/**
* Returns the current configuration instance.
*
* @return DomainConfiguration
*/
public function getConfiguration();
/**
* Returns the current Guard instance.
*
* @return \Adldap\Auth\Guard
*/
public function getGuard();
/**
* Returns a new default Guard instance.
*
* @param ConnectionInterface $connection
* @param DomainConfiguration $configuration
*
* @return \Adldap\Auth\Guard
*/
public function getDefaultGuard(ConnectionInterface $connection, DomainConfiguration $configuration);
/**
* Sets the current connection.
*
* @param ConnectionInterface $connection
*
* @return $this
*/
public function setConnection(ConnectionInterface $connection = null);
/**
* Sets the current configuration.
*
* @param DomainConfiguration|array $configuration
*
* @throws \Adldap\Configuration\ConfigurationException
*/
public function setConfiguration($configuration = []);
/**
* Sets the current LDAP attribute schema.
*
* @param SchemaInterface|null $schema
*
* @return $this
*/
public function setSchema(SchemaInterface $schema = null);
/**
* Returns the current LDAP attribute schema.
*
* @return SchemaInterface
*/
public function getSchema();
/**
* Sets the current Guard instance.
*
* @param GuardInterface $guard
*
* @return $this
*/
public function setGuard(GuardInterface $guard);
/**
* Returns a new Model factory instance.
*
* @return \Adldap\Models\Factory
*/
public function make();
/**
* Returns a new Search factory instance.
*
* @return \Adldap\Query\Factory
*/
public function search();
/**
* Returns a new Auth Guard instance.
*
* @return \Adldap\Auth\Guard
*/
public function auth();
/**
* Connects and Binds to the Domain Controller.
*
* If no username or password is specified, then the
* configured administrator credentials are used.
*
* @param string|null $username
* @param string|null $password
*
* @throws \Adldap\Auth\BindException If binding to the LDAP server fails.
* @throws ConnectionException If upgrading the connection to TLS fails
*
* @return ProviderInterface
*/
public function connect($username = null, $password = null);
}

View File

@ -0,0 +1,320 @@
<?php
namespace Adldap\Events;
use Illuminate\Support\Arr;
/**
* Class Dispatcher.
*
* Handles event listening and dispatching.
*
* This code was taken out of the Laravel Framework core
* with broadcasting and queuing omitted to remove
* an extra dependency that would be required.
*
* @author Taylor Otwell
*
* @see https://github.com/laravel/framework
*/
class Dispatcher implements DispatcherInterface
{
/**
* The registered event listeners.
*
* @var array
*/
protected $listeners = [];
/**
* The wildcard listeners.
*
* @var array
*/
protected $wildcards = [];
/**
* The cached wildcard listeners.
*
* @var array
*/
protected $wildcardsCache = [];
/**
* {@inheritdoc}
*/
public function listen($events, $listener)
{
foreach ((array) $events as $event) {
if (strpos($event, '*') !== false) {
$this->setupWildcardListen($event, $listener);
} else {
$this->listeners[$event][] = $this->makeListener($listener);
}
}
}
/**
* Setup a wildcard listener callback.
*
* @param string $event
* @param mixed $listener
*
* @return void
*/
protected function setupWildcardListen($event, $listener)
{
$this->wildcards[$event][] = $this->makeListener($listener, true);
$this->wildcardsCache = [];
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName)
{
return isset($this->listeners[$eventName]) || isset($this->wildcards[$eventName]);
}
/**
* {@inheritdoc}
*/
public function until($event, $payload = [])
{
return $this->dispatch($event, $payload, true);
}
/**
* {@inheritdoc}
*/
public function fire($event, $payload = [], $halt = false)
{
return $this->dispatch($event, $payload, $halt);
}
/**
* {@inheritdoc}
*/
public function dispatch($event, $payload = [], $halt = false)
{
// When the given "event" is actually an object we will assume it is an event
// object and use the class as the event name and this event itself as the
// payload to the handler, which makes object based events quite simple.
list($event, $payload) = $this->parseEventAndPayload(
$event,
$payload
);
$responses = [];
foreach ($this->getListeners($event) as $listener) {
$response = $listener($event, $payload);
// If a response is returned from the listener and event halting is enabled
// we will just return this response, and not call the rest of the event
// listeners. Otherwise we will add the response on the response list.
if ($halt && !is_null($response)) {
return $response;
}
// If a boolean false is returned from a listener, we will stop propagating
// the event to any further listeners down in the chain, else we keep on
// looping through the listeners and firing every one in our sequence.
if ($response === false) {
break;
}
$responses[] = $response;
}
return $halt ? null : $responses;
}
/**
* Parse the given event and payload and prepare them for dispatching.
*
* @param mixed $event
* @param mixed $payload
*
* @return array
*/
protected function parseEventAndPayload($event, $payload)
{
if (is_object($event)) {
list($payload, $event) = [[$event], get_class($event)];
}
return [$event, Arr::wrap($payload)];
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName)
{
$listeners = $this->listeners[$eventName] ?? [];
$listeners = array_merge(
$listeners,
$this->wildcardsCache[$eventName] ?? $this->getWildcardListeners($eventName)
);
return class_exists($eventName, false)
? $this->addInterfaceListeners($eventName, $listeners)
: $listeners;
}
/**
* Get the wildcard listeners for the event.
*
* @param string $eventName
*
* @return array
*/
protected function getWildcardListeners($eventName)
{
$wildcards = [];
foreach ($this->wildcards as $key => $listeners) {
if ($this->wildcardContainsEvent($key, $eventName)) {
$wildcards = array_merge($wildcards, $listeners);
}
}
return $this->wildcardsCache[$eventName] = $wildcards;
}
/**
* Determine if the wildcard matches or contains the given event.
*
* This function is a direct excerpt from Laravel's Str::is().
*
* @param string $wildcard
* @param string $eventName
*
* @return bool
*/
protected function wildcardContainsEvent($wildcard, $eventName)
{
$patterns = Arr::wrap($wildcard);
if (empty($patterns)) {
return false;
}
foreach ($patterns as $pattern) {
// If the given event is an exact match we can of course return true right
// from the beginning. Otherwise, we will translate asterisks and do an
// actual pattern match against the two strings to see if they match.
if ($pattern == $eventName) {
return true;
}
$pattern = preg_quote($pattern, '#');
// Asterisks are translated into zero-or-more regular expression wildcards
// to make it convenient to check if the strings starts with the given
// pattern such as "library/*", making any string check convenient.
$pattern = str_replace('\*', '.*', $pattern);
if (preg_match('#^'.$pattern.'\z#u', $eventName) === 1) {
return true;
}
}
return false;
}
/**
* Add the listeners for the event's interfaces to the given array.
*
* @param string $eventName
* @param array $listeners
*
* @return array
*/
protected function addInterfaceListeners($eventName, array $listeners = [])
{
foreach (class_implements($eventName) as $interface) {
if (isset($this->listeners[$interface])) {
foreach ($this->listeners[$interface] as $names) {
$listeners = array_merge($listeners, (array) $names);
}
}
}
return $listeners;
}
/**
* Register an event listener with the dispatcher.
*
* @param \Closure|string $listener
* @param bool $wildcard
*
* @return \Closure
*/
public function makeListener($listener, $wildcard = false)
{
if (is_string($listener)) {
return $this->createClassListener($listener, $wildcard);
}
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return $listener($event, $payload);
}
return $listener(...array_values($payload));
};
}
/**
* Create a class based listener.
*
* @param string $listener
* @param bool $wildcard
*
* @return \Closure
*/
protected function createClassListener($listener, $wildcard = false)
{
return function ($event, $payload) use ($listener, $wildcard) {
if ($wildcard) {
return call_user_func($this->parseListenerCallback($listener), $event, $payload);
}
return call_user_func_array(
$this->parseListenerCallback($listener),
$payload
);
};
}
/**
* Parse the class listener into class and method.
*
* @param string $listener
*
* @return array
*/
protected function parseListenerCallback($listener)
{
return strpos($listener, '@') !== false ?
explode('@', $listener, 2) :
[$listener, 'handle'];
}
/**
* {@inheritdoc}
*/
public function forget($event)
{
if (strpos($event, '*') !== false) {
unset($this->wildcards[$event]);
} else {
unset($this->listeners[$event]);
}
}
}

View File

@ -0,0 +1,75 @@
<?php
namespace Adldap\Events;
interface DispatcherInterface
{
/**
* Register an event listener with the dispatcher.
*
* @param string|array $events
* @param mixed $listener
*
* @return void
*/
public function listen($events, $listener);
/**
* Determine if a given event has listeners.
*
* @param string $eventName
*
* @return bool
*/
public function hasListeners($eventName);
/**
* Fire an event until the first non-null response is returned.
*
* @param string|object $event
* @param mixed $payload
*
* @return array|null
*/
public function until($event, $payload = []);
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @return mixed
*/
public function fire($event, $payload = [], $halt = false);
/**
* Fire an event and call the listeners.
*
* @param string|object $event
* @param mixed $payload
* @param bool $halt
*
* @return array|null
*/
public function dispatch($event, $payload = [], $halt = false);
/**
* Get all of the listeners for a given event name.
*
* @param string $eventName
*
* @return array
*/
public function getListeners($eventName);
/**
* Remove a set of listeners from the dispatcher.
*
* @param string $event
*
* @return void
*/
public function forget($event);
}

View File

@ -0,0 +1,51 @@
<?php
namespace Adldap\Events;
trait DispatchesEvents
{
/**
* The event dispatcher instance.
*
* @var DispatcherInterface
*/
protected static $dispatcher;
/**
* Get the event dispatcher instance.
*
* @return DispatcherInterface
*/
public static function getEventDispatcher()
{
// If no event dispatcher has been set, well instantiate and
// set one here. This will be our singleton instance.
if (!isset(static::$dispatcher)) {
static::setEventDispatcher(new Dispatcher());
}
return static::$dispatcher;
}
/**
* Set the event dispatcher instance.
*
* @param DispatcherInterface $dispatcher
*
* @return void
*/
public static function setEventDispatcher(DispatcherInterface $dispatcher)
{
static::$dispatcher = $dispatcher;
}
/**
* Unset the event dispatcher instance.
*
* @return void
*/
public static function unsetEventDispatcher()
{
static::$dispatcher = null;
}
}

View File

@ -0,0 +1,141 @@
<?php
namespace Adldap\Log;
use ReflectionClass;
use Psr\Log\LoggerInterface;
use Adldap\Auth\Events\Failed;
use Adldap\Auth\Events\Event as AuthEvent;
use Adldap\Models\Events\Event as ModelEvent;
use Adldap\Query\Events\QueryExecuted as QueryEvent;
class EventLogger
{
/**
* The logger instance.
*
* @var LoggerInterface|null
*/
protected $logger;
/**
* Constructor.
*
* @param LoggerInterface $logger
*/
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
/**
* Logs the given event.
*
* @param mixed $event
*/
public function log($event)
{
if ($event instanceof AuthEvent) {
$this->auth($event);
} elseif ($event instanceof ModelEvent) {
$this->model($event);
} elseif ($event instanceof QueryEvent) {
$this->query($event);
}
}
/**
* Logs an authentication event.
*
* @param AuthEvent $event
*
* @return void
*/
public function auth(AuthEvent $event)
{
if (isset($this->logger)) {
$connection = $event->getConnection();
$message = "LDAP ({$connection->getHost()})"
." - Connection: {$connection->getName()}"
." - Operation: {$this->getOperationName($event)}"
." - Username: {$event->getUsername()}";
$result = null;
$type = 'info';
if (is_a($event, Failed::class)) {
$type = 'warning';
$result = " - Reason: {$connection->getLastError()}";
}
$this->logger->$type($message.$result);
}
}
/**
* Logs a model event.
*
* @param ModelEvent $event
*
* @return void
*/
public function model(ModelEvent $event)
{
if (isset($this->logger)) {
$model = $event->getModel();
$on = get_class($model);
$connection = $model->getQuery()->getConnection();
$message = "LDAP ({$connection->getHost()})"
." - Connection: {$connection->getName()}"
." - Operation: {$this->getOperationName($event)}"
." - On: {$on}"
." - Distinguished Name: {$model->getDn()}";
$this->logger->info($message);
}
}
/**
* Logs a query event.
*
* @param QueryEvent $event
*
* @return void
*/
public function query(QueryEvent $event)
{
if (isset($this->logger)) {
$query = $event->getQuery();
$connection = $query->getConnection();
$selected = implode(',', $query->getSelects());
$message = "LDAP ({$connection->getHost()})"
." - Connection: {$connection->getName()}"
." - Operation: {$this->getOperationName($event)}"
." - Base DN: {$query->getDn()}"
." - Filter: {$query->getUnescapedQuery()}"
." - Selected: ({$selected})"
." - Time Elapsed: {$event->getTime()}";
$this->logger->info($message);
}
}
/**
* Returns the operational name of the given event.
*
* @param mixed $event
*
* @return string
*/
protected function getOperationName($event)
{
return (new ReflectionClass($event))->getShortName();
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace Adldap\Log;
use Psr\Log\LoggerInterface;
trait LogsInformation
{
/**
* The logger instance.
*
* @var LoggerInterface|null
*/
protected static $logger;
/**
* Get the logger instance.
*
* @return LoggerInterface|null
*/
public static function getLogger()
{
return static::$logger;
}
/**
* Set the logger instance.
*
* @param LoggerInterface $logger
*
* @return void
*/
public static function setLogger(LoggerInterface $logger)
{
static::$logger = $logger;
}
/**
* Unset the logger instance.
*
* @return void
*/
public static function unsetLogger()
{
static::$logger = null;
}
}

View File

@ -0,0 +1,458 @@
<?php
namespace Adldap\Models\Attributes;
use ReflectionClass;
/**
* The Account Control class.
*
* This class is for easily building a user account control value.
*
* @link https://support.microsoft.com/en-us/kb/305144
*/
class AccountControl
{
const SCRIPT = 1;
const ACCOUNTDISABLE = 2;
const HOMEDIR_REQUIRED = 8;
const LOCKOUT = 16;
const PASSWD_NOTREQD = 32;
const ENCRYPTED_TEXT_PWD_ALLOWED = 128;
const TEMP_DUPLICATE_ACCOUNT = 256;
const NORMAL_ACCOUNT = 512;
const INTERDOMAIN_TRUST_ACCOUNT = 2048;
const WORKSTATION_TRUST_ACCOUNT = 4096;
const SERVER_TRUST_ACCOUNT = 8192;
const DONT_EXPIRE_PASSWORD = 65536;
const MNS_LOGON_ACCOUNT = 131072;
const SMARTCARD_REQUIRED = 262144;
const TRUSTED_FOR_DELEGATION = 524288;
const NOT_DELEGATED = 1048576;
const USE_DES_KEY_ONLY = 2097152;
const DONT_REQ_PREAUTH = 4194304;
const PASSWORD_EXPIRED = 8388608;
const TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216;
const PARTIAL_SECRETS_ACCOUNT = 67108864;
/**
* Stores the values to be added together to
* build the user account control integer.
*
* @var array
*/
protected $values = [];
/**
* Constructor.
*
* @param int $flag
*/
public function __construct($flag = null)
{
if (!is_null($flag)) {
$this->apply($flag);
}
}
/**
* Get the value when casted to string.
*
* @return string
*/
public function __toString()
{
return (string) $this->getValue();
}
/**
* Get the value when casted to int.
*
* @return int
*/
public function __toInt()
{
return $this->getValue();
}
/**
* Add the value to the account control values.
*
* @param int $value
*
* @return AccountControl
*/
public function add($value)
{
// Use the value as a key so if the same value
// is used, it will always be overwritten
$this->values[$value] = $value;
return $this;
}
/**
* Remove the value from the account control.
*
* @param int $value
*
* @return $this
*/
public function remove($value)
{
unset($this->values[$value]);
return $this;
}
/**
* Extract and apply the flag.
*
* @param int $flag
*/
public function apply($flag)
{
$this->setValues($this->extractFlags($flag));
}
/**
* Determine if the current AccountControl object contains the given UAC flag(s).
*
* @param int $flag
*
* @return bool
*/
public function has($flag)
{
// We'll extract the given flag into an array of possible flags, and
// see if our AccountControl object contains any of them.
$flagsUsed = array_intersect($this->extractFlags($flag), $this->values);
return in_array($flag, $flagsUsed);
}
/**
* The logon script will be run.
*
* @return AccountControl
*/
public function runLoginScript()
{
return $this->add(static::SCRIPT);
}
/**
* The user account is locked.
*
* @return AccountControl
*/
public function accountIsLocked()
{
return $this->add(static::LOCKOUT);
}
/**
* The user account is disabled.
*
* @return AccountControl
*/
public function accountIsDisabled()
{
return $this->add(static::ACCOUNTDISABLE);
}
/**
* This is an account for users whose primary account is in another domain.
*
* This account provides user access to this domain, but not to any domain that
* trusts this domain. This is sometimes referred to as a local user account.
*
* @return AccountControl
*/
public function accountIsTemporary()
{
return $this->add(static::TEMP_DUPLICATE_ACCOUNT);
}
/**
* This is a default account type that represents a typical user.
*
* @return AccountControl
*/
public function accountIsNormal()
{
return $this->add(static::NORMAL_ACCOUNT);
}
/**
* This is a permit to trust an account for a system domain that trusts other domains.
*
* @return AccountControl
*/
public function accountIsForInterdomain()
{
return $this->add(static::INTERDOMAIN_TRUST_ACCOUNT);
}
/**
* This is a computer account for a computer that is running Microsoft
* Windows NT 4.0 Workstation, Microsoft Windows NT 4.0 Server, Microsoft
* Windows 2000 Professional, or Windows 2000 Server and is a member of this domain.
*
* @return AccountControl
*/
public function accountIsForWorkstation()
{
return $this->add(static::WORKSTATION_TRUST_ACCOUNT);
}
/**
* This is a computer account for a domain controller that is a member of this domain.
*
* @return AccountControl
*/
public function accountIsForServer()
{
return $this->add(static::SERVER_TRUST_ACCOUNT);
}
/**
* This is an MNS logon account.
*
* @return AccountControl
*/
public function accountIsMnsLogon()
{
return $this->add(static::MNS_LOGON_ACCOUNT);
}
/**
* (Windows 2000/Windows Server 2003) This account does
* not require Kerberos pre-authentication for logging on.
*
* @return AccountControl
*/
public function accountDoesNotRequirePreAuth()
{
return $this->add(static::DONT_REQ_PREAUTH);
}
/**
* When this flag is set, it forces the user to log on by using a smart card.
*
* @return AccountControl
*/
public function accountRequiresSmartCard()
{
return $this->add(static::SMARTCARD_REQUIRED);
}
/**
* (Windows Server 2008/Windows Server 2008 R2) The account is a read-only domain controller (RODC).
*
* This is a security-sensitive setting. Removing this setting from an RODC compromises security on that server.
*
* @return AccountControl
*/
public function accountIsReadOnly()
{
return $this->add(static::PARTIAL_SECRETS_ACCOUNT);
}
/**
* The home folder is required.
*
* @return AccountControl
*/
public function homeFolderIsRequired()
{
return $this->add(static::HOMEDIR_REQUIRED);
}
/**
* No password is required.
*
* @return AccountControl
*/
public function passwordIsNotRequired()
{
return $this->add(static::PASSWD_NOTREQD);
}
/**
* The user cannot change the password. This is a permission on the user's object.
*
* For information about how to programmatically set this permission, visit the following link:
*
* @link http://msdn2.microsoft.com/en-us/library/aa746398.aspx
*
* @return AccountControl
*/
public function passwordCannotBeChanged()
{
return $this->add(static::PASSWD_NOTREQD);
}
/**
* Represents the password, which should never expire on the account.
*
* @return AccountControl
*/
public function passwordDoesNotExpire()
{
return $this->add(static::DONT_EXPIRE_PASSWORD);
}
/**
* (Windows 2000/Windows Server 2003) The user's password has expired.
*
* @return AccountControl
*/
public function passwordIsExpired()
{
return $this->add(static::PASSWORD_EXPIRED);
}
/**
* The user can send an encrypted password.
*
* @return AccountControl
*/
public function allowEncryptedTextPassword()
{
return $this->add(static::ENCRYPTED_TEXT_PWD_ALLOWED);
}
/**
* When this flag is set, the service account (the user or computer account)
* under which a service runs is trusted for Kerberos delegation.
*
* Any such service can impersonate a client requesting the service.
*
* To enable a service for Kerberos delegation, you must set this
* flag on the userAccountControl property of the service account.
*
* @return AccountControl
*/
public function trustForDelegation()
{
return $this->add(static::TRUSTED_FOR_DELEGATION);
}
/**
* (Windows 2000/Windows Server 2003) The account is enabled for delegation.
*
* This is a security-sensitive setting. Accounts that have this option enabled
* should be tightly controlled. This setting lets a service that runs under the
* account assume a client's identity and authenticate as that user to other remote
* servers on the network.
*
* @return AccountControl
*/
public function trustToAuthForDelegation()
{
return $this->add(static::TRUSTED_TO_AUTH_FOR_DELEGATION);
}
/**
* When this flag is set, the security context of the user is not delegated to a
* service even if the service account is set as trusted for Kerberos delegation.
*
* @return AccountControl
*/
public function doNotTrustForDelegation()
{
return $this->add(static::NOT_DELEGATED);
}
/**
* (Windows 2000/Windows Server 2003) Restrict this principal to
* use only Data Encryption Standard (DES) encryption types for keys.
*
* @return AccountControl
*/
public function useDesKeyOnly()
{
return $this->add(static::USE_DES_KEY_ONLY);
}
/**
* Get the account control value.
*
* @return int
*/
public function getValue()
{
return array_sum($this->values);
}
/**
* Get the account control flag values.
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Set the account control values.
*
* @param array $flags
*/
public function setValues(array $flags)
{
$this->values = $flags;
}
/**
* Get all possible account control flags.
*
* @return array
*/
public function getAllFlags()
{
return (new ReflectionClass(__CLASS__))->getConstants();
}
/**
* Extracts the given flag into an array of flags used.
*
* @param int $flag
*
* @return array
*/
public function extractFlags($flag)
{
$flags = [];
for ($i = 0; $i <= 26; $i++) {
if ((int) $flag & (1 << $i)) {
$flags[1 << $i] = 1 << $i;
}
}
return $flags;
}
}

View File

@ -0,0 +1,312 @@
<?php
namespace Adldap\Models\Attributes;
use Adldap\Utilities;
class DistinguishedName
{
/**
* The distinguished name components (in order of assembly).
*
* @var array
*/
protected $components = [
'cn' => [],
'uid' => [],
'ou' => [],
'dc' => [],
'o' => [],
];
/**
* Constructor.
*
* @param mixed $baseDn
*/
public function __construct($baseDn = null)
{
$this->setBase($baseDn);
}
/**
* Returns the complete distinguished name.
*
* @return string
*/
public function __toString()
{
return $this->get();
}
/**
* Returns the complete distinguished name by assembling the RDN components.
*
* @return string
*/
public function get()
{
$components = [];
// We'll go through each component type and assemble its RDN.
foreach ($this->components as $component => $values) {
array_map(function ($value) use ($component, &$components) {
// Assemble the component and escape the value.
$components[] = sprintf('%s=%s', $component, ldap_escape($value, '', 2));
}, $values);
}
return implode(',', $components);
}
/**
* Adds a domain component.
*
* @param string $dc
*
* @return DistinguishedName
*/
public function addDc($dc)
{
$this->addComponent('dc', $dc);
return $this;
}
/**
* Removes a domain component.
*
* @param string $dc
*
* @return DistinguishedName
*/
public function removeDc($dc)
{
$this->removeComponent('dc', $dc);
return $this;
}
/**
* Adds an organization name.
*
* @param string $o
*
* @return $this
*/
public function addO($o)
{
$this->addComponent('o', $o);
return $this;
}
/**
* Removes an organization name.
*
* @param string $o
*
* @return DistinguishedName
*/
public function removeO($o)
{
$this->removeComponent('o', $o);
return $this;
}
/**
* Add a user identifier.
*
* @param string $uid
*
* @return DistinguishedName
*/
public function addUid($uid)
{
$this->addComponent('uid', $uid);
return $this;
}
/**
* Removes a user identifier.
*
* @param string $uid
*
* @return DistinguishedName
*/
public function removeUid($uid)
{
$this->removeComponent('uid', $uid);
return $this;
}
/**
* Adds a common name.
*
* @param string $cn
*
* @return DistinguishedName
*/
public function addCn($cn)
{
$this->addComponent('cn', $cn);
return $this;
}
/**
* Removes a common name.
*
* @param string $cn
*
* @return DistinguishedName
*/
public function removeCn($cn)
{
$this->removeComponent('cn', $cn);
return $this;
}
/**
* Adds an organizational unit.
*
* @param string $ou
*
* @return DistinguishedName
*/
public function addOu($ou)
{
$this->addComponent('ou', $ou);
return $this;
}
/**
* Removes an organizational unit.
*
* @param string $ou
*
* @return DistinguishedName
*/
public function removeOu($ou)
{
$this->removeComponent('ou', $ou);
return $this;
}
/**
* Sets the base RDN of the distinguished name.
*
* @param string|DistinguishedName $base
*
* @return DistinguishedName
*/
public function setBase($base)
{
// Typecast base to string in case we've been given
// an instance of the distinguished name object.
$base = (string) $base;
// If the base DN isn't null we'll try to explode it.
$base = Utilities::explodeDn($base, false) ?: [];
// Remove the count key from the exploded distinguished name.
unset($base['count']);
foreach ($base as $key => $rdn) {
// We'll break the RDN into pieces
$pieces = explode('=', $rdn) ?: [];
// If there's exactly 2 pieces, then we can work with it.
if (count($pieces) === 2) {
$attribute = ucfirst(strtolower($pieces[0]));
$method = 'add'.$attribute;
if (method_exists($this, $method)) {
// We see what type of RDN it is and add each accordingly.
call_user_func_array([$this, $method], [$pieces[1]]);
}
}
}
return $this;
}
/**
* Returns an array of all components in the distinguished name.
*
* If a component name is given ('cn', 'dc' for example) then
* the values of that component will be returned.
*
* @param string|null $component The component to retrieve values of
*
* @return array
*/
public function getComponents($component = null)
{
if (is_null($component)) {
return $this->components;
}
$this->validateComponentExists($component);
return $this->components[$component];
}
/**
* Adds a component to the distinguished name.
*
* @param string $component
* @param string $value
*
* @throws \UnexpectedValueException When the given name does not exist.
*/
protected function addComponent($component, $value)
{
$this->validateComponentExists($component);
// We need to make sure the value we're given isn't empty before adding it into our components.
if (!empty($value)) {
$this->components[$component][] = $value;
}
}
/**
* Removes the given value from the given component.
*
* @param string $component
* @param string $value
*
* @throws \UnexpectedValueException When the given component does not exist.
*
* @return void
*/
protected function removeComponent($component, $value)
{
$this->validateComponentExists($component);
$this->components[$component] = array_diff($this->components[$component], [$value]);
}
/**
* Validates that the given component exists in the available components.
*
* @param string $component The name of the component to validate.
*
* @throws \UnexpectedValueException When the given component does not exist.
*
* @return void
*/
protected function validateComponentExists($component)
{
if (!array_key_exists($component, $this->components)) {
throw new \UnexpectedValueException("The RDN component '$component' does not exist.");
}
}
}

View File

@ -0,0 +1,157 @@
<?php
namespace Adldap\Models\Attributes;
use Adldap\Utilities;
use InvalidArgumentException;
class Guid
{
/**
* The string GUID value.
*
* @var string
*/
protected $value;
/**
* The guid structure in order by section to parse using substr().
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*
* @link https://github.com/ldaptools/ldaptools
*
* @var array
*/
protected $guidSections = [
[[-26, 2], [-28, 2], [-30, 2], [-32, 2]],
[[-22, 2], [-24, 2]],
[[-18, 2], [-20, 2]],
[[-16, 4]],
[[-12, 12]],
];
/**
* The hexadecimal octet order based on string position.
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*
* @link https://github.com/ldaptools/ldaptools
*
* @var array
*/
protected $octetSections = [
[6, 4, 2, 0],
[10, 8],
[14, 12],
[16, 18, 20, 22, 24, 26, 28, 30],
];
/**
* Determines if the specified GUID is valid.
*
* @param string $guid
*
* @return bool
*/
public static function isValid($guid)
{
return Utilities::isValidGuid($guid);
}
/**
* Constructor.
*
* @param mixed $value
*
* @throws InvalidArgumentException
*/
public function __construct($value)
{
if (static::isValid($value)) {
$this->value = $value;
} elseif ($value = $this->binaryGuidToString($value)) {
$this->value = $value;
} else {
throw new InvalidArgumentException('Invalid Binary / String GUID.');
}
}
/**
* Returns the string value of the GUID.
*
* @return string
*/
public function __toString()
{
return $this->getValue();
}
/**
* Returns the string value of the SID.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Get the binary representation of the GUID string.
*
* @return string
*/
public function getBinary()
{
$data = '';
$guid = str_replace('-', '', $this->value);
foreach ($this->octetSections as $section) {
$data .= $this->parseSection($guid, $section, true);
}
return hex2bin($data);
}
/**
* Returns the string variant of a binary GUID.
*
* @param string $binary
*
* @return string|null
*/
protected function binaryGuidToString($binary)
{
return Utilities::binaryGuidToString($binary);
}
/**
* Return the specified section of the hexadecimal string.
*
* @author Chad Sikorra <Chad.Sikorra@gmail.com>
*
* @link https://github.com/ldaptools/ldaptools
*
* @param string $hex The full hex string.
* @param array $sections An array of start and length (unless octet is true, then length is always 2).
* @param bool $octet Whether this is for octet string form.
*
* @return string The concatenated sections in upper-case.
*/
protected function parseSection($hex, array $sections, $octet = false)
{
$parsedString = '';
foreach ($sections as $section) {
$start = $octet ? $section : $section[0];
$length = $octet ? 2 : $section[1];
$parsedString .= substr($hex, $start, $length);
}
return $parsedString;
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Adldap\Models\Attributes;
class MbString
{
/**
* Get the integer value of a specific character.
*
* @param $string
*
* @return int
*/
public static function ord($string)
{
if (self::isLoaded()) {
$result = unpack('N', mb_convert_encoding($string, 'UCS-4BE', 'UTF-8'));
if (is_array($result) === true) {
return $result[1];
}
}
return ord($string);
}
/**
* Get the character for a specific integer value.
*
* @param $int
*
* @return string
*/
public static function chr($int)
{
if (self::isLoaded()) {
return mb_convert_encoding(pack('n', $int), 'UTF-8', 'UTF-16BE');
}
return chr($int);
}
/**
* Split a string into its individual characters and return it as an array.
*
* @param string $value
*
* @return string[]
*/
public static function split($value)
{
return preg_split('/(?<!^)(?!$)/u', $value);
}
/**
* Detects if the given string is UTF 8.
*
* @param $string
*
* @return string|false
*/
public static function isUtf8($string)
{
if (self::isLoaded()) {
return mb_detect_encoding($string, 'UTF-8', $strict = true);
}
return $string;
}
/**
* Checks if the mbstring extension is enabled in PHP.
*
* @return bool
*/
public static function isLoaded()
{
return extension_loaded('mbstring');
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace Adldap\Models\Attributes;
use Adldap\Utilities;
use InvalidArgumentException;
class Sid
{
/**
* The string SID value.
*
* @var string
*/
protected $value;
/**
* Determines if the specified SID is valid.
*
* @param string $sid
*
* @return bool
*/
public static function isValid($sid)
{
return Utilities::isValidSid($sid);
}
/**
* Constructor.
*
* @param mixed $value
*
* @throws InvalidArgumentException
*/
public function __construct($value)
{
if (static::isValid($value)) {
$this->value = $value;
} elseif ($value = $this->binarySidToString($value)) {
$this->value = $value;
} else {
throw new InvalidArgumentException('Invalid Binary / String SID.');
}
}
/**
* Returns the string value of the SID.
*
* @return string
*/
public function __toString()
{
return $this->getValue();
}
/**
* Returns the string value of the SID.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Returns the binary variant of the SID.
*
* @return string
*/
public function getBinary()
{
$sid = explode('-', ltrim($this->value, 'S-'));
$level = (int) array_shift($sid);
$authority = (int) array_shift($sid);
$subAuthorities = array_map('intval', $sid);
$params = array_merge(
['C2xxNV*', $level, count($subAuthorities), $authority],
$subAuthorities
);
return call_user_func_array('pack', $params);
}
/**
* Returns the string variant of a binary SID.
*
* @param string $binary
*
* @return string|null
*/
protected function binarySidToString($binary)
{
return Utilities::binarySidToString($binary);
}
}

View File

@ -0,0 +1,396 @@
<?php
namespace Adldap\Models\Attributes;
class TSProperty
{
/**
* Nibble control values. The first value for each is if the nibble is <= 9, otherwise the second value is used.
*/
const NIBBLE_CONTROL = [
'X' => ['001011', '011010'],
'Y' => ['001110', '011010'],
];
/**
* The nibble header.
*/
const NIBBLE_HEADER = '1110';
/**
* Conversion factor needed for time values in the TSPropertyArray (stored in microseconds).
*/
const TIME_CONVERSION = 60 * 1000;
/**
* A simple map to help determine how the property needs to be decoded/encoded from/to its binary value.
*
* There are some names that are simple repeats but have 'W' at the end. Not sure as to what that signifies. I
* cannot find any information on them in Microsoft documentation. However, their values appear to stay in sync with
* their non 'W' counterparts. But not doing so when manipulating the data manually does not seem to affect anything.
* This probably needs more investigation.
*
* @var array
*/
protected $propTypes = [
'string' => [
'CtxWFHomeDir',
'CtxWFHomeDirW',
'CtxWFHomeDirDrive',
'CtxWFHomeDirDriveW',
'CtxInitialProgram',
'CtxInitialProgramW',
'CtxWFProfilePath',
'CtxWFProfilePathW',
'CtxWorkDirectory',
'CtxWorkDirectoryW',
'CtxCallbackNumber',
],
'time' => [
'CtxMaxDisconnectionTime',
'CtxMaxConnectionTime',
'CtxMaxIdleTime',
],
'int' => [
'CtxCfgFlags1',
'CtxCfgPresent',
'CtxKeyboardLayout',
'CtxMinEncryptionLevel',
'CtxNWLogonServer',
'CtxShadow',
],
];
/**
* The property name.
*
* @var string
*/
protected $name;
/**
* The property value.
*
* @var string|int
*/
protected $value;
/**
* The property value type.
*
* @var int
*/
protected $valueType = 1;
/**
* Pass binary TSProperty data to construct its object representation.
*
* @param string|null $value
*/
public function __construct($value = null)
{
if ($value) {
$this->decode(bin2hex($value));
}
}
/**
* Set the name for the TSProperty.
*
* @param string $name
*
* @return TSProperty
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get the name for the TSProperty.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set the value for the TSProperty.
*
* @param string|int $value
*
* @return TSProperty
*/
public function setValue($value)
{
$this->value = $value;
return $this;
}
/**
* Get the value for the TSProperty.
*
* @return string|int
*/
public function getValue()
{
return $this->value;
}
/**
* Convert the TSProperty name/value back to its binary
* representation for the userParameters blob.
*
* @return string
*/
public function toBinary()
{
$name = bin2hex($this->name);
$binValue = $this->getEncodedValueForProp($this->name, $this->value);
$valueLen = strlen(bin2hex($binValue)) / 3;
$binary = hex2bin(
$this->dec2hex(strlen($name))
.$this->dec2hex($valueLen)
.$this->dec2hex($this->valueType)
.$name
);
return $binary.$binValue;
}
/**
* Given a TSProperty blob, decode the name/value/type/etc.
*
* @param string $tsProperty
*/
protected function decode($tsProperty)
{
$nameLength = hexdec(substr($tsProperty, 0, 2));
// 1 data byte is 3 encoded bytes
$valueLength = hexdec(substr($tsProperty, 2, 2)) * 3;
$this->valueType = hexdec(substr($tsProperty, 4, 2));
$this->name = pack('H*', substr($tsProperty, 6, $nameLength));
$this->value = $this->getDecodedValueForProp($this->name, substr($tsProperty, 6 + $nameLength, $valueLength));
}
/**
* Based on the property name/value in question, get its encoded form.
*
* @param string $propName
* @param string|int $propValue
*
* @return string
*/
protected function getEncodedValueForProp($propName, $propValue)
{
if (in_array($propName, $this->propTypes['string'])) {
// Simple strings are null terminated. Unsure if this is
// needed or simply a product of how ADUC does stuff?
$value = $this->encodePropValue($propValue."\0", true);
} elseif (in_array($propName, $this->propTypes['time'])) {
// Needs to be in microseconds (assuming it is in minute format)...
$value = $this->encodePropValue($propValue * self::TIME_CONVERSION);
} else {
$value = $this->encodePropValue($propValue);
}
return $value;
}
/**
* Based on the property name in question, get its actual value from the binary blob value.
*
* @param string $propName
* @param string $propValue
*
* @return string|int
*/
protected function getDecodedValueForProp($propName, $propValue)
{
if (in_array($propName, $this->propTypes['string'])) {
// Strip away null terminators. I think this should
// be desired, otherwise it just ends in confusion.
$value = str_replace("\0", '', $this->decodePropValue($propValue, true));
} elseif (in_array($propName, $this->propTypes['time'])) {
// Convert from microseconds to minutes (how ADUC displays
// it anyway, and seems the most practical).
$value = hexdec($this->decodePropValue($propValue)) / self::TIME_CONVERSION;
} elseif (in_array($propName, $this->propTypes['int'])) {
$value = hexdec($this->decodePropValue($propValue));
} else {
$value = $this->decodePropValue($propValue);
}
return $value;
}
/**
* Decode the property by inspecting the nibbles of each blob, checking
* the control, and adding up the results into a final value.
*
* @param string $hex
* @param bool $string Whether or not this is simple string data.
*
* @return string
*/
protected function decodePropValue($hex, $string = false)
{
$decodePropValue = '';
$blobs = str_split($hex, 6);
foreach ($blobs as $blob) {
$bin = decbin(hexdec($blob));
$controlY = substr($bin, 4, 6);
$nibbleY = substr($bin, 10, 4);
$controlX = substr($bin, 14, 6);
$nibbleX = substr($bin, 20, 4);
$byte = $this->nibbleControl($nibbleX, $controlX).$this->nibbleControl($nibbleY, $controlY);
if ($string) {
$decodePropValue .= MbString::chr(bindec($byte));
} else {
$decodePropValue = $this->dec2hex(bindec($byte)).$decodePropValue;
}
}
return $decodePropValue;
}
/**
* Get the encoded property value as a binary blob.
*
* @param string $value
* @param bool $string
*
* @return string
*/
protected function encodePropValue($value, $string = false)
{
// An int must be properly padded. (then split and reversed).
// For a string, we just split the chars. This seems
// to be the easiest way to handle UTF-8 characters
// instead of trying to work with their hex values.
$chars = $string ? MbString::split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2));
$encoded = '';
foreach ($chars as $char) {
// Get the bits for the char. Using this method to ensure it is fully padded.
$bits = sprintf('%08b', $string ? MbString::ord($char) : hexdec($char));
$nibbleX = substr($bits, 0, 4);
$nibbleY = substr($bits, 4, 4);
// Construct the value with the header, high nibble, then low nibble.
$value = self::NIBBLE_HEADER;
foreach (['Y' => $nibbleY, 'X' => $nibbleX] as $nibbleType => $nibble) {
$value .= $this->getNibbleWithControl($nibbleType, $nibble);
}
// Convert it back to a binary bit stream
foreach ([0, 8, 16] as $start) {
$encoded .= $this->packBitString(substr($value, $start, 8), 8);
}
}
return $encoded;
}
/**
* PHP's pack() function has no 'b' or 'B' template. This is
* a workaround that turns a literal bit-string into a
* packed byte-string with 8 bits per byte.
*
* @param string $bits
* @param bool $len
*
* @return string
*/
protected function packBitString($bits, $len)
{
$bits = substr($bits, 0, $len);
// Pad input with zeros to next multiple of 4 above $len
$bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0');
// Split input into chunks of 4 bits, convert each to hex and pack them
$nibbles = str_split($bits, 4);
foreach ($nibbles as $i => $nibble) {
$nibbles[$i] = base_convert($nibble, 2, 16);
}
return pack('H*', implode('', $nibbles));
}
/**
* Based on the control, adjust the nibble accordingly.
*
* @param string $nibble
* @param string $control
*
* @return string
*/
protected function nibbleControl($nibble, $control)
{
// This control stays constant for the low/high nibbles,
// so it doesn't matter which we compare to
if ($control == self::NIBBLE_CONTROL['X'][1]) {
$dec = bindec($nibble);
$dec += 9;
$nibble = str_pad(decbin($dec), 4, '0', STR_PAD_LEFT);
}
return $nibble;
}
/**
* Get the nibble value with the control prefixed.
*
* If the nibble dec is <= 9, the control X equals 001011 and Y equals 001110, otherwise if the nibble dec is > 9
* the control for X or Y equals 011010. Additionally, if the dec value of the nibble is > 9, then the nibble value
* must be subtracted by 9 before the final value is constructed.
*
* @param string $nibbleType Either X or Y
* @param string $nibble
*
* @return string
*/
protected function getNibbleWithControl($nibbleType, $nibble)
{
$dec = bindec($nibble);
if ($dec > 9) {
$dec -= 9;
$control = self::NIBBLE_CONTROL[$nibbleType][1];
} else {
$control = self::NIBBLE_CONTROL[$nibbleType][0];
}
return $control.sprintf('%04d', decbin($dec));
}
/**
* Need to make sure hex values are always an even length, so pad as needed.
*
* @param int $int
* @param int $padLength The hex string must be padded to this length (with zeros).
*
* @return string
*/
protected function dec2hex($int, $padLength = 2)
{
return str_pad(dechex($int), $padLength, 0, STR_PAD_LEFT);
}
}

View File

@ -0,0 +1,295 @@
<?php
namespace Adldap\Models\Attributes;
use InvalidArgumentException;
class TSPropertyArray
{
/**
* Represents that the TSPropertyArray data is valid.
*/
const VALID_SIGNATURE = 'P';
/**
* The default values for the TSPropertyArray structure.
*
* @var array
*/
const DEFAULTS = [
'CtxCfgPresent' => 2953518677,
'CtxWFProfilePath' => '',
'CtxWFProfilePathW' => '',
'CtxWFHomeDir' => '',
'CtxWFHomeDirW' => '',
'CtxWFHomeDirDrive' => '',
'CtxWFHomeDirDriveW' => '',
'CtxShadow' => 1,
'CtxMaxDisconnectionTime' => 0,
'CtxMaxConnectionTime' => 0,
'CtxMaxIdleTime' => 0,
'CtxWorkDirectory' => '',
'CtxWorkDirectoryW' => '',
'CtxCfgFlags1' => 2418077696,
'CtxInitialProgram' => '',
'CtxInitialProgramW' => '',
];
/**
* @var string The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?)
*/
protected $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020';
/**
* @var TSProperty[]
*/
protected $tsProperty = [];
/**
* @var string
*/
protected $signature = self::VALID_SIGNATURE;
/**
* Binary data that occurs before the TSPropertyArray data in userParameters.
*
* @var string
*/
protected $preBinary = '';
/**
* Binary data that occurs after the TSPropertyArray data in userParameters.
*
* @var string
*/
protected $postBinary = '';
/**
* Construct in one of the following ways:.
*
* - Pass an array of TSProperty key => value pairs (See DEFAULTS constant).
* - Pass the userParameters binary value. The object representation of that will be decoded and constructed.
* - Pass nothing and a default set of TSProperty key => value pairs will be used (See DEFAULTS constant).
*
* @param mixed $tsPropertyArray
*/
public function __construct($tsPropertyArray = null)
{
$this->preBinary = hex2bin($this->defaultPreBinary);
if (is_null($tsPropertyArray) || is_array($tsPropertyArray)) {
$tsPropertyArray = $tsPropertyArray ?: self::DEFAULTS;
foreach ($tsPropertyArray as $key => $value) {
$tsProperty = new TSProperty();
$this->tsProperty[$key] = $tsProperty->setName($key)->setValue($value);
}
} else {
$this->decodeUserParameters($tsPropertyArray);
}
}
/**
* Check if a specific TSProperty exists by its property name.
*
* @param string $propName
*
* @return bool
*/
public function has($propName)
{
return array_key_exists(strtolower($propName), array_change_key_case($this->tsProperty));
}
/**
* Get a TSProperty object by its property name (ie. CtxWFProfilePath).
*
* @param string $propName
*
* @return TSProperty
*/
public function get($propName)
{
$this->validateProp($propName);
return $this->getTsPropObj($propName);
}
/**
* Add a TSProperty object. If it already exists, it will be overwritten.
*
* @param TSProperty $tsProperty
*
* @return $this
*/
public function add(TSProperty $tsProperty)
{
$this->tsProperty[$tsProperty->getName()] = $tsProperty;
return $this;
}
/**
* Remove a TSProperty by its property name (ie. CtxMinEncryptionLevel).
*
* @param string $propName
*
* @return $this
*/
public function remove($propName)
{
foreach (array_keys($this->tsProperty) as $property) {
if (strtolower($propName) == strtolower($property)) {
unset($this->tsProperty[$property]);
}
}
return $this;
}
/**
* Set the value for a specific TSProperty by its name.
*
* @param string $propName
* @param mixed $propValue
*
* @return $this
*/
public function set($propName, $propValue)
{
$this->validateProp($propName);
$this->getTsPropObj($propName)->setValue($propValue);
return $this;
}
/**
* Get the full binary representation of the userParameters containing the TSPropertyArray data.
*
* @return string
*/
public function toBinary()
{
$binary = $this->preBinary;
$binary .= hex2bin(str_pad(dechex(MbString::ord($this->signature)), 2, 0, STR_PAD_LEFT));
$binary .= hex2bin(str_pad(dechex(count($this->tsProperty)), 2, 0, STR_PAD_LEFT));
foreach ($this->tsProperty as $tsProperty) {
$binary .= $tsProperty->toBinary();
}
return $binary.$this->postBinary;
}
/**
* Get a simple associative array containing of all TSProperty names and values.
*
* @return array
*/
public function toArray()
{
$userParameters = [];
foreach ($this->tsProperty as $property => $tsPropObj) {
$userParameters[$property] = $tsPropObj->getValue();
}
return $userParameters;
}
/**
* Get all TSProperty objects.
*
* @return TSProperty[]
*/
public function getTSProperties()
{
return $this->tsProperty;
}
/**
* Validates that the given property name exists.
*
* @param string $propName
*/
protected function validateProp($propName)
{
if (!$this->has($propName)) {
throw new InvalidArgumentException(sprintf('TSProperty for "%s" does not exist.', $propName));
}
}
/**
* @param string $propName
*
* @return TSProperty
*/
protected function getTsPropObj($propName)
{
return array_change_key_case($this->tsProperty)[strtolower($propName)];
}
/**
* Get an associative array with all of the userParameters property names and values.
*
* @param string $userParameters
*
* @return void
*/
protected function decodeUserParameters($userParameters)
{
$userParameters = bin2hex($userParameters);
// Save the 96-byte array of reserved data, so as to not ruin anything that may be stored there.
$this->preBinary = hex2bin(substr($userParameters, 0, 96));
// The signature is a 2-byte unicode character at the front
$this->signature = MbString::chr(hexdec(substr($userParameters, 96, 2)));
// This asserts the validity of the tsPropertyArray data. For some reason 'P' means valid...
if ($this->signature != self::VALID_SIGNATURE) {
throw new InvalidArgumentException('Invalid TSPropertyArray data');
}
// The property count is a 2-byte unsigned integer indicating the number of elements for the tsPropertyArray
// It starts at position 98. The actual variable data begins at position 100.
$length = $this->addTSPropData(substr($userParameters, 100), hexdec(substr($userParameters, 98, 2)));
// Reserved data length + (count and sig length == 4) + the added lengths of the TSPropertyArray
// This saves anything after that variable TSPropertyArray data, so as to not squash anything stored there
if (strlen($userParameters) > (96 + 4 + $length)) {
$this->postBinary = hex2bin(substr($userParameters, (96 + 4 + $length)));
}
}
/**
* Given the start of TSPropertyArray hex data, and the count for the number
* of TSProperty structures in contains, parse and split out the
* individual TSProperty structures. Return the full length
* of the TSPropertyArray data.
*
* @param string $tsPropertyArray
* @param int $tsPropCount
*
* @return int The length of the data in the TSPropertyArray
*/
protected function addTSPropData($tsPropertyArray, $tsPropCount)
{
$length = 0;
for ($i = 0; $i < $tsPropCount; $i++) {
// Prop length = name length + value length + type length + the space for the length data.
$propLength = hexdec(substr($tsPropertyArray, $length, 2)) + (hexdec(substr($tsPropertyArray, $length + 2, 2)) * 3) + 6;
$tsProperty = new TSProperty(hex2bin(substr($tsPropertyArray, $length, $propLength)));
$this->tsProperty[$tsProperty->getName()] = $tsProperty;
$length += $propLength;
}
return $length;
}
}

View File

@ -0,0 +1,270 @@
<?php
namespace Adldap\Models;
use InvalidArgumentException;
/**
* Class BatchModification.
*
* A utility class to assist in the creation of LDAP
* batch modifications and ensure their validity.
*/
class BatchModification
{
/**
* The array keys to be used in batch modifications.
*/
const KEY_ATTRIB = 'attrib';
const KEY_MODTYPE = 'modtype';
const KEY_VALUES = 'values';
/**
* The original value of the attribute before modification.
*
* @var null
*/
protected $original = null;
/**
* The attribute of the modification.
*
* @var int|string
*/
protected $attribute;
/**
* The values of the modification.
*
* @var array
*/
protected $values = [];
/**
* The modtype integer of the batch modification.
*
* @var int
*/
protected $type;
/**
* Constructor.
*
* @param string|null $attribute
* @param string|int|null $type
* @param array $values
*/
public function __construct($attribute = null, $type = null, $values = [])
{
$this->setAttribute($attribute)
->setType($type)
->setValues($values);
}
/**
* Sets the original value of the attribute before modification.
*
* @param mixed $original
*
* @return $this
*/
public function setOriginal($original = null)
{
$this->original = $original;
return $this;
}
/**
* Returns the original value of the attribute before modification.
*
* @return mixed
*/
public function getOriginal()
{
return $this->original;
}
/**
* Sets the attribute of the modification.
*
* @param string $attribute
*
* @return $this
*/
public function setAttribute($attribute)
{
$this->attribute = $attribute;
return $this;
}
/**
* Returns the attribute of the modification.
*
* @return string
*/
public function getAttribute()
{
return $this->attribute;
}
/**
* Sets the values of the modification.
*
* @param array $values
*
* @return $this
*/
public function setValues(array $values = [])
{
$this->values = array_map(function ($value) {
// We need to make sure all values given to a batch modification are
// strings, otherwise we'll receive an LDAP exception when
// we try to process the modification.
return (string) $value;
}, $values);
return $this;
}
/**
* Returns the values of the modification.
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Sets the type of the modification.
*
* @param int|null $type
*
* @return $this
*/
public function setType($type = null)
{
if (!is_null($type) && !$this->isValidType($type)) {
throw new InvalidArgumentException('Given batch modification type is invalid.');
}
$this->type = $type;
return $this;
}
/**
* Returns the type of the modification.
*
* @return int
*/
public function getType()
{
return $this->type;
}
/**
* Determines if the batch modification
* is valid in its current state.
*
* @return bool
*/
public function isValid()
{
return !is_null($this->get());
}
/**
* Builds the type of modification automatically
* based on the current and original values.
*
* @return $this
*/
public function build()
{
$filtered = array_diff(
array_map('trim', $this->values),
['']
);
if (is_null($this->original)) {
// If the original value is null, we'll assume
// that the attribute doesn't exist yet.
if (!empty($filtered)) {
// If the filtered array is not empty, we can
// assume we're looking to add values
// to the current attribute.
$this->setType(LDAP_MODIFY_BATCH_ADD);
}
// If the filtered array is empty and there is no original
// value, then we can ignore this attribute since
// we can't push null values to the server.
} else {
if (empty($filtered)) {
// If there's an original value and the array is
// empty then we can assume we are looking
// to completely remove all values
// of the current attribute.
$this->setType(LDAP_MODIFY_BATCH_REMOVE_ALL);
} else {
// If the array isn't empty then we can assume
// we're looking to replace all attributes.
$this->setType(LDAP_MODIFY_BATCH_REPLACE);
}
}
return $this;
}
/**
* Returns the built batch modification array.
*
* @return array|null
*/
public function get()
{
switch ($this->type) {
case LDAP_MODIFY_BATCH_REMOVE_ALL:
// A values key cannot be provided when
// a remove all type is selected.
return [
static::KEY_ATTRIB => $this->attribute,
static::KEY_MODTYPE => $this->type,
];
case LDAP_MODIFY_BATCH_REMOVE:
// Fallthrough.
case LDAP_MODIFY_BATCH_ADD:
// Fallthrough.
case LDAP_MODIFY_BATCH_REPLACE:
return [
static::KEY_ATTRIB => $this->attribute,
static::KEY_MODTYPE => $this->type,
static::KEY_VALUES => $this->values,
];
default:
// If the modtype isn't recognized, we'll return null.
return;
}
}
/**
* Determines if the given modtype is valid.
*
* @param int $type
*
* @return bool
*/
protected function isValidType($type)
{
return in_array($type, [
LDAP_MODIFY_BATCH_REMOVE_ALL,
LDAP_MODIFY_BATCH_REMOVE,
LDAP_MODIFY_BATCH_REPLACE,
LDAP_MODIFY_BATCH_ADD,
]);
}
}

View File

@ -0,0 +1,87 @@
<?php
namespace Adldap\Models;
/**
* Class Computer.
*
* Represents an LDAP computer / server.
*/
class Computer extends Entry
{
use Concerns\HasMemberOf;
use Concerns\HasDescription;
use Concerns\HasLastLogonAndLogOff;
use Concerns\HasUserAccountControl;
use Concerns\HasCriticalSystemObject;
/**
* Returns the computers operating system.
*
* @link https://msdn.microsoft.com/en-us/library/ms679076(v=vs.85).aspx
*
* @return string
*/
public function getOperatingSystem()
{
return $this->getFirstAttribute($this->schema->operatingSystem());
}
/**
* Returns the computers operating system version.
*
* @link https://msdn.microsoft.com/en-us/library/ms679079(v=vs.85).aspx
*
* @return string
*/
public function getOperatingSystemVersion()
{
return $this->getFirstAttribute($this->schema->operatingSystemVersion());
}
/**
* Returns the computers operating system service pack.
*
* @link https://msdn.microsoft.com/en-us/library/ms679078(v=vs.85).aspx
*
* @return string
*/
public function getOperatingSystemServicePack()
{
return $this->getFirstAttribute($this->schema->operatingSystemServicePack());
}
/**
* Returns the computers DNS host name.
*
* @return string
*/
public function getDnsHostName()
{
return $this->getFirstAttribute($this->schema->dnsHostName());
}
/**
* Returns the computers bad password time.
*
* @link https://msdn.microsoft.com/en-us/library/ms675243(v=vs.85).aspx
*
* @return int
*/
public function getBadPasswordTime()
{
return $this->getFirstAttribute($this->schema->badPasswordTime());
}
/**
* Returns the computers account expiry date.
*
* @link https://msdn.microsoft.com/en-us/library/ms675098(v=vs.85).aspx
*
* @return int
*/
public function getAccountExpiry()
{
return $this->getFirstAttribute($this->schema->accountExpires());
}
}

View File

@ -0,0 +1,345 @@
<?php
namespace Adldap\Models\Concerns;
use Illuminate\Support\Arr;
trait HasAttributes
{
/**
* The default output date format for all time related methods.
*
* Default format is suited for MySQL timestamps.
*
* @var string
*/
public $dateFormat = 'Y-m-d H:i:s';
/**
* The format that is used to convert timestamps to unix timestamps.
*
* Currently set for compatibility with Active Directory.
*
* @var string
*/
protected $timestampFormat = 'YmdHis.0Z';
/**
* The models attributes.
*
* @var array
*/
protected $attributes = [];
/**
* The models original attributes.
*
* @var array
*/
protected $original = [];
/**
* Dynamically retrieve attributes on the object.
*
* @param mixed $key
*
* @return bool
*/
public function __get($key)
{
return $this->getAttribute($key);
}
/**
* Dynamically set attributes on the object.
*
* @param mixed $key
* @param mixed $value
*
* @return $this
*/
public function __set($key, $value)
{
return $this->setAttribute($key, $value);
}
/**
* Synchronizes the models original attributes
* with the model's current attributes.
*
* @return $this
*/
public function syncOriginal()
{
$this->original = $this->attributes;
return $this;
}
/**
* Returns the models attribute with the specified key.
*
* If a sub-key is specified, it will try and
* retrieve it from the parent keys array.
*
* @param int|string $key
* @param int|string $subKey
*
* @return mixed
*/
public function getAttribute($key, $subKey = null)
{
if (!$key) {
return;
}
// We'll normalize the given key to prevent case sensitivity issues.
$key = $this->normalizeAttributeKey($key);
if (is_null($subKey) && $this->hasAttribute($key)) {
return $this->attributes[$key];
} elseif ($this->hasAttribute($key, $subKey)) {
return $this->attributes[$key][$subKey];
}
}
/**
* Returns the first attribute by the specified key.
*
* @param string $key
*
* @return mixed
*/
public function getFirstAttribute($key)
{
return $this->getAttribute($key, 0);
}
/**
* Returns all of the models attributes.
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Fills the entry with the supplied attributes.
*
* @param array $attributes
*
* @return $this
*/
public function fill(array $attributes = [])
{
foreach ($attributes as $key => $value) {
$this->setAttribute($key, $value);
}
return $this;
}
/**
* Sets an attributes value by the specified key and sub-key.
*
* @param int|string $key
* @param mixed $value
* @param int|string $subKey
*
* @return $this
*/
public function setAttribute($key, $value, $subKey = null)
{
// Normalize key.
$key = $this->normalizeAttributeKey($key);
// If the key is equal to 'dn', we'll automatically
// change it to the full attribute name.
$key = ($key == 'dn' ? $this->schema->distinguishedName() : $key);
if (is_null($subKey)) {
// We need to ensure all attributes are set as arrays so all
// of our model methods retrieve attributes correctly.
$this->attributes[$key] = is_array($value) ? $value : [$value];
} else {
$this->attributes[$key][$subKey] = $value;
}
return $this;
}
/**
* Sets the first attributes value by the specified key.
*
* @param int|string $key
* @param mixed $value
*
* @return $this
*/
public function setFirstAttribute($key, $value)
{
return $this->setAttribute($key, $value, 0);
}
/**
* Sets the attributes property.
*
* Used when constructing an existing LDAP record.
*
* @param array $attributes
*
* @return $this
*/
public function setRawAttributes(array $attributes = [])
{
// We'll filter out those annoying 'count' keys returned with LDAP results,
// and lowercase all root array keys to prevent any casing issues.
$this->attributes = array_change_key_case($this->filterRawAttributes($attributes), CASE_LOWER);
// We'll pull out the distinguished name from our raw attributes
// and set it into our attributes array with the full attribute
// definition. This allows us to normalize distinguished
// names across different LDAP variants.
if ($dn = Arr::get($attributes, 'dn')) {
// In some LDAP variants, the distinguished
// name is returned as an array.
if (is_array($dn)) {
$dn = Arr::first($dn);
}
$this->setDistinguishedName($dn);
}
$this->syncOriginal();
// Set exists to true since raw attributes are only
// set in the case of attributes being loaded by
// query results.
$this->exists = true;
return $this;
}
/**
* Filters the count key recursively from raw LDAP attributes.
*
* @param array $attributes
* @param array|string $keys
*
* @return array
*/
public function filterRawAttributes(array $attributes = [], $keys = ['count', 'dn'])
{
$attributes = Arr::except($attributes, $keys);
array_walk($attributes, function (&$value) use ($keys) {
$value = is_array($value) ?
$this->filterRawAttributes($value, $keys) :
$value;
});
return $attributes;
}
/**
* Returns true / false if the specified attribute
* exists in the attributes array.
*
* @param int|string $key
* @param int|string $subKey
*
* @return bool
*/
public function hasAttribute($key, $subKey = null)
{
// Normalize key.
$key = $this->normalizeAttributeKey($key);
if (is_null($subKey)) {
return Arr::has($this->attributes, $key);
}
return Arr::has($this->attributes, "$key.$subKey");
}
/**
* Returns the number of attributes inside
* the attributes property.
*
* @return int
*/
public function countAttributes()
{
return count($this->getAttributes());
}
/**
* Returns the models original attributes.
*
* @return array
*/
public function getOriginal()
{
return $this->original;
}
/**
* Get the attributes that have been changed since last sync.
*
* @return array
*/
public function getDirty()
{
$dirty = [];
foreach ($this->attributes as $key => $value) {
if (!$this->originalIsEquivalent($key)) {
// We need to reset the array's indices using array_values due to
// LDAP requiring consecutive indices (0, 1, 2 etc.)
$dirty[$key] = array_values($value);
}
}
return $dirty;
}
/**
* Returns a normalized attribute key.
*
* @param string $key
*
* @return string
*/
protected function normalizeAttributeKey($key)
{
return strtolower($key);
}
/**
* Determine if the new and old values for a given key are equivalent.
*
* @param string $key
*
* @return bool
*/
protected function originalIsEquivalent($key)
{
if (!array_key_exists($key, $this->original)) {
return false;
}
$current = $this->attributes[$key];
$original = $this->original[$key];
if ($current === $original) {
return true;
}
return is_numeric($current) &&
is_numeric($original) &&
strcmp((string) $current, (string) $original) === 0;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Adldap\Models\Concerns;
trait HasCriticalSystemObject
{
/**
* Returns true / false if the entry is a critical system object.
*
* @return null|bool
*/
public function isCriticalSystemObject()
{
$attribute = $this->getFirstAttribute($this->schema->isCriticalSystemObject());
return $this->convertStringToBool($attribute);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Adldap\Models\Concerns;
trait HasDescription
{
/**
* Returns the models's description.
*
* @link https://msdn.microsoft.com/en-us/library/ms675492(v=vs.85).aspx
*
* @return string
*/
public function getDescription()
{
return $this->getFirstAttribute($this->schema->description());
}
/**
* Sets the models's description.
*
* @param string $description
*
* @return $this
*/
public function setDescription($description)
{
return $this->setFirstAttribute($this->schema->description(), $description);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Adldap\Models\Concerns;
use Adldap\Adldap;
use Adldap\Models\Events\Event;
trait HasEvents
{
/**
* Fires the specified model event.
*
* @param Event $event
*
* @return mixed
*/
protected function fireModelEvent(Event $event)
{
return Adldap::getEventDispatcher()->fire($event);
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Adldap\Models\Concerns;
trait HasLastLogonAndLogOff
{
/**
* Returns the models's last log off date.
*
* @link https://msdn.microsoft.com/en-us/library/ms676822(v=vs.85).aspx
*
* @return string
*/
public function getLastLogOff()
{
return $this->getFirstAttribute($this->schema->lastLogOff());
}
/**
* Returns the models's last log on date.
*
* @link https://msdn.microsoft.com/en-us/library/ms676823(v=vs.85).aspx
*
* @return string
*/
public function getLastLogon()
{
return $this->getFirstAttribute($this->schema->lastLogOn());
}
/**
* Returns the models's last log on timestamp.
*
* @link https://msdn.microsoft.com/en-us/library/ms676824(v=vs.85).aspx
*
* @return string
*/
public function getLastLogonTimestamp()
{
return $this->getFirstAttribute($this->schema->lastLogOnTimestamp());
}
}

View File

@ -0,0 +1,260 @@
<?php
namespace Adldap\Models\Concerns;
use Adldap\Utilities;
use Adldap\Models\User;
use Adldap\Models\Group;
use Adldap\Query\Collection;
trait HasMemberOf
{
/**
* Returns an array of distinguished names of groups that the current model belongs to.
*
* @link https://msdn.microsoft.com/en-us/library/ms677099(v=vs.85).aspx
*
* @return array
*/
public function getMemberOf()
{
$dns = $this->getAttribute($this->schema->memberOf());
// Normalize returned distinguished names if the attribute is null.
return is_array($dns) ? $dns : [];
}
/**
* Adds the current model to the specified group.
*
* @param string|Group $group
*
* @return bool
*/
public function addGroup($group)
{
if (is_string($group)) {
// If the group is a string, we'll assume the dev is passing
// in a DN string of the group. We'll try to locate it.
$group = $this->query->newInstance()->findByDn($group);
}
if ($group instanceof Group) {
// If the group is Group model instance, we can
// add the current models DN to the group.
return $group->addMember($this->getDn());
}
return false;
}
/**
* Removes the current model from the specified group.
*
* @param string|Group $group
*
* @return bool
*/
public function removeGroup($group)
{
if (is_string($group)) {
// If the group is a string, we'll assume the dev is passing
// in a DN string of the group. We'll try to locate it.
$group = $this->query->newInstance()->findByDn($group);
}
if ($group instanceof Group) {
// If the group is Group model instance, we can
// remove the current models DN from the group.
return $group->removeMember($this->getDn());
}
return false;
}
/**
* Removes the current model from all groups.
*
* @return array The group distinguished names that were successfully removed
*/
public function removeAllGroups()
{
$removed = [];
foreach ($this->getMemberOf() as $group) {
if ($this->removeGroup($group)) {
$removed[] = $group;
}
}
return $removed;
}
/**
* Returns the models groups that it is apart of.
*
* If a recursive option is given, groups of groups
* are retrieved and then merged with
* the resulting collection.
*
* @link https://msdn.microsoft.com/en-us/library/ms677099(v=vs.85).aspx
*
* @param array $fields
* @param bool $recursive
* @param array $visited
*
* @return Collection
*/
public function getGroups(array $fields = ['*'], $recursive = false, array $visited = [])
{
if (!in_array($this->schema->memberOf(), $fields)) {
// We want to make sure that we always select the memberof
// field in case developers want recursive members.
$fields = array_merge($fields, [$this->schema->memberOf()]);
}
$groups = $this->getGroupsByNames($this->getMemberOf(), $fields);
// We need to check if we're working with a User model. Only users
// contain a primary group. If we are, we'll merge the users
// primary group into the resulting collection.
if ($this instanceof User && $primary = $this->getPrimaryGroup()) {
$groups->push($primary);
}
// If recursive results are requested, we'll ask each group
// for their groups, and merge the resulting collection.
if ($recursive) {
/** @var Group $group */
foreach ($groups as $group) {
// We need to validate that we haven't already queried
// for this group's members so we don't allow
// infinite recursion in case of circular
// group dependencies in LDAP.
if (!in_array($group->getDn(), $visited)) {
$visited[] = $group->getDn();
$members = $group->getGroups($fields, $recursive, $visited);
/** @var Group $member */
foreach ($members as $member) {
$visited[] = $member->getDn();
}
$groups = $groups->merge($members);
}
}
}
return $groups;
}
/**
* Returns the models groups names in a single dimension array.
*
* If a recursive option is given, groups of groups
* are retrieved and then merged with
* the resulting collection.
*
* @param bool $recursive
*
* @return array
*/
public function getGroupNames($recursive = false)
{
$fields = [$this->schema->commonName(), $this->schema->memberOf()];
$names = $this->getGroups($fields, $recursive)->map(function (Group $group) {
return $group->getCommonName();
})->toArray();
return array_unique($names);
}
/**
* Determine if the current model is a member of the specified group(s).
*
* @param mixed $group
* @param bool $recursive
*
* @return bool
*/
public function inGroup($group, $recursive = false)
{
$memberOf = $this->getGroups(['cn'], $recursive);
if ($group instanceof Collection) {
// If we've been given a collection then we'll convert
// it to an array to normalize the value.
$group = $group->toArray();
}
$groups = is_array($group) ? $group : [$group];
foreach ($groups as $group) {
// We need to iterate through each given group that the
// model must be apart of, then go through the models
// actual groups and perform validation.
$exists = $memberOf->filter(function (Group $parent) use ($group) {
return $this->groupIsParent($group, $parent);
})->count() !== 0;
if (!$exists) {
// If the current group isn't at all contained
// in the memberOf collection, we'll
// return false here.
return false;
}
}
return true;
}
/**
* Retrieves groups by their distinguished name.
*
* @param array $dns
* @param array $fields
*
* @return Collection
*/
protected function getGroupsByNames(array $dns = [], $fields = [])
{
$query = $this->query->newInstance();
return $query->newCollection($dns)->map(function ($dn) use ($query, $fields) {
return $query->select($fields)->clearFilters()->findByDn($dn);
})->filter(function ($group) {
return $group instanceof Group;
});
}
/**
* Validates if the specified group is the given parent instance.
*
* @param Group|string $group
* @param Group $parent
*
* @return bool
*/
protected function groupIsParent($group, Group $parent)
{
if ($group instanceof Group) {
// We've been given a group instance, we'll compare their DNs.
return $parent->getDn() === $group->getDn();
}
if (Utilities::explodeDn($group)) {
// We've been given a DN, we'll compare it to the parents.
return $parent->getDn() === $group;
}
if (!empty($group)) {
// We've been given just a string, we'll
// compare it to the parents name.
return $parent->getCommonName() === $group;
}
return false;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Adldap\Models\Concerns;
use Adldap\Models\Attributes\AccountControl;
trait HasUserAccountControl
{
/**
* Returns the users user account control integer.
*
* @return string
*/
public function getUserAccountControl()
{
return $this->getFirstAttribute($this->schema->userAccountControl());
}
/**
* Returns the users user account control as an AccountControl object.
*
* @return AccountControl
*/
public function getUserAccountControlObject()
{
return new AccountControl($this->getUserAccountControl());
}
/**
* Sets the users account control property.
*
* @param int|string|AccountControl $accountControl
*
* @return $this
*/
public function setUserAccountControl($accountControl)
{
return $this->setAttribute($this->schema->userAccountControl(), (string) $accountControl);
}
/**
* Returns if the user is disabled.
*
* @return bool
*/
public function isDisabled()
{
return ($this->getUserAccountControl() & AccountControl::ACCOUNTDISABLE) === AccountControl::ACCOUNTDISABLE;
}
/**
* Returns if the user is enabled.
*
* @return bool
*/
public function isEnabled()
{
return $this->getUserAccountControl() === null ? false : !$this->isDisabled();
}
}

View File

@ -0,0 +1,453 @@
<?php
namespace Adldap\Models\Concerns;
trait HasUserProperties
{
/**
* Returns the users country.
*
* @return string|null
*/
public function getCountry()
{
return $this->getFirstAttribute($this->schema->country());
}
/**
* Sets the users country.
*
* @param string $country
*
* @return $this
*/
public function setCountry($country)
{
return $this->setFirstAttribute($this->schema->country(), $country);
}
/**
* Returns the users department.
*
* @link https://msdn.microsoft.com/en-us/library/ms675490(v=vs.85).aspx
*
* @return string|null
*/
public function getDepartment()
{
return $this->getFirstAttribute($this->schema->department());
}
/**
* Sets the users department.
*
* @param string $department
*
* @return $this
*/
public function setDepartment($department)
{
return $this->setFirstAttribute($this->schema->department(), $department);
}
/**
* Returns the users email address.
*
* @link https://msdn.microsoft.com/en-us/library/ms676855(v=vs.85).aspx
*
* @return string|null
*/
public function getEmail()
{
return $this->getFirstAttribute($this->schema->email());
}
/**
* Sets the users email.
*
* Keep in mind this will remove all other
* email addresses the user currently has.
*
* @param string $email
*
* @return $this
*/
public function setEmail($email)
{
return $this->setFirstAttribute($this->schema->email(), $email);
}
/**
* Returns the users facsimile number.
*
* @link https://msdn.microsoft.com/en-us/library/ms675675(v=vs.85).aspx
*
* @return string|null
*/
public function getFacsimileNumber()
{
return $this->getFirstAttribute($this->schema->facsimile());
}
/**
* Sets the users facsimile number.
*
* @param string $number
*
* @return $this
*/
public function setFacsimileNumber($number)
{
return $this->setFirstAttribute($this->schema->facsimile(), $number);
}
/**
* Returns the users first name.
*
* @link https://msdn.microsoft.com/en-us/library/ms675719(v=vs.85).aspx
*
* @return string|null
*/
public function getFirstName()
{
return $this->getFirstAttribute($this->schema->firstName());
}
/**
* Sets the users first name.
*
* @param string $firstName
*
* @return $this
*/
public function setFirstName($firstName)
{
return $this->setFirstAttribute($this->schema->firstName(), $firstName);
}
/**
* Returns the users initials.
*
* @return string|null
*/
public function getInitials()
{
return $this->getFirstAttribute($this->schema->initials());
}
/**
* Sets the users initials.
*
* @param string $initials
*
* @return $this
*/
public function setInitials($initials)
{
return $this->setFirstAttribute($this->schema->initials(), $initials);
}
/**
* Returns the users IP Phone.
*
* @return string|null
*/
public function getIpPhone()
{
return $this->getFirstAttribute($this->schema->ipPhone());
}
/**
* Sets the users IP phone.
*
* @param string $ip
*
* @return $this
*/
public function setIpPhone($ip)
{
return $this->setFirstAttribute($this->schema->ipPhone(), $ip);
}
/**
* Returns the users last name.
*
* @link https://msdn.microsoft.com/en-us/library/ms679872(v=vs.85).aspx
*
* @return string|null
*/
public function getLastName()
{
return $this->getFirstAttribute($this->schema->lastName());
}
/**
* Sets the users last name.
*
* @param string $lastName
*
* @return $this
*/
public function setLastName($lastName)
{
return $this->setFirstAttribute($this->schema->lastName(), $lastName);
}
/**
* Returns the users postal code.
*
* @return string|null
*/
public function getPostalCode()
{
return $this->getFirstAttribute($this->schema->postalCode());
}
/**
* Sets the users postal code.
*
* @param string $postalCode
*
* @return $this
*/
public function setPostalCode($postalCode)
{
return $this->setFirstAttribute($this->schema->postalCode(), $postalCode);
}
/**
* Get the users post office box.
*
* @return string|null
*/
public function getPostOfficeBox()
{
return $this->getFirstAttribute($this->schema->postOfficeBox());
}
/**
* Sets the users post office box.
*
* @param string|int $box
*
* @return $this
*/
public function setPostOfficeBox($box)
{
return $this->setFirstAttribute($this->schema->postOfficeBox(), $box);
}
/**
* Sets the users proxy addresses.
*
* This will remove all proxy addresses on the user and insert the specified addresses.
*
* @link https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx
*
* @param array $addresses
*
* @return $this
*/
public function setProxyAddresses(array $addresses = [])
{
return $this->setAttribute($this->schema->proxyAddresses(), $addresses);
}
/**
* Add's a single proxy address to the user.
*
* @param string $address
*
* @return $this
*/
public function addProxyAddress($address)
{
$addresses = $this->getProxyAddresses();
$addresses[] = $address;
return $this->setAttribute($this->schema->proxyAddresses(), $addresses);
}
/**
* Returns the users proxy addresses.
*
* @link https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx
*
* @return array
*/
public function getProxyAddresses()
{
return $this->getAttribute($this->schema->proxyAddresses()) ?? [];
}
/**
* Returns the users street address.
*
* @return string|null
*/
public function getStreetAddress()
{
return $this->getFirstAttribute($this->schema->streetAddress());
}
/**
* Sets the users street address.
*
* @param string $address
*
* @return $this
*/
public function setStreetAddress($address)
{
return $this->setFirstAttribute($this->schema->streetAddress(), $address);
}
/**
* Returns the users title.
*
* @link https://msdn.microsoft.com/en-us/library/ms680037(v=vs.85).aspx
*
* @return string|null
*/
public function getTitle()
{
return $this->getFirstAttribute($this->schema->title());
}
/**
* Sets the users title.
*
* @param string $title
*
* @return $this
*/
public function setTitle($title)
{
return $this->setFirstAttribute($this->schema->title(), $title);
}
/**
* Returns the users telephone number.
*
* @link https://msdn.microsoft.com/en-us/library/ms680027(v=vs.85).aspx
*
* @return string|null
*/
public function getTelephoneNumber()
{
return $this->getFirstAttribute($this->schema->telephone());
}
/**
* Sets the users telephone number.
*
* @param string $number
*
* @return $this
*/
public function setTelephoneNumber($number)
{
return $this->setFirstAttribute($this->schema->telephone(), $number);
}
/**
* Returns the users primary mobile phone number.
*
* @return string|null
*/
public function getMobileNumber()
{
return $this->getFirstAttribute($this->schema->mobile());
}
/**
* Sets the users primary mobile phone number.
*
* @param string $number
*
* @return $this
*/
public function setMobileNumber($number)
{
return $this->setFirstAttribute($this->schema->mobile(), $number);
}
/**
* Returns the users secondary (other) mobile phone number.
*
* @return string|null
*/
public function getOtherMobileNumber()
{
return $this->getFirstAttribute($this->schema->otherMobile());
}
/**
* Sets the users secondary (other) mobile phone number.
*
* @param string $number
*
* @return $this
*/
public function setOtherMobileNumber($number)
{
return $this->setFirstAttribute($this->schema->otherMobile(), $number);
}
/**
* Returns the users other mailbox attribute.
*
* @link https://msdn.microsoft.com/en-us/library/ms679091(v=vs.85).aspx
*
* @return array
*/
public function getOtherMailbox()
{
return $this->getAttribute($this->schema->otherMailbox());
}
/**
* Sets the users other mailboxes.
*
* @param array $otherMailbox
*
* @return $this
*/
public function setOtherMailbox($otherMailbox = [])
{
return $this->setAttribute($this->schema->otherMailbox(), $otherMailbox);
}
/**
* Returns the distinguished name of the user who is the user's manager.
*
* @return string|null
*/
public function getManager()
{
return $this->getFirstAttribute($this->schema->manager());
}
/**
* Sets the distinguished name of the user who is the user's manager.
*
* @param string $managerDn
*
* @return $this
*/
public function setManager($managerDn)
{
return $this->setFirstAttribute($this->schema->manager(), $managerDn);
}
/**
* Returns the users mail nickname.
*
* @return string|null
*/
public function getMailNickname()
{
return $this->getFirstAttribute($this->schema->emailNickname());
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Adldap\Models;
/**
* Class Contact.
*
* Represents an LDAP contact.
*/
class Contact extends Entry
{
use Concerns\HasMemberOf;
use Concerns\HasUserProperties;
}

View File

@ -0,0 +1,28 @@
<?php
namespace Adldap\Models;
/**
* Class Container.
*
* Represents an LDAP container.
*/
class Container extends Entry
{
use Concerns\HasDescription;
use Concerns\HasCriticalSystemObject;
/**
* Returns the containers system flags integer.
*
* An integer value that contains flags that define additional properties of the class.
*
* @link https://msdn.microsoft.com/en-us/library/ms680022(v=vs.85).aspx
*
* @return string
*/
public function getSystemFlags()
{
return $this->getFirstAttribute($this->schema->systemFlags());
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Adldap\Models;
/**
* Class Entry.
*
* Represents an LDAP record that could not be identified as another type of model.
*/
class Entry extends Model
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Models\Events;
class Created extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Models\Events;
class Creating extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Models\Events;
class Deleted extends Event
{
//
}

View File

@ -0,0 +1,8 @@
<?php
namespace Adldap\Models\Events;
class Deleting extends Event
{
//
}

View File

@ -0,0 +1,35 @@
<?php
namespace Adldap\Models\Events;
use Adldap\Models\Model;
abstract class Event
{
/**
* The model that the event is being triggered on.
*
* @var Model
*/
protected $model;
/**
* Constructor.
*
* @param Model $model
*/
public function __construct(Model $model)
{
$this->model = $model;
}
/**
* Returns the model that generated the event.
*
* @return Model
*/
public function getModel()
{
return $this->model;
}
}

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