summaryrefslogtreecommitdiffstats
path: root/modules/ldap/lib/Auth/Process/AttributeAddUsersGroups.php
blob: 06872b140b1ec4a1aa182c1a5054839e5a9ec89d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
<?php

/**
 * Does a reverse membership lookup on the logged in user,
 * looking for groups it is a member of and adds them to
 * a defined attribute, in DN format.
 *
 * @author Ryan Panning <panman@traileyes.com>
 * @package simpleSAMLphp
 * @version $Id$
 */
class sspmod_ldap_Auth_Process_AttributeAddUsersGroups extends sspmod_ldap_Auth_Process_BaseFilter {


	/**
	 * This is run when the filter is processed by SimpleSAML.
	 * It will attempt to find the current users groups using
	 * the best method possible for the LDAP product. The groups
	 * are then added to the request attributes.
	 *
	 * @throws SimpleSAML_Error_Exception
	 * @param $request
	 */
	public function process(&$request) {
		assert('is_array($request)');
		assert('array_key_exists("Attributes", $request)');

		// Log the process
		SimpleSAML_Logger::debug(
			$this->title . 'Attempting to get the users groups...'
		);

		// Reference the attributes, just to make the names shorter
		$attributes =& $request['Attributes'];
		$map =& $this->attribute_map;

		// Get the users groups from LDAP
		$groups = $this->getGroups($attributes);

		// Make the array if it is not set already
		if (!isset($attributes[$map['groups']])) {
			$attributes[$map['groups']] = array();
		}

		// Must be an array, else cannot merge groups
		if (!is_array($attributes[$map['groups']])) {
			throw new SimpleSAML_Error_Exception(
				$this->title . 'The group attribute [' . $map['groups'] .
				'] is not an array of group DNs. ' . $this->var_export($attributes[$map['groups']])
			);
		}

		// Add the users group(s)
		$group_attribute =& $attributes[$map['groups']];
		$group_attribute = array_merge($group_attribute, $groups);
		$group_attribute = array_unique($group_attribute);

		// All done
		SimpleSAML_Logger::debug(
			$this->title . 'Added users groups to the group attribute [' .
			$map['groups'] . ']: ' . implode('; ', $groups)
		);
	}


	/**
	 * This section of code was broken out because the child
	 * filter AuthorizeByGroup can use this method as well.
	 * Based on the LDAP product, it will do an optimized search
	 * using the required attribute values from the user to
	 * get their group membership, recursively.
	 *
	 * @throws SimpleSAML_Error_Exception
	 * @param array $attributes
	 * @return array
	 */
	protected function getGroups(array $attributes) {

		// Reference the map, just to make the name shorter
		$map =& $this->attribute_map;

		// Log the request
		SimpleSAML_Logger::debug(
			$this->title . 'Checking for groups based on the best method for the LDAP product.'
		);

		// Based on the directory service, search LDAP for groups.
		// If any attributes are needed, prepare them before calling search method
		switch ($this->product) {

			case 'ACTIVEDIRECTORY':

				// Log the AD specific search
				SimpleSAML_Logger::debug(
					$this->title . 'Searching LDAP using ActiveDirectory specific method.'
				);

				// Make sure the defined dn attribute exists
				if (!isset($attributes[$map['dn']])) {
					throw new SimpleSAML_Error_Exception(
						$this->title . 'The DN attribute [' . $map['dn'] .
						'] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes))
					);
				}

				// DN attribute must have a value
				if (!isset($attributes[$map['dn']][0]) || !$attributes[$map['dn']][0]) {
					throw new SimpleSAML_Error_Exception(
						$this->title . 'The DN attribute [' . $map['dn'] .
						'] does not have a [0] value defined. ' . $this->var_export($attributes[$map['dn']])
					);
				}

				// Pass to the AD specific search
				$groups = $this->searchActiveDirectory($attributes[$map['dn']][0]);
				break;

			default:

				// Log the general search
				SimpleSAML_Logger::debug(
					$this->title . 'Searching LDAP using the default search method.'
				);

				// Make sure the defined memberOf attribute exists
				if (!isset($attributes[$map['memberof']])) {
					throw new SimpleSAML_Error_Exception(
						$this->title . 'The memberof attribute [' . $map['memberof'] .
						'] is not defined in the users Attributes: ' . implode(', ', array_keys($attributes))
					);
				}

				// MemberOf must be an array of group DN's
				if (!is_array($attributes[$map['memberof']])) {
					throw new SimpleSAML_Error_Exception(
						$this->title . 'The memberof attribute [' . $map['memberof'] .
						'] is not an array of group DNs. ' . $this->var_export($attributes[$map['memberof']])
					);
				}

				// Search for the users group membership, recursively
				$groups = $this->search($attributes[$map['memberof']]);
		}

		// All done
		SimpleSAML_Logger::debug(
			$this->title . 'User found to be a member of the groups:' . implode('; ', $groups)
		);
		return $groups;
	}


