1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace SimpleSAML\Module\exampleattributeserver\Controller; |
||||
6 | |||||
7 | use DateInterval; |
||||
8 | use Nyholm\Psr7\Factory\Psr17Factory; |
||||
9 | use SimpleSAML\{Configuration, Error, Logger}; |
||||
10 | use SimpleSAML\HTTP\RunnableResponse; |
||||
11 | use SimpleSAML\Metadata\MetaDataStorageHandler; |
||||
12 | use SimpleSAML\SAML2\Binding; |
||||
13 | use SimpleSAML\SAML2\Binding\{SynchronousBindingInterface, SOAP}; |
||||
14 | use SimpleSAML\SAML2\Constants as C; |
||||
15 | use SimpleSAML\SAML2\Utils as SAML2_Utils; |
||||
16 | use SimpleSAML\SAML2\XML\saml\{ |
||||
17 | Assertion, |
||||
18 | Attribute, |
||||
19 | AttributeStatement, |
||||
20 | AttributeValue, |
||||
21 | Audience, |
||||
22 | AudienceRestriction, |
||||
23 | Conditions, |
||||
24 | Issuer, |
||||
25 | Subject, |
||||
26 | SubjectConfirmation, |
||||
27 | SubjectConfirmationData, |
||||
28 | }; |
||||
29 | use SimpleSAML\SAML2\XML\samlp\{AttributeQuery, Response, Status, StatusCode}; |
||||
30 | use SimpleSAML\Utils; |
||||
31 | use SimpleSAML\XML\Utils\Random; |
||||
32 | use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; |
||||
33 | use SimpleSAML\XMLSecurity\Key\PrivateKey; |
||||
34 | use SimpleSAML\XMLSecurity\XML\ds\{KeyInfo, X509Certificate, X509Data}; |
||||
35 | use SimpleSAML\XMLSecurity\XML\SignableElementInterface; |
||||
36 | use Symfony\Bridge\PsrHttpMessage\Factory\{HttpFoundationFactory, PsrHttpFactory}; |
||||
37 | use Symfony\Component\HttpFoundation\Request; |
||||
38 | |||||
39 | use function array_filter; |
||||
40 | |||||
41 | /** |
||||
42 | * Controller class for the exampleattributeserver module. |
||||
43 | * |
||||
44 | * This class serves the attribute server available in the module. |
||||
45 | * |
||||
46 | * @package SimpleSAML\Module\exampleattributeserver |
||||
47 | */ |
||||
48 | class AttributeServer |
||||
49 | { |
||||
50 | /** @var \SimpleSAML\Configuration */ |
||||
51 | protected Configuration $config; |
||||
52 | |||||
53 | /** @var \SimpleSAML\Metadata\MetaDataStorageHandler|null */ |
||||
54 | protected ?MetaDataStorageHandler $metadataHandler = null; |
||||
55 | |||||
56 | |||||
57 | /** |
||||
58 | * ConfigController constructor. |
||||
59 | * |
||||
60 | * @param \SimpleSAML\Configuration $config The configuration to use. |
||||
61 | */ |
||||
62 | public function __construct(Configuration $config) |
||||
63 | { |
||||
64 | $this->config = $config; |
||||
65 | } |
||||
66 | |||||
67 | |||||
68 | /** |
||||
69 | * Inject the \SimpleSAML\Metadata\MetaDataStorageHandler dependency. |
||||
70 | * |
||||
71 | * @param \SimpleSAML\Metadata\MetaDataStorageHandler $handler |
||||
72 | */ |
||||
73 | public function setMetadataStorageHandler(MetaDataStorageHandler $handler): void |
||||
74 | { |
||||
75 | $this->metadataHandler = $handler; |
||||
76 | } |
||||
77 | |||||
78 | |||||
79 | /** |
||||
80 | * @param \Symfony\Component\HttpFoundation\Request $request The current request. |
||||
81 | * |
||||
82 | * @return \SimpleSAML\HTTP\RunnableResponse |
||||
83 | * @throws \SimpleSAML\Error\BadRequest |
||||
84 | */ |
||||
85 | public function main(/** @scrutinizer ignore-unused */ Request $request): RunnableResponse |
||||
86 | { |
||||
87 | $psr17Factory = new Psr17Factory(); |
||||
88 | $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); |
||||
89 | $psrRequest = $psrHttpFactory->createRequest($request); |
||||
90 | |||||
91 | $binding = Binding::getCurrentBinding($psrRequest); |
||||
92 | if (!($binding instanceof SynchronousBindingInterface)) { |
||||
93 | throw new Error\BadRequest('Invalid binding; MUST use a synchronous binding.'); |
||||
94 | } |
||||
95 | |||||
96 | $message = $binding->receive($psrRequest); |
||||
97 | if (!($message instanceof AttributeQuery)) { |
||||
98 | throw new Error\BadRequest('Invalid message received to AttributeQuery endpoint.'); |
||||
99 | } |
||||
100 | |||||
101 | $idpEntityId = $this->metadataHandler->getMetaDataCurrentEntityID('saml20-idp-hosted'); |
||||
0 ignored issues
–
show
|
|||||
102 | |||||
103 | $issuer = $message->getIssuer(); |
||||
104 | if ($issuer === null) { |
||||
105 | throw new Error\BadRequest('Missing <saml:Issuer> in <samlp:AttributeQuery>.'); |
||||
106 | } else { |
||||
107 | $spEntityId = $issuer->getContent(); |
||||
108 | if ($spEntityId === '') { |
||||
109 | throw new Error\BadRequest('Empty <saml:Issuer> in <samlp:AttributeQuery>.'); |
||||
110 | } |
||||
111 | } |
||||
112 | |||||
113 | $idpMetadata = $this->metadataHandler->getMetaDataConfig($idpEntityId, 'saml20-idp-hosted'); |
||||
114 | $spMetadata = $this->metadataHandler->getMetaDataConfig($spEntityId, 'saml20-sp-remote'); |
||||
115 | |||||
116 | // The endpoint we should deliver the message to |
||||
117 | $endpoint = $spMetadata->getString('testAttributeEndpoint'); |
||||
118 | |||||
119 | // The attributes we will return |
||||
120 | $attributes = [ |
||||
121 | new Attribute( |
||||
122 | 'name', |
||||
123 | C::NAMEFORMAT_UNSPECIFIED, |
||||
124 | null, |
||||
125 | [ |
||||
126 | new AttributeValue('value1'), |
||||
127 | new AttributeValue('value2'), |
||||
128 | new AttributeValue('value3'), |
||||
129 | ], |
||||
130 | ), |
||||
131 | new Attribute( |
||||
132 | 'test', |
||||
133 | C::NAMEFORMAT_UNSPECIFIED, |
||||
134 | null, |
||||
135 | [ |
||||
136 | new AttributeValue('test'), |
||||
137 | ], |
||||
138 | ), |
||||
139 | ]; |
||||
140 | |||||
141 | // Determine which attributes we will return |
||||
142 | // @phpstan-ignore identical.alwaysFalse |
||||
143 | if (count($attributes) === 0) { |
||||
144 | Logger::debug('No attributes requested - return all attributes.'); |
||||
145 | $attributeStatement = null; |
||||
146 | } else { |
||||
147 | $returnAttributes = []; |
||||
148 | foreach ($message->getAttributes() as $reqAttr) { |
||||
149 | foreach ($attributes as $attr) { |
||||
150 | if ( |
||||
151 | $attr->getName() === $reqAttr->getName() |
||||
152 | && $attr->getNameFormat() === $reqAttr->getNameFormat() |
||||
153 | ) { |
||||
154 | // The requested attribute is available |
||||
155 | if ($reqAttr->getAttributeValues() === []) { |
||||
156 | // If no specific values are requested, return all |
||||
157 | $returnAttributes[] = $attr; |
||||
158 | } else { |
||||
159 | $returnValues = $this->filterAttributeValues( |
||||
160 | $reqAttr->getAttributeValues(), |
||||
161 | $attr->getAttributeValues(), |
||||
162 | ); |
||||
163 | |||||
164 | $returnAttributes[] = new Attribute( |
||||
165 | $attr->getName(), |
||||
166 | $attr->getNameFormat(), |
||||
167 | null, |
||||
168 | $returnValues, |
||||
169 | $attr->getAttributesNS(), |
||||
170 | ); |
||||
171 | } |
||||
172 | } |
||||
173 | } |
||||
174 | } |
||||
175 | |||||
176 | $attributeStatement = $returnAttributes ? (new AttributeStatement($returnAttributes)) : null; |
||||
177 | } |
||||
178 | |||||
179 | // $returnAttributes contains the attributes we should return. Send them |
||||
180 | $clock = SAML2_Utils::getContainer()->getClock(); |
||||
181 | |||||
182 | $statements = array_filter([$attributeStatement]); |
||||
183 | $assertion = new Assertion( |
||||
184 | issuer: new Issuer($idpEntityId), |
||||
185 | issueInstant: $clock->now(), |
||||
186 | id: (new Random())->generateID(), |
||||
187 | subject: new Subject( |
||||
188 | identifier: $message->getSubject()->getIdentifier(), |
||||
189 | subjectConfirmation: [ |
||||
190 | new SubjectConfirmation( |
||||
191 | method: C::CM_BEARER, |
||||
192 | subjectConfirmationData: new SubjectConfirmationData( |
||||
193 | notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')), |
||||
194 | recipient: $endpoint, |
||||
195 | inResponseTo: $message->getId(), |
||||
196 | ), |
||||
197 | ), |
||||
198 | ], |
||||
199 | ), |
||||
200 | conditions: new Conditions( |
||||
201 | notBefore: $clock->now(), |
||||
202 | notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')), |
||||
203 | audienceRestriction: [ |
||||
204 | new AudienceRestriction([ |
||||
205 | new Audience($spEntityId), |
||||
206 | ]), |
||||
207 | ], |
||||
208 | ), |
||||
209 | statements: $statements, |
||||
210 | ); |
||||
211 | |||||
212 | self::addSign($idpMetadata, $spMetadata, $assertion); |
||||
0 ignored issues
–
show
The function
SimpleSAML\Module\exampl...ributeServer::addSign() has been deprecated: This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and should be replaced with a call to a future ServiceProvider-class in the saml2-library
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
213 | |||||
214 | $response = new Response( |
||||
215 | status: new Status( |
||||
216 | new StatusCode(C::STATUS_SUCCESS), |
||||
217 | ), |
||||
218 | issueInstant: $clock->now(), |
||||
219 | issuer: $issuer, |
||||
220 | id: (new Random())->generateID(), |
||||
221 | version: '2.0', |
||||
222 | inResponseTo: $message->getId(), |
||||
223 | destination: $endpoint, |
||||
224 | assertions: [$assertion], |
||||
225 | ); |
||||
226 | |||||
227 | self::addSign($idpMetadata, $spMetadata, $response); |
||||
0 ignored issues
–
show
The function
SimpleSAML\Module\exampl...ributeServer::addSign() has been deprecated: This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and should be replaced with a call to a future ServiceProvider-class in the saml2-library
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
228 | |||||
229 | $soap = new SOAP(); |
||||
230 | return new RunnableResponse([$soap, 'send'], [$response]); |
||||
231 | } |
||||
232 | |||||
233 | |||||
234 | /** |
||||
235 | * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $reqValues |
||||
236 | * @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $values |
||||
237 | * |
||||
238 | * @return array<\SimpleSAML\SAML2\XML\saml\AttributeValue> |
||||
239 | */ |
||||
240 | private function filterAttributeValues(array $reqValues, array $values): array |
||||
241 | { |
||||
242 | $result = []; |
||||
243 | |||||
244 | foreach ($reqValues as $x) { |
||||
245 | foreach ($values as $y) { |
||||
246 | if ($x->getValue() === $y->getValue()) { |
||||
247 | $result[] = $y; |
||||
248 | } |
||||
249 | } |
||||
250 | } |
||||
251 | |||||
252 | return $result; |
||||
253 | } |
||||
254 | |||||
255 | |||||
256 | /** |
||||
257 | * @deprecated This method is a modified version of \SimpleSAML\Module\saml\Message::addSign and |
||||
258 | * should be replaced with a call to a future ServiceProvider-class in the saml2-library |
||||
259 | * |
||||
260 | * Add signature key and sender certificate to an element (Message or Assertion). |
||||
261 | * |
||||
262 | * @param \SimpleSAML\Configuration $srcMetadata The metadata of the sender. |
||||
263 | * @param \SimpleSAML\Configuration $dstMetadata The metadata of the recipient. |
||||
264 | * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $element The element we should add the data to. |
||||
265 | */ |
||||
266 | private static function addSign( |
||||
267 | Configuration $srcMetadata, |
||||
268 | Configuration $dstMetadata, |
||||
269 | SignableElementInterface &$element, |
||||
270 | ): void { |
||||
271 | $dstPrivateKey = $dstMetadata->getOptionalString('signature.privatekey', null); |
||||
272 | $cryptoUtils = new Utils\Crypto(); |
||||
273 | |||||
274 | if ($dstPrivateKey !== null) { |
||||
275 | /** @var string[] $keyArray */ |
||||
276 | $keyArray = $cryptoUtils->loadPrivateKey($dstMetadata, true, 'signature.'); |
||||
277 | $certArray = $cryptoUtils->loadPublicKey($dstMetadata, false, 'signature.'); |
||||
278 | } else { |
||||
279 | /** @var string[] $keyArray */ |
||||
280 | $keyArray = $cryptoUtils->loadPrivateKey($srcMetadata, true); |
||||
281 | $certArray = $cryptoUtils->loadPublicKey($srcMetadata, false); |
||||
282 | } |
||||
283 | |||||
284 | $algo = $dstMetadata->getOptionalString('signature.algorithm', null); |
||||
285 | if ($algo === null) { |
||||
286 | $algo = $srcMetadata->getOptionalString('signature.algorithm', C::SIG_RSA_SHA256); |
||||
287 | } |
||||
288 | |||||
289 | $privateKey = PrivateKey::fromFile($keyArray['PEM'], $keyArray['password']); |
||||
290 | |||||
291 | $keyInfo = null; |
||||
292 | if ($certArray !== null) { |
||||
293 | $keyInfo = new KeyInfo([ |
||||
294 | new X509Data( |
||||
295 | [ |
||||
296 | new X509Certificate($certArray['PEM']), |
||||
297 | ], |
||||
298 | ), |
||||
299 | ]); |
||||
300 | } |
||||
301 | |||||
302 | $signer = (new SignatureAlgorithmFactory())->getAlgorithm( |
||||
303 | $algo, |
||||
0 ignored issues
–
show
It seems like
$algo can also be of type null ; however, parameter $algId of SimpleSAML\XMLSecurity\A...Factory::getAlgorithm() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
304 | $privateKey, |
||||
305 | ); |
||||
306 | |||||
307 | $element->sign($signer, C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); |
||||
308 | } |
||||
309 | } |
||||
310 |
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.
This is most likely a typographical error or the method has been renamed.