X Tutup
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/simplesamlphp-reference-sp-remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,58 @@ The following SAML 2.0 options are available:

: The value of this option is specified in one of several [endpoint formats](./simplesamlphp-metadata-endpoints).

`AttributeConsumingService`
: this parameter overrides `attributes` and enables the SAML 2.0 AttributeConsumingServiceIndex support. It holds the mapping between AttributeConsumingServiceIndex and corresponding service definition containing the attribute set.

Each service definition contains the following parameters:

- `name`: (optional) Service name in different languages.
- `description`: (optional) Service description in different languages.
- `attributes`: list of attributes SP requests for the given Service.
- `attributes.required`: (optional) list of *required* attributes SP requests for the given Service.
- `attributes.NameFormat`: (optional) the Format field of attribute
statements for this Service (see below for more information).

Here is an example:

'AttributeConsumingService' =>
array (
0 =>
array (
'name' =>
array (
'en' => 'Example service 0',
),
'description' =>
array (
'nl' => 'Dit is een voorbeeld voor de unittest 0.',
),
'attributes' =>
array (
0 => 'urn:mace:dir:attribute-def:eduPersonPrincipalName',
1 => 'urn:mace:dir:attribute-def:mail',
2 => 'urn:mace:dir:attribute-def:displayName',
),
'attributes.required' =>
array (
0 => 'urn:mace:dir:attribute-def:eduPersonPrincipalName',
),
'attributes.NameFormat' => 'urn:mace:shibboleth:1.0:attributeNamespace:uri',
),
1 =>
array (
'attributes' =>
array (
0 => 'uid',
1 => 'mail',
),
),
)


`AttributeConsumingService.default`
: When using `AttributeConsumingService`, this parameter specifies the default Service Index when SP does not include it in the Request.

`attributes.NameFormat`
: What value will be set in the Format field of attribute
statements. This parameter can be configured multiple places, and
Expand Down
91 changes: 60 additions & 31 deletions lib/SimpleSAML/Metadata/SAMLParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,8 @@ public function getMetadata1xIdP()
* - 'SingleLogoutService': String with the URL where we should send logout requests/responses.
* - 'NameIDFormat': The name ID format this SP expects. This may be unset.
* - 'certData': X509Certificate for entity (if present).
* - 'AttributeConsumingService': set of attributes associated to a service index
* - 'AttributeConsumingService.default' default acs index
*
* Metadata must be loaded with one of the parse functions before this function can be called.
*
Expand Down Expand Up @@ -680,6 +682,14 @@ public function getMetadata20SP()
$ret['NameIDFormat'] = $spd['nameIDFormats'][0];
}

// find the AttributeConsumingService indexes and default index
if (array_key_exists('AttributeConsumingService', $spd)) {
$ret['AttributeConsumingService'] = $spd['AttributeConsumingService'];
}
if (array_key_exists('AttributeConsumingService.default', $spd)) {
$ret['AttributeConsumingService.default'] = $spd['AttributeConsumingService.default'];
}

