blob: 3c1b85edd635a973f6e783367ab872d9f18668fe [file] [log] [blame]
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +02001<?php
2
3namespace Adldap\Models\Concerns;
4
5use Adldap\Utilities;
6use Adldap\Models\User;
7use Adldap\Models\Group;
8use Adldap\Query\Collection;
9
10trait HasMemberOf
11{
12 /**
13 * Returns an array of distinguished names of groups that the current model belongs to.
14 *
15 * @link https://msdn.microsoft.com/en-us/library/ms677099(v=vs.85).aspx
16 *
17 * @return array
18 */
19 public function getMemberOf()
20 {
21 $dns = $this->getAttribute($this->schema->memberOf());
22
23 // Normalize returned distinguished names if the attribute is null.
24 return is_array($dns) ? $dns : [];
25 }
26
27 /**
28 * Adds the current model to the specified group.
29 *
30 * @param string|Group $group
31 *
32 * @return bool
33 */
34 public function addGroup($group)
35 {
36 if (is_string($group)) {
37 // If the group is a string, we'll assume the dev is passing
38 // in a DN string of the group. We'll try to locate it.
39 $group = $this->query->newInstance()->findByDn($group);
40 }
41
42 if ($group instanceof Group) {
43 // If the group is Group model instance, we can
44 // add the current models DN to the group.
45 return $group->addMember($this->getDn());
46 }
47
48 return false;
49 }
50
51 /**
52 * Removes the current model from the specified group.
53 *
54 * @param string|Group $group
55 *
56 * @return bool
57 */
58 public function removeGroup($group)
59 {
60 if (is_string($group)) {
61 // If the group is a string, we'll assume the dev is passing
62 // in a DN string of the group. We'll try to locate it.
63 $group = $this->query->newInstance()->findByDn($group);
64 }
65
66 if ($group instanceof Group) {
67 // If the group is Group model instance, we can
68 // remove the current models DN from the group.
69 return $group->removeMember($this->getDn());
70 }
71
72 return false;
73 }
74
75 /**
76 * Removes the current model from all groups.
77 *
78 * @return array The group distinguished names that were successfully removed
79 */
80 public function removeAllGroups()
81 {
82 $removed = [];
83
84 foreach ($this->getMemberOf() as $group) {
85 if ($this->removeGroup($group)) {
86 $removed[] = $group;
87 }
88 }
89
90 return $removed;
91 }
92
93 /**
94 * Returns the models groups that it is apart of.
95 *
96 * If a recursive option is given, groups of groups
97 * are retrieved and then merged with
98 * the resulting collection.
99 *
100 * @link https://msdn.microsoft.com/en-us/library/ms677099(v=vs.85).aspx
101 *
102 * @param array $fields
103 * @param bool $recursive
104 * @param array $visited
105 *
106 * @return Collection
107 */
108 public function getGroups(array $fields = ['*'], $recursive = false, array $visited = [])
109 {
110 if (!in_array($this->schema->memberOf(), $fields)) {
111 // We want to make sure that we always select the memberof
112 // field in case developers want recursive members.
113 $fields = array_merge($fields, [$this->schema->memberOf()]);
114 }
115
116 $groups = $this->getGroupsByNames($this->getMemberOf(), $fields);
117
118 // We need to check if we're working with a User model. Only users
119 // contain a primary group. If we are, we'll merge the users
120 // primary group into the resulting collection.
121 if ($this instanceof User && $primary = $this->getPrimaryGroup()) {
122 $groups->push($primary);
123 }
124
125 // If recursive results are requested, we'll ask each group
126 // for their groups, and merge the resulting collection.
127 if ($recursive) {
128 /** @var Group $group */
129 foreach ($groups as $group) {
130 // We need to validate that we haven't already queried
131 // for this group's members so we don't allow
132 // infinite recursion in case of circular
133 // group dependencies in LDAP.
134 if (!in_array($group->getDn(), $visited)) {
135 $visited[] = $group->getDn();
136
137 $members = $group->getGroups($fields, $recursive, $visited);
138
139 /** @var Group $member */
140 foreach ($members as $member) {
141 $visited[] = $member->getDn();
142 }
143
144 $groups = $groups->merge($members);
145 }
146 }
147 }
148
149 return $groups;
150 }
151
152 /**
153 * Returns the models groups names in a single dimension array.
154 *
155 * If a recursive option is given, groups of groups
156 * are retrieved and then merged with
157 * the resulting collection.
158 *
159 * @param bool $recursive
160 *
161 * @return array
162 */
163 public function getGroupNames($recursive = false)
164 {
165 $fields = [$this->schema->commonName(), $this->schema->memberOf()];
166
167 $names = $this->getGroups($fields, $recursive)->map(function (Group $group) {
168 return $group->getCommonName();
169 })->toArray();
170
171 return array_unique($names);
172 }
173
174 /**
175 * Determine if the current model is a member of the specified group(s).
176 *
177 * @param mixed $group
178 * @param bool $recursive
179 *
180 * @return bool
181 */
182 public function inGroup($group, $recursive = false)
183 {
184 $memberOf = $this->getGroups(['cn'], $recursive);
185
186 if ($group instanceof Collection) {
187 // If we've been given a collection then we'll convert
188 // it to an array to normalize the value.
189 $group = $group->toArray();
190 }
191
192 $groups = is_array($group) ? $group : [$group];
193
194 foreach ($groups as $group) {
195 // We need to iterate through each given group that the
196 // model must be apart of, then go through the models
197 // actual groups and perform validation.
198 $exists = $memberOf->filter(function (Group $parent) use ($group) {
199 return $this->groupIsParent($group, $parent);
200 })->count() !== 0;
201
202 if (!$exists) {
203 // If the current group isn't at all contained
204 // in the memberOf collection, we'll
205 // return false here.
206 return false;
207 }
208 }
209
210 return true;
211 }
212
213 /**
214 * Retrieves groups by their distinguished name.
215 *
216 * @param array $dns
217 * @param array $fields
218 *
219 * @return Collection
220 */
221 protected function getGroupsByNames(array $dns = [], $fields = [])
222 {
223 $query = $this->query->newInstance();
224
225 return $query->newCollection($dns)->map(function ($dn) use ($query, $fields) {
226 return $query->select($fields)->clearFilters()->findByDn($dn);
227 })->filter(function ($group) {
228 return $group instanceof Group;
229 });
230 }
231
232 /**
233 * Validates if the specified group is the given parent instance.
234 *
235 * @param Group|string $group
236 * @param Group $parent
237 *
238 * @return bool
239 */
240 protected function groupIsParent($group, Group $parent)
241 {
242 if ($group instanceof Group) {
243 // We've been given a group instance, we'll compare their DNs.
244 return $parent->getDn() === $group->getDn();
245 }
246
247 if (Utilities::explodeDn($group)) {
248 // We've been given a DN, we'll compare it to the parents.
249 return $parent->getDn() === $group;
250 }
251
252 if (!empty($group)) {
253 // We've been given just a string, we'll
254 // compare it to the parents name.
255 return $parent->getCommonName() === $group;
256 }
257
258 return false;
259 }
260}