Skip to content

Commit 3e757f6

Browse files
committed
Add 'bilateral attributes' feature to AttributeLimit authproc module
1 parent 27b27be commit 3e757f6

3 files changed

Lines changed: 256 additions & 52 deletions

File tree

modules/core/docs/authproc_attributelimit.md

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,121 +8,144 @@ metadata. The configuration is a list of attributes that should be allowed. In c
88
release some specific values, make the name of the attribute the key of the array, and its value an array with all the
99
different values allowed for it.
1010

11+
You may need to send special bilateral, or local attributes to a set of SPs that are not specified in SP's metadata.
12+
In this case you can list the SPs then add the name of the attributes to send these SPs, or you can list the attributes and then specify the SPs to send them.
13+
Find the examples below.
14+
1115
Examples
1216
--------
1317

1418
Here you will find a few examples on how to use this simple module:
1519

1620
Limit to the `cn` and `mail` attribute:
1721

18-
'authproc' => array(
19-
50 => array(
22+
'authproc' => [
23+
50 => [
2024
'class' => 'core:AttributeLimit',
2125
'cn', 'mail'
22-
),
23-
),
26+
],
27+
],
2428

2529
Allow `eduPersonTargetedID` and `eduPersonAffiliation` by default, but allow the metadata to override the limitation.
2630

27-
'authproc' => array(
28-
50 => array(
31+
'authproc' => [
32+
50 => [
2933
'class' => 'core:AttributeLimit',
30-
'default' => TRUE,
34+
'default' => true,
3135
'eduPersonTargetedID', 'eduPersonAffiliation',
32-
),
33-
),
36+
],
37+
],
3438

3539
Only allow specific values for an attribute.
3640

37-
'authproc' => array(
38-
50 => array(
41+
'authproc' => [
42+
50 => [
3943
'class' => 'core:AttributeLimit',
4044
'eduPersonEntitlement' => array('urn:x-surfnet:surf.nl:surfdrive:quota:100')
41-
),
42-
),
45+
],
46+
],
4347

4448
Only allow specific values for an attribute ignoring case.
4549

46-
'authproc' => array(
47-
50 => array(
50+
'authproc' => [
51+
50 => [
4852
'class' => 'core:AttributeLimit',
49-
'eduPersonEntitlement' => array(
53+
'eduPersonEntitlement' => [
5054
'ignoreCase' => true,
5155
'URN:x-surfnet:surf.nl:SURFDRIVE:quota:100'
52-
)
53-
),
54-
),
56+
],
57+
],
58+
],
5559

5660
Only allow specific values for an attribute that match a regex pattern
5761

58-
'authproc' => array(
59-
50 => array(
62+
'authproc' => [
63+
50 => [
6064
'class' => 'core:AttributeLimit',
61-
'eduPersonEntitlement' => array(
65+
'eduPersonEntitlement' => [
6266
'regex' => true,
6367
'/^urn:x-surfnet:surf/',
6468
'/^urn:x-IGNORE_Case/i',
65-
)
66-
),
67-
),
69+
],
70+
],
71+
],
6872

6973

7074
Don't allow any attributes by default, but allow the metadata to override it.
7175

72-
'authproc' => array(
73-
50 => array(
76+
'authproc' => [
77+
50 => [
7478
'class' => 'core:AttributeLimit',
75-
'default' => TRUE,
76-
),
77-
),
79+
'default' => true,
80+
],
81+
],
7882

7983
In order to just use the list of attributes defined in the metadata for each service provider, configure the module
8084
like this:
8185

82-
'authproc' => array(
86+
'authproc' => [
8387
50 => 'core:AttributeLimit',
84-
),
88+
],
8589

8690
Then, add the allowed attributes to each service provider metadata, in the `attributes` option:
8791

88-
$metadata['https://saml2sp.example.org'] = array(
92+
$metadata['https://saml2sp.example.org'] = [
8993
'AssertionConsumerService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp',
9094
'SingleLogoutService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp',
9195
...
92-
'attributes' => array('cn', 'mail'),
96+
'attributes' => ['cn', 'mail'],
9397
...
94-
);
98+
];
9599

96100
Now, let's look to a couple of examples on how to filter out attribute values. First, allow only the entitlements known
97101
to be used by a service provider (among other attributes):
98102

99-
$metadata['https://saml2sp.example.org'] = array(
103+
$metadata['https://saml2sp.example.org'] = [
100104
'AssertionConsumerService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp',
101105
'SingleLogoutService' => 'https://saml2sp.example.org/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp',
102106
...
103-
'attributes' => array(
107+
'attributes' => [
104108
'uid',
105109
'mail',
106-
'eduPersonEntitlement' => array(
110+
'eduPersonEntitlement' => [
107111
'urn:mace:example.org:admin',
108112
'urn:mace:example.org:user',
109-
),
110-
),
113+
],
114+
],
111115
...
112-
);
116+
];
113117

114118
Now, an example on how to normalize the affiliations sent from an identity provider, to make sure that no custom
115119
values ever reach the service providers. Bear in mind that this configuration can be overridden by metadata:
116120

