-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathAttributeExtractor.php
More file actions
166 lines (140 loc) · 4.81 KB
/
AttributeExtractor.php
File metadata and controls
166 lines (140 loc) · 4.81 KB
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
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\casserver\Cas;
use SimpleSAML\Auth\ProcessingChain;
use SimpleSAML\Auth\State;
use SimpleSAML\Configuration;
use SimpleSAML\Error\NoState;
use SimpleSAML\Module;
use SimpleSAML\Module\casserver\Cas\Factories\ProcessingChainFactory;
/**
* Extract the user and any mapped attributes from the AuthSource attributes
*/
class AttributeExtractor
{
/** @var \SimpleSAML\Auth\State */
private State $authState;
/**
* ID of the Authentication Source used during authn.
*/
private ?string $authSourceId = null;
public function __construct(
protected Configuration $casconfig,
protected ProcessingChainFactory $processingChainFactory,
) {
$this->authState = new State();
}
/**
* Determine the user and any CAS attributes based on the attributes from the
* authsource and the CAS configuration.
*
* The result is an array
* [
* 'user' => 'user_value',
* 'attributes' => [
* // any attributes
* ]
*
* If no CAS attributes are configured, then the attributes' array is empty
*
* @param array|null $state
*
* @return array
* @throws \Exception
*/
public function extractUserAndAttributes(?array $state): array
{
if (
!isset($state[ProcessingChain::AUTHPARAM])
&& $this->casconfig->hasValue('authproc')
) {
$this->runAuthProcs($state);
}
// Get the attributes from the state
$attributes = $state['Attributes'];
$casUsernameAttribute = $this->casconfig->getOptionalValue('attrname', 'eduPersonPrincipalName');
$userName = $attributes[$casUsernameAttribute][0];
if (empty($userName)) {
throw new \Exception("No cas user defined for attribute $casUsernameAttribute");
}
$casAttributes = [];
if ($this->casconfig->getOptionalValue('attributes', true)) {
$attributesToTransfer = $this->casconfig->getOptionalValue('attributes_to_transfer', []);
if (sizeof($attributesToTransfer) > 0) {
foreach ($attributesToTransfer as $key) {
if (\array_key_exists($key, $attributes)) {
$casAttributes[$key] = $attributes[$key];
}
}
} else {
$casAttributes = $attributes;
}
}
return [
'user' => $userName,
'attributes' => $casAttributes,
];
}
/**
* Run authproc filters with the processing chain
* Creating the ProcessingChain require metadata.
* - For the idp metadata use the OIDC issuer as the entityId (and the authprocs from the main config file)
* - For the sp metadata use the client id as the entityId (and don’t set authprocs).
*
* @param array $state
*
* @return void
* @throws \SimpleSAML\Error\UnserializableException
* @throws \Exception
*/
protected function runAuthProcs(array &$state): void
{
$filters = $this->casconfig->getOptionalArray('authproc', []);
$idpMetadata = [
'entityid' => $state['Source']['entityid'] ?? '',
// ProcessChain needs to know the list of authproc filters we defined in module_oidc configuration
'authproc' => $filters,
];
$spMetadata = [
'entityid' => $state['Destination']['entityid'] ?? '',
];
// Get the ReturnTo from the state or fallback to the login page
$state['ReturnURL'] = $state['ReturnTo'] ?? Module::getModuleURL('casserver/login.php');
$state['Destination'] = $spMetadata;
$state['Source'] = $idpMetadata;
$this->processingChainFactory->build($state)->processState($state);
}
/**
* This is a wrapper around Auth/State::loadState that facilitates testing by
* hiding the static method
*
* @param string $stateId
*
* @return array|null
* @throws \SimpleSAML\Error\NoState
*/
public function manageState(string $stateId): ?array
{
if (empty($stateId)) {
throw new NoState();
}
$state = $this->loadState($stateId, ProcessingChain::COMPLETED_STAGE);
if (!empty($state['authSourceId'])) {
$this->authSourceId = (string)$state['authSourceId'];
unset($state['authSourceId']);
}
return $state;
}
/**
* @param string $id
* @param string $stage
* @param bool $allowMissing
*
* @return array|null
* @throws \SimpleSAML\Error\NoState
*/
protected function loadState(string $id, string $stage, bool $allowMissing = false): ?array
{
return $this->authState::loadState($id, $stage, $allowMissing);
}
}