blob: 3c1b85edd635a973f6e783367ab872d9f18668fe [file] [log] [blame]
<?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;
}
}