117-
'authproc' => array(
121+
'authproc' => [
118122
50 => 'core:AttributeLimit',
119-
'default' => TRUE,
120-
'eduPersonAffiliation' => array(
123+
'default' => true,
124+
'eduPersonAffiliation' => [
121125
'student',
122126
'staff',
123127
'member',
124128
'faculty',
125129
'employee',
126130
'affiliate',
127-
),
128-
),
131+
],
132+
],
133+
134+
Send attributes to an SP that are not specified in the SPs metadata.
135+
136+
'authproc' => [
137+
50 => 'core:AttributeLimit',
138+
'allowedAttributes' => [],
139+
'bilateralSPs' => [
140+
'entityid1' => [
141+
'mail',
142+
'local-mail'
143+
],
144+
],
145+
'bilateralAttributes' => [
146+
'localFooId' => [
147+
'entityid1',
148+
'entityid2'
149+
],
150+
],
151+
],

modules/core/lib/Auth/Process/AttributeLimit.php

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@
99
use SimpleSAML\Error;
1010
use SimpleSAML\Logger;
1111

12+
use function array_intersect;
13+
use function array_key_exists;
14+
use function array_uintersect;
15+
use function boolval;
16+
use function in_array;
17+
use function is_array;
18+
use function is_int;
19+
use function is_string;
20+
use function preg_match;
21+
use function strcasecmp;
22+
use function unset;
23+
use function var_export;
24+
1225
/**
1326
* A filter for limiting which attributes are passed on.
1427
*
@@ -29,6 +42,16 @@ class AttributeLimit extends Auth\ProcessingFilter
2942
*/
3043
private bool $isDefault = false;
3144

45+
/**
46+
* Array of sp attributes arrays which this filter will allow through.
47+
*/
48+
private $bilateralSPs = [];
49+
50+
/**
51+
* Array of attribute sps arrays which this filter will allow through.
52+
*/
53+
private $bilateralAttributes = [];
54+
3255

3356
/**
3457
* Initialize this filter.
@@ -43,17 +66,51 @@ public function __construct(array &$config, $reserved)
4366

4467
foreach ($config as $index => $value) {
4568
if ($index === 'default') {
46-
$this->isDefault = (bool) $value;
69+
$this->isDefault = boolval($value);
4770
} elseif (is_int($index)) {
4871
if (!is_string($value)) {
49-
throw new Error\Exception('AttributeLimit: Invalid attribute name: ' .
50-
var_export($value, true));
72+
throw new Error\Exception(
73+
'AttributeLimit: Invalid attribute name: ' . var_export($value, true)
74+
);
5175
}
5276
$this->allowedAttributes[] = $value;
77+
} elseif ($index === 'bilateralSPs') {
78+
if (!is_array($value)) {
79+
throw new Error\Exception(
80+
'AttributeLimit: Invalid option bilateralSPs: must be specified in an array: '
81+
. var_export($index, true)
82+
);
83+
}
84+
foreach ($value as $valuearray) {
85+
if (!is_array($valuearray)) {
86+
throw new Error\Exception(
87+
'AttributeLimit: An invalid value in option bilateralSPs: must be specified in an array: '
88+
. var_export($value, true)
89+
);
90+
}
91+
}
92+
$this->bilateralSPs = $value;
93+
} elseif ($index === 'bilateralAttributes') {
94+
if (!is_array($value)) {
95+
throw new Error\Exception(
96+
'AttributeLimit: Invalid option bilateralAttributes: must be specified in an array: '
97+
. var_export($index, true)
98+
);
99+
}
100+
foreach ($value as $valuearray) {
101+
if (!is_array($valuearray)) {
102+
throw new Error\Exception(
103+
'AttributeLimit: An invalid value in option bilateralAttributes: must be specified in an array: '
104+
. var_export($value, true)
105+
);
106+
}
107+
}
108+
$this->bilateralAttributes = $value;
53109
} else { // Can only be string since PHP only allows string|int for array keys
54110
if (!is_array($value)) {
55-
throw new Error\Exception('AttributeLimit: Values for ' .
56-
var_export($index, true) . ' must be specified in an array.');
111+
throw new Error\Exception(
112+
'AttributeLimit: Values for ' . var_export($index, true) . ' must be specified in an array.'
113+
);
57114
}
58115
$this->allowedAttributes[$index] = $value;
59116
}
@@ -91,7 +148,7 @@ private static function getSPIdPAllowed(array &$request): ?array
91148
*/
92149
public function process(array &$request): void
93150
{
94-
assert::keyExists($request, 'Attributes');
151+
Assert::keyExists($request, 'Attributes');
95152

96153
if ($this->isDefault) {
97154
$allowedAttributes = self::getSPIdPAllowed($request);
@@ -110,6 +167,10 @@ public function process(array &$request): void
110167

111168
$attributes = &$request['Attributes'];
112169

170+
if (!empty($this->bilateralSPs) || !empty($this->bilateralAttributes)) {
171+
$entityid = $request['Destination']['entityid'];
172+
}
173+
113174
foreach ($attributes as $name => $values) {
114175
if (!in_array($name, $allowedAttributes, true)) {
115176
// the attribute name is not in the array of allowed attributes
@@ -124,6 +185,22 @@ public function process(array &$request): void
124185
continue;
125186
}
126187
}
188+
if (!empty($this->bilateralSPs)) {
189+
if (
190+
array_key_exists($entityid, $this->bilateralSPs)
191+
&& in_array($name, $this->bilateralSPs[$entityid])
192+
) {
193+
continue;
194+
}
195+
}
196+
if (!empty($this->bilateralAttributes)) {
197+
if (
198+
array_key_exists($name, $this->bilateralAttributes)
199+
&& in_array($entityid, $this->bilateralAttributes[$name])
200+
) {
201+
continue;
202+
}
203+
}
127204
unset($attributes[$name]);
128205
}
129206
}

0 commit comments

Comments
 (0)