// add the list of attributes the SP should receive
if (array_key_exists('attributes', $spd)) {
$ret['attributes'] = $spd['attributes'];
Expand Down Expand Up @@ -918,7 +928,7 @@ private function processSPSSODescriptor(\SAML2\XML\md\SPSSODescriptor $element,
// find all the attributes and SP name...
$attcs = $element->AttributeConsumingService;
if (count($attcs) > 0) {
self::parseAttributeConsumerService($attcs[0], $sp);
self::parseAttributeConsumingService($attcs, $sp);
}

// check AuthnRequestsSigned
Expand Down Expand Up @@ -1179,52 +1189,71 @@ private function processContactPerson(\SAML2\XML\md\ContactPerson $element)


/**
* This function parses AttributeConsumerService elements.
* This function parses AttributeConsumingService elements.
*
* @param \SAML2\XML\md\AttributeConsumingService $element The AttributeConsumingService to parse.
* @param array of \SAML2\XML\md\AttributeConsumingService elements
* @param array $sp The array with the SP's metadata.
*/
private static function parseAttributeConsumerService(\SAML2\XML\md\AttributeConsumingService $element, &$sp)
private static function parseAttributeConsumingService(array $acs_elements, &$sp)
{
assert(is_array($sp));

$sp['name'] = $element->ServiceName;
$sp['description'] = $element->ServiceDescription;
$sp['AttributeConsumingService'] = array();
$sp['AttributeConsumingService.default'] = null;

foreach ($acs_elements as $acs) {
$current_acs = array();
$format = null;

$current_acs['name'] = $acs->ServiceName;
$current_acs['description'] = $acs->ServiceDescription;

$current_acs['attributes'] = array();
$current_acs['attributes.required'] = array();

foreach ($acs->RequestedAttribute as $child) {
$attrname = $child->Name;
$current_acs['attributes'][] = $attrname;

if ($child->isRequired !== null && $child->isRequired === true) {
$current_acs['attributes.required'][] = $attrname;
}

if ($child->NameFormat !== null) {
$attrformat = $child->NameFormat;
} else {
$attrformat = \SAML2\Constants::NAMEFORMAT_UNSPECIFIED;
}

$format = null;
$sp['attributes'] = array();
$sp['attributes.required'] = array();
foreach ($element->RequestedAttribute as $child) {
$attrname = $child->Name;
$sp['attributes'][] = $attrname;
if ($format === null) {
$format = $attrformat;
} elseif ($format !== $attrformat) {
$format = \SAML2\Constants::NAMEFORMAT_UNSPECIFIED;
}
}

if ($child->isRequired !== null && $child->isRequired === true) {
$sp['attributes.required'][] = $attrname;
if (empty($current_acs['attributes'])) {
// a really invalid configuration: all AttributeConsumingServices should have one or more attributes
unset($current_acs['attributes']);
}
if (empty($current_acs['attributes.required'])) {
unset($current_acs['attributes.required']);
}

if ($child->NameFormat !== null) {
$attrformat = $child->NameFormat;
} else {
$attrformat = \SAML2\Constants::NAMEFORMAT_UNSPECIFIED;
if ($format !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED && $format !== null) {
$current_acs['attributes.NameFormat'] = $format;
}

if ($format === null) {
$format = $attrformat;
} elseif ($format !== $attrformat) {
$format = \SAML2\Constants::NAMEFORMAT_UNSPECIFIED;
$sp['AttributeConsumingService'][$acs->index] = $current_acs;
if($acs->isDefault !== null && $acs->isDefault === true) {
$sp['AttributeConsumingService.default'] = $acs->index;
}
}

if (empty($sp['attributes'])) {
// a really invalid configuration: all AttributeConsumingServices should have one or more attributes
unset($sp['attributes']);
}
if (empty($sp['attributes.required'])) {
unset($sp['attributes.required']);
}

if ($format !== \SAML2\Constants::NAMEFORMAT_UNSPECIFIED && $format !== null) {
$sp['attributes.NameFormat'] = $format;
if($sp['AttributeConsumingService.default'] === null && count($sp['AttributeConsumingService']) > 0) {
//set default index as first availale index
$sp['AttributeConsumingService.default'] = key(array_keys($sp['AttributeConsumingService']));
}
}

Expand Down
34 changes: 34 additions & 0 deletions modules/core/lib/Auth/Process/AttributeLimit.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,41 @@ public function __construct($config, $reserved) {
* @return array|NULL Array with attribute names, or NULL if no limit is placed.
*/
private static function getSPIdPAllowed(array &$request) {
// check if AttributeConsumingService config is present
if(array_key_exists('AttributeConsumingService', $request['Destination'])) {

// if present, get AttributeConsumingServiceIndex from request
if(array_key_exists('saml:AttributeConsumingServiceIndex',$request) && !is_null($request['saml:AttributeConsumingServiceIndex'])) {
$acsi = $request['saml:AttributeConsumingServiceIndex'];

// if index from request exists in the configuration, return corresponding attributes
if(array_key_exists($acsi,$request['Destination']['AttributeConsumingService'])) {
return $request['Destination']['AttributeConsumingService'][$acsi]['attributes'];
}
else {
throw new SimpleSAML_Error_Exception('AttributeLimit: AttributeConsumingServiceIndex ' . var_export($acsi, TRUE) .
' received in request not found in SP configuration.');
}
}

// index not present in request, get default AttributeConsumingService Index from configuration
if(array_key_exists('AttributeConsumingService.default', $request['Destination'])) {
$acsi = $request['Destination']['AttributeConsumingService.default'];

// return default acsi attributes
if(array_key_exists($acsi,$request['Destination']['AttributeConsumingService'])) {
return $request['Destination']['AttributeConsumingService'][$acsi]['attributes'];
}
else {
throw new SimpleSAML_Error_Exception('AttributeLimit: AttributeConsumingService.default ' . var_export($acsi, TRUE) .
' is invalid.');
}
}
else {
throw new SimpleSAML_Error_Exception('AttributeLimit: AttributeConsumingService.default ' . var_export($acsi, TRUE) .
' must be specified in SP configuration.');
}
}
if (array_key_exists('attributes', $request['Destination'])) {
// SP Config
return $request['Destination']['attributes'];
Expand Down
3 changes: 3 additions & 0 deletions modules/saml/lib/IdP/SAML2.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ public static function receiveAuthnRequest(SimpleSAML_IdP $idp)
$extensions = null;
$allowCreate = true;
$authnContext = null;
$attrcsi = null;
$binding = null;

$idpInit = true;
Expand Down Expand Up @@ -351,6 +352,7 @@ public static function receiveAuthnRequest(SimpleSAML_IdP $idp)
$consumerIndex = $request->getAssertionConsumerServiceIndex();
$extensions = $request->getExtensions();
$authnContext = $request->getRequestedAuthnContext();
$attrcsi = $request->getAttributeConsumingServiceIndex();

$nameIdPolicy = $request->getNameIdPolicy();
if (isset($nameIdPolicy['Format'])) {
Expand Down Expand Up @@ -430,6 +432,7 @@ public static function receiveAuthnRequest(SimpleSAML_IdP $idp)
'saml:Extensions' => $extensions,
'saml:AuthnRequestReceivedAt' => microtime(true),
'saml:RequestedAuthnContext' => $authnContext,
'saml:AttributeConsumingServiceIndex' => $attrcsi,
);

// ECP AuthnRequests need to supply credentials
Expand Down
68 changes: 60 additions & 8 deletions tests/lib/SimpleSAML/Metadata/SAMLParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,21 @@ public function testAttributeConsumingServiceParsing()
</Extensions>
<SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<AttributeConsumingService index="0">
<ServiceName xml:lang="en">Example service</ServiceName>
<ServiceDescription xml:lang="nl">Dit is een voorbeeld voor de unittest.</ServiceDescription>
<ServiceName xml:lang="en">Example service 0</ServiceName>
<ServiceDescription xml:lang="nl">Dit is een voorbeeld voor de unittest 0.</ServiceDescription>

<RequestedAttribute FriendlyName="eduPersonPrincipalName" Name="urn:mace:dir:attribute-def:eduPersonPrincipalName" NameFormat="urn:mace:shibboleth:1.0:attributeNamespace:uri" isRequired="true"/>
<RequestedAttribute FriendlyName="mail" Name="urn:mace:dir:attribute-def:mail" NameFormat="urn:mace:shibboleth:1.0:attributeNamespace:uri"/>
<RequestedAttribute FriendlyName="displayName" Name="urn:mace:dir:attribute-def:displayName" NameFormat="urn:mace:shibboleth:1.0:attributeNamespace:uri"/>
</AttributeConsumingService>
<AttributeConsumingService index="1" isDefault="true">
<ServiceName xml:lang="en">Example service 1</ServiceName>
<ServiceDescription xml:lang="nl">Dit is een voorbeeld voor de unittest 1.</ServiceDescription>

<RequestedAttribute FriendlyName="testFN1" Name="testN1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
<RequestedAttribute FriendlyName="testFN2" Name="testN2" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" isRequired="true"/>
<RequestedAttribute FriendlyName="testFN3" Name="testN3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"/>
</AttributeConsumingService>
</SPSSODescriptor>

</EntityDescriptor>
Expand All @@ -121,14 +129,58 @@ public function testAttributeConsumingServiceParsing()

$metadata = $entities['theEntityID']->getMetadata20SP();

$this->assertEquals("Example service", $metadata['name']['en']);
$this->assertEquals("Dit is een voorbeeld voor de unittest.", $metadata['description']['nl']);
$expected_AttributeConsumingService = array (
0 =>
array (
'name' =>
array (
'en' => 'Example service 0',
),
'description' =>
array (
'nl' => 'Dit is een voorbeeld voor de unittest 0.',
),
'attributes' =>
array (
0 => 'urn:mace:dir:attribute-def:eduPersonPrincipalName',
1 => 'urn:mace:dir:attribute-def:mail',
2 => 'urn:mace:dir:attribute-def:displayName',
),
'attributes.required' =>
array (
0 => 'urn:mace:dir:attribute-def:eduPersonPrincipalName',
),
'attributes.NameFormat' => 'urn:mace:shibboleth:1.0:attributeNamespace:uri',
),
1 =>
array (
'name' =>
array (
'en' => 'Example service 1',
),
'description' =>
array (
'nl' => 'Dit is een voorbeeld voor de unittest 1.',
),
'attributes' =>
array (
0 => 'testN1',
1 => 'testN2',
2 => 'testN3',
),
'attributes.required' =>
array (
0 => 'testN1',
1 => 'testN2',
),
'attributes.NameFormat' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic',
),
);

$expected_a = array("urn:mace:dir:attribute-def:eduPersonPrincipalName", "urn:mace:dir:attribute-def:mail", "urn:mace:dir:attribute-def:displayName");
$expected_r = array("urn:mace:dir:attribute-def:eduPersonPrincipalName");
$expected_AttributeConsumingService_default = 1;

$this->assertEquals($expected_a, $metadata['attributes']);
$this->assertEquals($expected_r, $metadata['attributes.required']);
$this->assertEquals($expected_AttributeConsumingService, $metadata['AttributeConsumingService']);
$this->assertEquals($expected_AttributeConsumingService_default, $metadata['AttributeConsumingService.default']);
}

}
Loading
X Tutup