Skip to content

Commit 8f082de

Browse files
committed
Rewrite to use saml2v5
1 parent b36d006 commit 8f082de

File tree

2 files changed

+136
-67
lines changed

2 files changed

+136
-67
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@
3636
"require": {
3737
"php": "^8.1",
3838

39-
"simplesamlphp/saml2-legacy": "~4.18.1",
39+
"simplesamlphp/saml2": "~5.0.2",
4040
"simplesamlphp/simplesamlphp": "~2.4.0",
4141
"symfony/http-foundation": "~6.4.0"
4242
},
4343
"require-dev": {
44-
"simplesamlphp/simplesamlphp-test-framework": "~1.9.2",
44+
"simplesamlphp/simplesamlphp-test-framework": "~1.9.3",
4545
"simplesamlphp/xml-security": "~1.13.0"
4646
},
4747
"support": {

src/Controller/AttributeServer.php

Lines changed: 134 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,31 @@
44

55
namespace SimpleSAML\Module\exampleattributeserver\Controller;
66

7-
use SAML2\Assertion;
8-
use SAML2\AttributeQuery;
9-
use SAML2\Binding;
10-
use SAML2\Constants;
11-
use SAML2\HTTPPost;
12-
use SAML2\Response;
13-
use SAML2\XML\saml\Issuer;
14-
use SAML2\XML\saml\SubjectConfirmation;
15-
use SAML2\XML\saml\SubjectConfirmationData;
16-
use SimpleSAML\Configuration;
17-
use SimpleSAML\Error;
7+
use DateInterval;
8+
use SimpleSAML\{Configuration, Error, Logger};
189
use SimpleSAML\HTTP\RunnableResponse;
19-
use SimpleSAML\Logger;
2010
use SimpleSAML\Metadata\MetaDataStorageHandler;
21-
use SimpleSAML\Module\saml\Message;
11+
use SimpleSAML\SAML2\Binding;
12+
use SimpleSAML\SAML2\Binding\HTTPPost;
13+
use SimpleSAML\SAML2\Constants as C;
14+
use SimpleSAML\SAML2\Utils;
15+
use SimpleSAML\SAML2\XML\saml\{
16+
Assertion,
17+
Attribute,
18+
AttributeStatement,
19+
AttributeValue,
20+
Audience,
21+
AudienceRestriction,
22+
Conditions,
23+
Issuer,
24+
Status,
25+
StatusCode,
26+
Subject,
27+
SubjectConfirmation,
28+
SubjectConfirmationData,
29+
};
30+
use SimpleSAML\SAML2\XML\samlp\{AttributeQuery, Response};
31+
use SimpleSAML\XML\Utils\Random;
2232
use Symfony\Component\HttpFoundation\Request;
2333

2434
/**
@@ -93,73 +103,132 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab
93103

94104
// The attributes we will return
95105
$attributes = [
96-
'name' => ['value1', 'value2', 'value3'],
97-
'test' => ['test'],
106+
new Attribute(
107+
'name',
108+
C::NAMEFORMAT_UNSPECIFIED,
109+
null,
110+
[
111+
new AttributeValue('value1'),
112+
new AttributeValue('value2'),
113+
new AttributeValue('value3'),
114+
]
115+
),
116+
new Attribute(
117+
'test',
118+
C::NAMEFORMAT_UNSPECIFIED,
119+
null,
120+
[
121+
new AttributeValue('test'),
122+
],
123+
),
98124
];
99125

100-
// The name format of the attributes
101-
$attributeNameFormat = Constants::NAMEFORMAT_UNSPECIFIED;
102-
103126
// Determine which attributes we will return
104-
$returnAttributes = array_keys($query->getAttributes());
127+
$returnAttributes = [];
128+
105129
if (count($returnAttributes) === 0) {
106130
Logger::debug('No attributes requested - return all attributes.');
107131
$returnAttributes = $attributes;
108-
} elseif ($query->getAttributeNameFormat() !== $attributeNameFormat) {
109-
Logger::debug('Requested attributes with wrong NameFormat - no attributes returned.');
110-
$returnAttributes = [];
111132
} else {
112-
/** @var array<mixed>$values */
113-
foreach ($returnAttributes as $name => $values) {
114-
if (!array_key_exists($name, $attributes)) {
115-
// We don't have this attribute
116-
unset($returnAttributes[$name]);
117-
continue;
118-
}
119-
if (count($values) === 0) {
120-
// Return all attributes
121-
$returnAttributes[$name] = $attributes[$name];
122-
continue;
133+
foreach ($query->getAttributes() as $reqAttr) {
134+
foreach ($attributes as $attr) {
135+
if ($attr->getName() === $reqAttr->getName() && $attr->getNameFormat() === $reqAttr->getNameFormat()) {
136+
// The requested attribute is available
137+
if ($reqAttr->getAttributeValues() === []) {
138+
// If no specific values are requested, return all
139+
$returnAttributes[] = $attr;
140+
} else {
141+
$returnValues = $this->filterAttributeValues($reqAttr->getAttributeValues(), $attr->getAttributeValues());
142+
$returnAttributes[] = new Attribute(
143+
$attr->getName(),
144+
$attr->getNameFormat(),
145+
$returnValues,
146+
$attr->getAttributesNS(),
147+
);
148+
}
149+
}
123150
}
124-
125-
// Filter which attribute values we should return
126-
$returnAttributes[$name] = array_intersect($values, $attributes[$name]);
127151
}
128152
}
129153

130154
// $returnAttributes contains the attributes we should return. Send them
131-
$issuer = new Issuer();
132-
$issuer->setValue($idpEntityId);
133-
134-
$assertion = new Assertion();
135-
$assertion->setIssuer($issuer);
136-
$assertion->setNameId($query->getNameId());
137-
$assertion->setNotBefore(time());
138-
$assertion->setNotOnOrAfter(time() + 300); // 60*5 = 5min
139-
$assertion->setValidAudiences([$spEntityId]);
140-
$assertion->setAttributes($returnAttributes);
141-
$assertion->setAttributeNameFormat($attributeNameFormat);
142-
143-
$sc = new SubjectConfirmation();
144-
$sc->setMethod(Constants::CM_BEARER);
145-
146-
$scd = new SubjectConfirmationData();
147-
$scd->setNotOnOrAfter(time() + 300); // 60*5 = 5min
148-
$scd->setRecipient($endpoint);
149-
$scd->setInResponseTo($query->getId());
150-
$sc->setSubjectConfirmationData($scd);
151-
$assertion->setSubjectConfirmation([$sc]);
152-
155+
$clock = Utils::getContainer()->getClock();
156+
157+
$assertion = new Assertion(
158+
issuer: new Issuer($idpEntityId),
159+
issueInstant: $clock->now(),
160+
id: (new Random())->generateID(),
161+
subject: Subject(
162+
identifier: $query->getNameID(),
163+
subjectConfirmation: [
164+
new SubjectConfirmation(
165+
method: C::CM_BEARER,
166+
subjectConfirmationData: new SubjectConfirmationData(
167+
notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
168+
recipient: $endpoint,
169+
inResponseTo: $query->getId(),
170+
),
171+
),
172+
],
173+
),
174+
conditions: new Conditions(
175+
notBefore: $clock->now(),
176+
notOnOrAfter: $clock->now()->add(new DateInterval('PT300S')),
177+
audienceRestriction: [
178+
new AudienceRestriction([
179+
new Audience($spEntityId),
180+
]),
181+
],
182+
),
183+
statements: [
184+
new AttributeStatement($returnAttributes),
185+
],
186+
);
187+
188+
// TODO: Fix signing; should use xml-security lib
153189
Message::addSign($idpMetadata, $spMetadata, $assertion);
154190

155-
$response = new Response();
156-
$response->setRelayState($query->getRelayState());
157-
$response->setDestination($endpoint);
158-
$response->setIssuer($issuer);
159-
$response->setInResponseTo($query->getId());
160-
$response->setAssertions([$assertion]);
191+
$response = new Response(
192+
status: new Status(
193+
new StatusCode(C::STATUS_SUCCESS),
194+
),
195+
issueInstant: $clock->now(),
196+
issuer: new Issuer($issuer),
197+
id: (new Random())->generateID(),
198+
version: '2.0',
199+
inResponseTo: $query->getId(),
200+
destination: $endpoint,
201+
assertions: [$assertion],
202+
);
203+
204+
// TODO: Fix signing; should use xml-security lib
161205
Message::addSign($idpMetadata, $spMetadata, $response);
162206

163-
return new RunnableResponse([new HTTPPost(), 'send'], [$response]);
207+
$httpPost = new HTTPPost();
208+
$httpPost->setRelayState($binding->getRelayState());
209+
210+
return new RunnableResponse([$httpPost, 'send'], [$response]);
211+
}
212+
213+
214+
/**
215+
* @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $reqValues
216+
* @param array<\SimpleSAML\SAML2\XML\saml\AttributeValue> $values
217+
*
218+
* @return array<\SimpleSAML\SAML2\XML\saml\AttributeValue>
219+
*/
220+
private function filterAttributeValues(array $reqValues, array $values): array
221+
{
222+
$result = [];
223+
224+
foreach ($reqValues as $x) {
225+
foreach ($values as $y) {
226+
if ($x->getValue() === $y->getValue()) {
227+
$result[] = $y;
228+
}
229+
}
230+
}
231+
232+
return $result;
164233
}
165234
}

0 commit comments

Comments
 (0)