|
4 | 4 |
|
5 | 5 | namespace SimpleSAML\Module\exampleattributeserver\Controller; |
6 | 6 |
|
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}; |
18 | 9 | use SimpleSAML\HTTP\RunnableResponse; |
19 | | -use SimpleSAML\Logger; |
20 | 10 | 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; |
22 | 32 | use Symfony\Component\HttpFoundation\Request; |
23 | 33 |
|
24 | 34 | /** |
@@ -93,73 +103,132 @@ public function main(/** @scrutinizer ignore-unused */ Request $request): Runnab |
93 | 103 |
|
94 | 104 | // The attributes we will return |
95 | 105 | $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 | + ), |
98 | 124 | ]; |
99 | 125 |
|
100 | | - // The name format of the attributes |
101 | | - $attributeNameFormat = Constants::NAMEFORMAT_UNSPECIFIED; |
102 | | - |
103 | 126 | // Determine which attributes we will return |
104 | | - $returnAttributes = array_keys($query->getAttributes()); |
| 127 | + $returnAttributes = []; |
| 128 | + |
105 | 129 | if (count($returnAttributes) === 0) { |
106 | 130 | Logger::debug('No attributes requested - return all attributes.'); |
107 | 131 | $returnAttributes = $attributes; |
108 | | - } elseif ($query->getAttributeNameFormat() !== $attributeNameFormat) { |
109 | | - Logger::debug('Requested attributes with wrong NameFormat - no attributes returned.'); |
110 | | - $returnAttributes = []; |
111 | 132 | } 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 | + } |
123 | 150 | } |
124 | | - |
125 | | - // Filter which attribute values we should return |
126 | | - $returnAttributes[$name] = array_intersect($values, $attributes[$name]); |
127 | 151 | } |
128 | 152 | } |
129 | 153 |
|
130 | 154 | // $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 |
153 | 189 | Message::addSign($idpMetadata, $spMetadata, $assertion); |
154 | 190 |
|
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 |
161 | 205 | Message::addSign($idpMetadata, $spMetadata, $response); |
162 | 206 |
|
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; |
164 | 233 | } |
165 | 234 | } |
0 commit comments