	/**
	 * Looks for groups from the list of DN's passed. Also
	 * recursively searches groups for further membership.
	 * Avoids loops by only searching a DN once. Returns
	 * the list of groups found.
	 *
	 * @param array $memberof
	 * @return array
	 */
	protected function search($memberof) {
		assert('is_array($memberof)');

		// Used to determine what DN's have already been searched
		static $searched = array();

		// Init the groups variable
		$groups = array();

		// Shorten the variable name
		$map =& $this->attribute_map;

		// Log the search
		SimpleSAML_Logger::debug(
			$this->title . 'Checking DNs for groups.' .
			' DNs: '. implode('; ', $memberof) .
			' Attributes: ' . $map['memberof'] . ', ' . $map['type'] .
			' Group Type: ' . $this->type_map['group']
		);

		// Check each DN of the passed memberOf
		foreach ($memberof as $dn) {

			// Avoid infinite loops, only need to check a DN once
			if (isset($searched[$dn])) {
				continue;
			}

			// Track all DN's that are searched
			// Use DN for key as well, isset() is faster than in_array()
			$searched[$dn] = $dn;

			// Query LDAP for the attribute values for the DN
			try {
				$attributes = $this->getLdap()->getAttributes($dn, array($map['memberof'], $map['type']));
			} catch (SimpleSAML_Error_AuthSource $e) {
				continue; // DN must not exist, just continue. Logged by the LDAP object
			}

			// Only look for groups
			if (!in_array($this->type_map['group'], $attributes[$map['type']])) {
				continue;
			}

			// Add to found groups array
			$groups[] = $dn;

			// Recursively search "sub" groups
			$groups = array_merge($groups, $this->search($attributes[$map['memberof']]));
		}

		// Return only the unique group names
		return array_unique($groups);
	}


	/**
	 * Searches LDAP using a ActiveDirectory specific filter,
	 * looking for group membership for the users DN. Returns
	 * the list of group DNs retrieved.
	 *
	 * @param string $dn
	 * @return array
	 */
	protected function searchActiveDirectory($dn) {
		assert('is_string($dn) && $dn != ""');

		// Shorten the variable name
		$map =& $this->attribute_map;

		// Log the search
		SimpleSAML_Logger::debug(
			$this->title . 'Searching ActiveDirectory group membership.' .
			' DN: ' . $dn .
			' DN Attribute: ' . $map['dn'] .
			' Member Attribute: ' . $map['member'] .
			' Type Attribute: ' . $map['type'] .
			' Type Value: ' . $this->type_map['group'] .
			' Base: ' . implode('; ', $this->base_dn)
		);

		// AD connections should have this set
		$this->getLdap()->setOption(LDAP_OPT_REFERRALS, 0);

		// Search AD with the specific recursive flag
		try {
			$entries = $this->getLdap()->searchformultiple(
				$this->base_dn,
				array($map['type'] => $this->type_map['group'], $map['member'] . ':1.2.840.113556.1.4.1941:' => $dn),
				array($map['dn'])
			);

		// The search may throw an exception if no entries
		// are found, unlikely but possible.
		} catch (SimpleSAML_Error_UserNotFound $e) {
			return array();
		}

		//Init the groups
		$groups = array();

		// Check each entry..
		foreach ($entries as $entry) {

			// Check for the DN using the original attribute name
			if (isset($entry[$map['dn']][0])) {
				$groups[] = $entry[$map['dn']][0];
				continue;
			}

			// Sometimes the returned attribute names are lowercase
			if (isset($entry[strtolower($map['dn'])][0])) {
				$groups[] = $entry[strtolower($map['dn'])][0];
				continue;
			}

			// AD queries also seem to return the objects dn by default
			if (isset($entry['dn'])) {
				$groups[] = $entry['dn'];
				continue;
			}

			// Could not find DN, log and continue
			SimpleSAML_Logger::notice(
				$this->title . 'The DN attribute [' .
				implode(', ', array($map['dn'], strtolower($map['dn']), 'dn')) .
				'] could not be found in the entry. ' . $this->var_export($entry)
			);
		}

		// All done
		return $groups;
	}
}