/** * Get the default address list for the domain forest * * @return Object The default global address list */ public function defaultGAL() { if ($this->gal instanceof Object) { return $this->gal; } $task = new \ADX\Core\Task(Enums\Operation::OpSearch, $this->link); $gal = $task->base(static::$base)->filter(q::a(['objectCategory' => 'msExchConfigurationContainer']))->attributes('globalAddressList')->run()->first(); $this->gal = Object::read($gal->globalAddressList(0), static::$attributes, $this->link); return $this->gal; }
/** * Get all mailbox stores for a particular Exchange server * * @param Object|string The Exchange server for which to return mailbox stores * * @return Result|mixed The Result containing all the matched objects or whatever the {@link self::_process_result()} returns */ public function for_server($exchangeServer = null) { $serverFilter = $exchangeServer !== null ? q::a(['msExchOwningServer' => "{$exchangeServer}"]) : null; return $this->where($serverFilter); }
/** * Pull changes from Active Directory for a given query * * Use this method to set up a search query and later get all changed objects * for that query. This method returns a special {@link Delta} object that preserves * your search configuration and you use this object for consequent * searches. It also contains your search results. When you run the method for the first time, * it behaves as if you performed just an ordinary search, but on consequent searches, * it only returns objects that were modified or deleted since you last ran the search query. * * <br> * * <p class='alert'>Note that due to the way this feature is implemented it is impossible to * search for objects where only specific attributes have been modified - the method returns all objects * that were modified in any way since your last execution and it is your task to determine if * the changes happened on attributes that you are interested in.</p> * <br> * * This method takes two possible forms of arguments:<br> * When you run it for the first time, you pass the search parameters * as for any other ldap search query - the search filter, the list of attributes to be * returned and a fully configured {@link Link} instance. * * <br> * * For all consecutive executions, you only pass the {@link Delta} instance * that you got when you ran the method for the first time and, again, a configured {@link Link} * instance. * * <h2>Example:</h2> * To watch for changes on all users in your domain: * <code> * // Establish connection to your directory server * $link = new Link( 'example.com' ); * $link->bind( '*****@*****.**', 'SecretPwd' ); * * // When called for the first time for a particular query, you get * // an instance of Delta class - this instance contains the results * // of your query as well as all the information/state necessary * // to perform subsequent queries to get all changes for that resultset * $delta = Task::changes( '(objectcategory=person)', ['mail', 'name', 'memberof'], $link ); * $result = $delta->result; // Here's where the actual data is * // Do something with result... * * // Now it's time to store the $delta instance someplace safe so we can get * // changes for this query at a later time - you should serialise the instance * // and store it somewhere - i.e. in a database or on disk * file_put_contents( 'my_query_state.txt', serialize( $delta ) ); // Always serialise! * * // Many moments pass... * // In another script, far, far away... * * // It's time to see which objects have been modified since our last run * // Connect to ldap again * $link = new Link( 'example.com' ); * $link->bind( '*****@*****.**', 'SecretPwd' ); * * // Get the previous query state from the file * $delta = unserialize( file_get_contents( 'my_query_state.txt' ) ); * // And get all the objects that have been changed since we last * // run the changes method, but this time we only pass in * // the instance of Delta class - it contains all the information * // about our previous query so we don't have to configure it again * $delta = Task::changes( $delta, $link ); * $result = $delta->result; // Changed objects are here again! * * // Notice that now we have a new instance of the Delta class in the * // $class variable - reflecting the fact that we just ran the query again * * // And so the story continues on and on... * </code> * * <h2>Things you should know</h2> * <ul> * <li>You must always connect to the same domain controller for a particular query, * otherwise you will receive the full resultset on each run</li> * <li>To track deleted objects, all returned objects have an <i>isDeleted</i> * {@link Attribute} automatically that you can use to check if that object has been deleted</li> * <li>You cannot use BaseDN overrides with this feature at this time * ( this might be implemented in the future )</li> * </ul> * * @return Delta Object containing the state and data of the changes * * @see <a href="http://msdn.microsoft.com/en-us/library/ms677627.aspx">MSDN - Polling for changes using usnChanged</a> */ public static function changes() { $args = func_get_args(); $link = array_pop($args); // Link is always last $rootDSE = $link->rootDSE; $server = $rootDSE->dnsHostName(0); // We will only be able to get a diff when talking to the same server next time if ($args[0] instanceof Delta) { $last_delta = $args[0]; $filter = $last_delta->filter; $attributes = $last_delta->attributes; $boundaryUSN = $last_delta->server == $server ? $last_delta->cookie + 1 : 0; // Pull all data if we are dealing with a different server // Include deleted objects in the resultset $link->show_deleted(true); // Also make this control extension critical } else { $filter = $args[0]; $attributes = $args[1]; $boundaryUSN = 0; } $task = new static(Enums\Operation::OpSearch, $link); $task->filter(q::a("(usnChanged>={$boundaryUSN})", $filter))->attributes(array_merge($attributes, ['isDeleted'])); // Include isDeleted to help identifying deleted objects // Run, Forest, run! $result = $task->run_paged(); return new Delta($result, $filter, $attributes, $server, $rootDSE->highestCommittedUSN(0)); }
/** * Further customise the Selector by providing additional filter * * This is very useful if you want to write a generic Selector for all users, * but sometimes you only need to get disabled users. So, instead of writing * a new Selector for that purpose, you can use the `where()` method to refine * the search results. * * @param string A valid ldap filter. This will be added to the main filter using '&' logic * * @return ADX\Core\Result|mixed The Result containing all the matched objects or * whatever the {@link self::_process_result()} returns */ public function where($filter) { // Build the search filter $query = q::a(static::$filter, $filter); // If we already have data for this query, no need to load it from server again if ($this->query === $query) { return $this->_lookup(); } // Remove the previous resultset and execute the query again $this->result = null; $this->query = $query; return $this->_lookup(); }
/** * Get all transfer agents for a particular Exchange server * * @param Object|string The Exchange server for which to return mailbox stores * * @return Result|mixed The Result containing all the matched objects or whatever the {@link self::_process_result()} returns */ public function for_server($exchangeServer = null) { $serverFilter = $exchangeServer !== null ? q::a(['msExchResponsibleMTAServerBL' => "{$exchangeServer}"]) : null; return $this->where($serverFilter); }
/** * Build the local schema from server, using provided {@link Link} * * This method creates the locally cached schema from all schema objects located in * CN=Schema,CN=Configuration ( or similar, depending on domain configuration ). * The location of the Schema is taken from the RootDSE's "schemanamingcontext" entry. * * @param Link The Link object to be used to connect to directory server * * @return void */ public static function build(Link $adxLink) { // Define where to store the schema definition $schemaDir = ADX_ROOT_PATH . static::$schema_dir; // Prepare the schema folder either by cleaning it's contents or by creating it file_exists($schemaDir) ? static::flush() : mkdir($schemaDir, 0755); $schema_base = $adxLink->rootDSE->schemaNamingContext(0); // schemanamingcontext is loaded by default // Create the tasks... // I have to create them separately because I have two different // sets of attributes that I need to have loaded $tasks[0] = new Task(Enums\Operation::OpList, $adxLink); $tasks[0]->use_pages(500)->base($schema_base)->filter(q::a(['objectclass' => 'attributeschema']))->attributes(static::$attribute_properties); $tasks[1] = new Task(Enums\Operation::OpList, $adxLink); $tasks[1]->use_pages(500)->base($schema_base)->filter(q::a(['objectclass' => 'classschema']))->attributes(static::$class_properties); // And retrieve the schema objects! foreach ($tasks as $task) { // Do not use the Task::run_paged() method as it will very likely hit the memory execution limit. // Instead, mimick that method's functionality and handle the data for each page separately do { $objects = $task->run(); if ($objects) { // Loop through the schema objects and save them to a local file, named // after the attribute they represent foreach ($objects as $object) { $filename = $object->ldapDisplayName(0) . ".json"; $data = $object->json(); file_put_contents($schemaDir . ADX_DS . strtolower($filename), $data); } } else { throw new Exception('Maximum number of referrals reached'); } } while (!$task->complete); } // Generate a lockfile to identify the fact that the Schema is present file_put_contents($schemaDir . ADX_DS . '.lockfile', time()); }