EntityCategory   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 70
c 1
b 0
f 0
dl 0
loc 165
rs 10
wmc 23

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 48 5
D process() 0 62 18
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\Module\entitycategories\Auth\Process;
6
7
use SimpleSAML\Assert\Assert;
8
use SimpleSAML\Auth;
9
use SimpleSAML\Error;
10
11
use function array_key_exists;
12
use function array_merge;
13
use function in_array;
14
use function is_numeric;
15
16
/**
17
 * An authentication processing filter that modifies the list of attributes sent to a service depending on the entity
18
 * categories it belongs to. This filter DOES NOT alter the list of attributes sent itself, but modifies the list of
19
 * attributes requested by the service provider. Therefore, in order to be of any use, it must be used together with the
20
 * core:AttributeLimit authentication processing filter.
21
 *
22
 * @package SimpleSAMLphp
23
 */
24
class EntityCategory extends Auth\ProcessingFilter
25
{
26
    /**
27
     * A list of categories available. An associative array where the identifier of the category is the key, and the
28
     * associated value is an array with all the attributes allowed for services in that category.
29
     *
30
     * @var array
31
     */
32
    protected array $categories = [];
33
34
    /**
35
     * Whether the attributes allowed by this category should be sent by default in case no attributes are explicitly
36
     * requested or not.
37
     *
38
     * @var bool
39
     */
40
    protected bool $default = false;
41
42
    /**
43
     *
44
     * Whether it is allowed to release attributes to entities having no entity category or
45
     * having unconfigured entity categories
46
     * Strict means not to release attributes to that entities. If strict is false, attributeLimit will do the filtering
47
     *
48
     * @var bool
49
     */
50
    protected bool $strict = false;
51
52
    /**
53
     *
54
     * Whether it is allowed to release additional requested attributes than configured in the list of the
55
     * configuration of the entity category and allow release attributes based on requested attributes to entities
56
     * having unconfigured entity categories.
57
     *
58
     * @var bool
59
     */
60
    protected bool $allowRequestedAttributes = false;
61
62
63
    /**
64
     * EntityCategory constructor.
65
     *
66
     * @param array $config An array with the configuration for this processing filter.
67
     * @param mixed $reserved For future use.
68
     * @throws \SimpleSAML\Error\ConfigurationError In case of a misconfiguration of the filter.
69
     */
70
    public function __construct(array $config, $reserved)
71
    {
72
        parent::__construct($config, $reserved);
73
74
        foreach ($config as $index => $value) {
75
            if ($index === 'default') {
76
                Assert::boolean(
77
                    $value,
78
                    "The 'default' configuration option must have a boolean value.",
79
                    Error\ConfigurationError::class,
80
                );
81
                $this->default = $value;
82
                continue;
83
            }
84
85
            if ($index === 'strict') {
86
                Assert::boolean(
87
                    $value,
88
                    "The 'strict' configuration option must have a boolean value.",
89
                    Error\ConfigurationError::class,
90
                );
91
                $this->strict = $value;
92
                continue;
93
            }
94
95
            if ($index === 'allowRequestedAttributes') {
96
                Assert::boolean(
97
                    $value,
98
                    "The 'allowRequestedAttributes' configuration option must have a boolean value.",
99
                    Error\ConfigurationError::class,
100
                );
101
                $this->allowRequestedAttributes = $value;
102
                continue;
103
            }
104
105
            Assert::string(
106
                $index,
107
                "Identifier of a category must be a string. '$index' is set.",
108
                Error\ConfigurationError::class,
109
            );
110
111
            Assert::isArray(
112
                $value,
113
                "The list of allowed attributes for category '$index' is not an array.",
114
                Error\ConfigurationError::class,
115
            );
116
117
            $this->categories[$index] = $value;
118
        }
119
    }
120
121
122
    /**
123
     * Apply the filter to modify the list of attributes for the current service provider.
124
     *
125
     * @param array &$state The current request.
126
     */
127
    public function process(array &$state): void
128
    {
129
        if (!array_key_exists('EntityAttributes', $state['Destination'])) {
130
            if ($this->strict === true) {
131
                // We do not allow to release any attribute to entity having no entity attribute
132
                $state['Destination']['attributes'] = [];
133
            }
134
            return;
135
        }
136
137
        if (!array_key_exists('http://macedir.org/entity-category', $state['Destination']['EntityAttributes'])) {
138
            if ($this->strict === true) {
139
                // We do not allow to release any attribute to entity having no entity category
140
                $state['Destination']['attributes'] = [];
141
            }
142
            return;
143
        }
144
        $categories = $state['Destination']['EntityAttributes']['http://macedir.org/entity-category'];
145
146
        if (!array_key_exists('attributes', $state['Destination'])) {
147
            if ($this->default === true) {
148
                // handle the case of service providers requesting no attributes and the filter being the default policy
149
                $state['Destination']['attributes'] = [];
150
                foreach ($categories as $category) {
151
                    if (!array_key_exists($category, $this->categories)) {
152
                        continue;
153
                    }
154
155
                    $state['Destination']['attributes'] = array_merge(
156
                        $state['Destination']['attributes'],
157
                        $this->categories[$category],
158
                    );
159
                }
160
            }
161
            return;
162
        }
163
164
        // iterate over the requested attributes and see if any of the categories allows them
165
        foreach ($state['Destination']['attributes'] as $index => $value) {
166
            $attrname = $value;
167
            if (!is_numeric($index)) {
168
                $attrname = $index;
169
            }
170
171
            $found = false;
172
            foreach ($categories as $category) {
173
                if (!array_key_exists($category, $this->categories)) {
174
                    continue;
175
                }
176
177
                if (
178
                    in_array($attrname, $this->categories[$category], true)
179
                    || $this->allowRequestedAttributes === true
180
                ) {
181
                    $found = true;
182
                    break;
183
                }
184
            }
185
186
            if ($found === false && ($this->allowRequestedAttributes === false || $this->strict === true)) {
187
                // no category (if any) allows the attribute, so remove it
188
                unset($state['Destination']['attributes'][$index]);
189
            }
190
        }
191
    }
192
}
193