Important

This document in a DRAFT. The information contained herein is subject to change.

Attribute Mapping Basics#

Attribute mapping policies describe a means of extracting a set of well known identity attributes from a signed SAML assertion produced by an Identity Service Provider (IDP). We’ll begin this section by looking at a sample SAML assertion in detail, we’ll then describe the attributes that identity needs to extract from the assertion in order to operate, and we’ll wrap up by walking through the construction of a mapping policy that extracts those attributes from the assertion.

The SAML Assertion#

When an Identity Provider successfully authenticates a user it presents Rackspace Identity with a SAML Assertion, much like the one below:

Example SAML Assertion.#
 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
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema"
                 xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
                 xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 ID="_7fcd6173-e6e0-45a4-a2fd-74a4ef85bf30"
                 IssueInstant="2017-11-15T16:19:06.310Z"
                 Version="2.0">
  <saml2:Issuer>http://test.rackspace.com</saml2:Issuer>
  <ds:Signature>
      <ds:SignedInfo>
         <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
         <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
         <ds:Reference URI="#_1105c5b8-28d4-45e5-a6e4-bc4179f5a111">
            <ds:Transforms>
               <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
               <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>JaeeD+wkw297KPF+BKpdacRtaPmQKFD+DqXQ3JE3Aus=</ds:DigestValue>
         </ds:Reference>
      </ds:SignedInfo>
      <ds:SignatureValue>kDBwO3xYaxITVS7ZZIy9AGOlk16Y5E0BlqU12QlRpWGfiwjtgok4p5q3YQS+N/Pxh84JUIjd7i+n0to/2yJyaCfoSA2SIUUf448lTtHNzVmjiC4WiUmUTRGaxUpsdcYUkjFAVAS40yGDBLXMYn/JYS4cbRV52/RTJ5smCCpqBMjgzhVaeAqJif/gXGjvMLl4RFN8JGvHZGzpjCb14UdKhVqfP0ZumLo4cLIWd3Ch49zRBQBgchbFqEJbTdPPLTJ4SMIEYm5RwX4PtQ2Ce94u8IGXkIhYf32H43l+955a35XGh37hcZMLZEzjk4FBMqSScupKqDej1c0m34MkeRGMlQ==</ds:SignatureValue>
      <ds:KeyInfo>
         <ds:X509Data>
            <ds:X509Certificate>MIIC6jCCAdKgAwIBAgIQE+gZKcmH841I4gYjUiHCSDANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDEyZBREZTIFNpZ25pbmcgLSBhZGZzLmNvbnRvc293aWRnZXRzLmNvbTAeFw0xNjA2MTYwMDUyNTZaFw0xNzA2MTYwMDUyNTZaMDExLzAtBgNVBAMTJkFERlMgU2lnbmluZyAtIGFkZnMuY29udG9zb3dpZGdldHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAna30lllMTaivaXPCjrW7VcRI6BsPs0iVxV559I9UONENSldX9pYTPlqLzxTP1RAVzfbGNoSvNelXrc0cb6jslgi+0Ya0jxrj1CsxQDgLtZeZchwWUYnJgsvk/HHfXiQBrWPLaZbImPNVvzG1zlYoQyQHTe1Nvr1m5Lv9foruSnw4My2LP4M27ZLPGL7rLaqpBg0E9sMX0iIrucNNN6AdyyPsR8oAUtV//QB49pCk+/rb3UtSDyGrdFFD+sJBDiAXYjTGzzYxYnMjckBZQfPKMWRntGwe7lM1KkX7mtUr9pNSvX1mQS/PHxhIcvO7aWKc15FJKzFmtdAEM2mvZjnCtwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBj5cSPBQGyICJZHsMXA2KddWxSUqtYSBDPWYVsW9gJSMYiJBjdEnR1aGpw5K6iYei7KCACH717VQNfEF64qnBCbOvWc7FmZ3V0n4plfyZYuexbbZqp7RTi+J1q2xPsdb8MB7138YhXCc3Uf1p0oEuw+hKZ5rt4srcfgxuEauKXhnaI/UAOWOgDslzTuku+ogPsHBkc7wfH2CS9UqA3JUVJksR42yMg/Y47DUTp0Ma02RoVIfjFh+y3lX01O7B3ccCCdiKaSxcnLQ8n/ypn7LBhUUWDWZVIBj1flioohFMc5gU2Jl2Ueki72yxOwKVehqYgBHLPZBCUQUJDnUsbJJgb</ds:X509Certificate>
         </ds:X509Data>
      </ds:KeyInfo>
  </ds:Signature>
  <saml2p:Status>
      <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </saml2p:Status>
  <saml2:Assertion ID="_406fb7fe-a519-4919-a42c-f67794a670a5"
                    IssueInstant="2017-11-15T16:19:06.310Z"
                    Version="2.0">
      <saml2:Issuer>http://my.rackspace.com</saml2:Issuer>
      <ds:Signature>
         <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#_a8a6920c-d4eb-467f-85df-6fa2767ae63d">
               <ds:Transforms>
                  <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                  <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
               </ds:Transforms>
               <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
               <ds:DigestValue>uQmSKQT03SRunafqzpb6v159a+jMVvTqmBCFYn17e7s=</ds:DigestValue>
            </ds:Reference>
         </ds:SignedInfo>
         <ds:SignatureValue>cYKqfE92hWqaPylJIcl89U9TKNzJXcFIPO0fvohg70zLB4JWnYlIKOz7S9XFUvS24mN47XS1T8DeR0IGITBMhqA/GCM624SOW0QjIRhQ9gh6/ONlyuAxGbVDo5tYb82sICFa9sMWI2Vr5ZH2LeTqyvsBRnlWBkZIw4hS2PBDHbhcnILUGX9uUDRcOrONAEMimnB7cNmZxSwQgdPfupyS39oedrUAiORa7GMII8GglWoj6Jy8SX0fQKXfsXD+wC5XFw76WAKJjSCuEkrXfxMQia/2H1tE24zNgZd6Y+uQ2Nh8YlUvO+DaMoj7mTKZUBqlxQt6It4kGH0+hfqvWx1MHQ==</ds:SignatureValue>
         <ds:KeyInfo>
            <ds:X509Data>
               <ds:X509Certificate>MIIC6jCCAdKgAwIBAgIQE+gZKcmH841I4gYjUiHCSDANBgkqhkiG9w0BAQsFADAxMS8wLQYDVQQDEyZBREZTIFNpZ25pbmcgLSBhZGZzLmNvbnRvc293aWRnZXRzLmNvbTAeFw0xNjA2MTYwMDUyNTZaFw0xNzA2MTYwMDUyNTZaMDExLzAtBgNVBAMTJkFERlMgU2lnbmluZyAtIGFkZnMuY29udG9zb3dpZGdldHMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAna30lllMTaivaXPCjrW7VcRI6BsPs0iVxV559I9UONENSldX9pYTPlqLzxTP1RAVzfbGNoSvNelXrc0cb6jslgi+0Ya0jxrj1CsxQDgLtZeZchwWUYnJgsvk/HHfXiQBrWPLaZbImPNVvzG1zlYoQyQHTe1Nvr1m5Lv9foruSnw4My2LP4M27ZLPGL7rLaqpBg0E9sMX0iIrucNNN6AdyyPsR8oAUtV//QB49pCk+/rb3UtSDyGrdFFD+sJBDiAXYjTGzzYxYnMjckBZQfPKMWRntGwe7lM1KkX7mtUr9pNSvX1mQS/PHxhIcvO7aWKc15FJKzFmtdAEM2mvZjnCtwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBj5cSPBQGyICJZHsMXA2KddWxSUqtYSBDPWYVsW9gJSMYiJBjdEnR1aGpw5K6iYei7KCACH717VQNfEF64qnBCbOvWc7FmZ3V0n4plfyZYuexbbZqp7RTi+J1q2xPsdb8MB7138YhXCc3Uf1p0oEuw+hKZ5rt4srcfgxuEauKXhnaI/UAOWOgDslzTuku+ogPsHBkc7wfH2CS9UqA3JUVJksR42yMg/Y47DUTp0Ma02RoVIfjFh+y3lX01O7B3ccCCdiKaSxcnLQ8n/ypn7LBhUUWDWZVIBj1flioohFMc5gU2Jl2Ueki72yxOwKVehqYgBHLPZBCUQUJDnUsbJJgb</ds:X509Certificate>
            </ds:X509Data>
         </ds:KeyInfo>
      </ds:Signature>
      <saml2:Subject>
         <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">john.doe</saml2:NameID>
         <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <saml2:SubjectConfirmationData NotOnOrAfter="2017-11-17T16:19:06.298Z"/>
         </saml2:SubjectConfirmation>
      </saml2:Subject>
      <saml2:AuthnStatement AuthnInstant="2017-11-15T16:19:04.055Z">
         <saml2:AuthnContext>
            <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
            </saml2:AuthnContextClassRef>
         </saml2:AuthnContext>
      </saml2:AuthnStatement>
      <saml2:AttributeStatement>
         <saml2:Attribute Name="roles">
            <saml2:AttributeValue xsi:type="xs:string">nova:admin</saml2:AttributeValue>
         </saml2:Attribute>
         <saml2:Attribute Name="domain">
            <saml2:AttributeValue xsi:type="xs:string">323676</saml2:AttributeValue>
         </saml2:Attribute>
         <saml2:Attribute Name="email">
            <saml2:AttributeValue xsi:type="xs:string">john.doe@rackspace.com</saml2:AttributeValue>
         </saml2:Attribute>
         <saml2:Attribute Name="groups">
            <saml2:AttributeValue xsi:type="xs:string">group1</saml2:AttributeValue>
            <saml2:AttributeValue xsi:type="xs:string">group2</saml2:AttributeValue>
            <saml2:AttributeValue xsi:type="xs:string">group3</saml2:AttributeValue>
         </saml2:Attribute>
         <saml2:Attribute Name="FirstName">
            <saml2:AttributeValue xsi:type="xs:string">John</saml2:AttributeValue>
         </saml2:Attribute>
         <saml2:Attribute Name="LastName">
            <saml2:AttributeValue xsi:type="xs:string">Doe</saml2:AttributeValue>
         </saml2:Attribute>
      </saml2:AttributeStatement>
  </saml2:Assertion>
</saml2p:Response>

The assertion describes a view of the user that has successfully logged in. It contains within it all of the information deemed by the IDP to be relevant to the Service Provider (the SP—which in this case is Rackspace).

Note

For help configuring Third Party identity providers (such as Active Directory Federation Services, Okta, and others) please refer to the Rackspace Identity Federation User Guide.

Parts of the SAML Assertion#

In this section, we break down the SAML assertion listed above into its relevant parts. Note first that per the SAML protocol, the assertion itself is wrapped in a <saml2p:Response /> element which begins on line 2. The actual assertion (<saml2p:Assertion />) begins in line 34.

Issuer (37):
The issuer is the system that generated (or issued) the assertion. This is identified as a URI.
Signature (38–57):
The XML Signature of the assertion part of the request. The signature is used to verify that that the assertion was indeed produced by the issuer.
Subject (58–63):
The subject is used to identify the identity (or user) that the assertion is about.
AuthnStatement (64–69):
The AuthnStatement contains details on how the subject authenticated.
AttributeStatement (70–91):
This section contains a list of arbitrary attributes associated with the subject. Each attribute in the list is essentially a name/value pair. Note, that values are of a type identified by the xsi:type XML attribute—in this case they are all strings. Also note, that attributes may have multiple values. The groups attribute defined in 80–84, for example, contains 3 separate values (group1, group2, and group3).

Signing SAML Assertions#

Both the SAML Response (2) and the SAML Assertion (34) may be signed. Rackspace Identity may verify both signatures. It’s important to note however that while signing the SAML Response is optional, signing the SAML Assertion is strictly required. This means that a message that contains a single signature at the SAML Response level will be rejected.

It is possible for a SAML Response to contain multiple assertions. In this case, all assertions must be signed and they must all be issued by the same issuer. Rackspace Identity typically examines only the first assertion for authorization data, this behavior can be easily overwritten with a mapping policy.

Required Attributes#

We now examine the attributes that Rackspace Identity requires in order to successfully authenticate a user. As we look through these, we’ll examine where we can retrieve these attributes from the SAML Response above.

Domain#

Rackspace Identity keeps information about users, roles, and other entitlements in a domain. When a user federates into Rackspace, the user is placed in a single identity domain. Each domain is accessed via a unique alpha-numeric ID which Rackspace usually sets to be the same the user’s account ID. This ID is required when a federated user requests access. This is especially important because a customer is allowed to create multiple domains and Rackspace Identity needs to place the federated user in the correct one.

In the SAML Assertion above the domain is passed as a SAML attribute in lines 74–76. This implies that the identity provider was pre-configured to emit the correct value. It is not strictly required that IDP do this since most federated users target a single domain and the domain value can be easily hard coded in an attribute mapping policy as we’ll see later in this chapter.

Name#

This is the username of the federated user. Rackspace Identity assumes that each user will be identified with a unique username, and that the same user will have the same username from one federated login to the next.

In the SAML Assertion above the username is identified by the NameID in the Subject section of the assertion (line 59). That said, it is possible for an IDP to return a stable username as an attribute in the AttributeStatement section.

Email#

This is the email of the federated user. In the SAML assertion it is identified as an attribute in the AttributeStatement section (lines 77–79). Some IDPs will make no distinction between a username and an email, in which case the email will be located in the Subject section.

Roles#

Another attribute expected by Rackspace Identity is the list of roles that should be assigned to the federated user. Rackspace Identity only allows roles that it recognizes to be assigned. See the Rackspace Identity Federation User Guide for the most current list of allowed roles.

In the SAML Assertion above the list of roles is specified in an attribute named roles on lines 71–73. Note that this is a good use case for a multi-value attribute, but in this case we only assign the nova:admin role.

We are assigning nova:admin at the domain level. This means that the user will have the role on all accounts (sometimes referred to as tenants) that are associated with the domain. For example, if accounts 12873 and 33987 are associated with the domain the user will have the nova:admin role on both of those accounts.

It is possible to restrict the role to a specific account. For example, to grant nova:admin on account 12873 and nova:observer on account 33987, you should specify the roles as nova:admin/33987 and nova:observer/33987.

Expire#

Finally, Rackspace identity needs to understand the amount of time that a federated user should be allowed on Rackspace systems before the user is forced to re-authenticate. This attribute can be expressed in two different formats. First, an ISO 8601 timestamp may be provided, this timestamp should include a time zone designator. For example, the timestamp 2017-10-04T16:20:57Z signifies that the user should be forced to re-authenticate after October 4th 2017 at 16:20:57 UTC. Secondly, an ISO 8601 duration may be specified. For example, PT1H2M signifies that the user should be forced to re-authenticate one hour and two minuets after successfully logging in.

In the SAML Assertion above an expire timestamp is specified in the NotOnOrAfter attribute of the SubjectConfirmationData on line 61. In SAML, this attribute is meant to denote the time after which the SAML Assertion should no longer be considered valid. While this timestamp does not fit semantically with the expire attribute that Rackspace Identity expects it still works as a reasonable default.

Other Attributes#

The attributes described in the previous sections (domain, name, email, roles, and expire) are expected in every federated login. Some Rackspace products may expect additional optional attributes. Please consult the Rackspace Identity Federation User Guide for details on these attributes.

Mapping Attributes#

So far, we’ve broken down the SAML Assertion and identified places where we can find values for the 5 attributes that Rackspace Identity requires. This is summarized in the table below:

Attributes mapped to locations#
Attribute SAML Assertion Location (Line Numbers)
domain 74–76
name 59
email 77–79
roles 71–73
expire 61

The values at those locations are listed here:

domain 323676
name john.doe
email john.doe@rackspace.com
roles
  • nova:admin
expire 2017-11-17T16:19:06.298Z

In a sense, this table represents an attribute mapping. We are mapping data located in the SAML Assertion into attributes that Rackspace Identity requires to log in a federated user. This is a silly mapping, however, because mapping attributes by referring to line numbers is extremely unpractical, inexact, and brittle. Using XPath, on the other hand, is a more stable and practical way of pinpointing the exact location of the data that we need. After all, XPath was designed specifically to pinpoint and extract data form XML documents [1].

Mapping Attributes with XPath#

In the table below, we replace line numbers with XPaths into the SAML Assertion.

Attributes mapped to XPaths#
Attribute SAML Assertion Location (XPath)
domain /saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name=’domain’]/saml2:AttributeValue[1]
name /saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID
email /saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name=’email’]/saml2:AttributeValue[1]
roles /saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name=’roles’]/saml2:AttributeValue
expire /saml2p:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter

We can easily turn this table into an attribute mapping policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
---
mapping:
  version: RAX-1
  description: |-
    Simple policy where we select required attributes via an XPath.
  rules:
  - local:
      user:
        domain: "{Pts(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='domain']/saml2:AttributeValue[1])}"
        name:   "{Pts(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID)}"
        email:  "{Pts(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='email']/saml2:AttributeValue[1])}"
        roles:  "{Pts(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='roles']/saml2:AttributeValue)}"
        expire: "{Pts(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter)}"

Let’s walk through the policy above in detail and examine how XPath is used to extract the attribute values.

Parts of the Mapping Policy#

The mapping policy is YAML document that contains instructions aimed at retrieving (or deriving) identity attributes from a SAML Assertion. You can think of it as a simple script that executes every time a SAML Assertion is presented to Rackspace Identity. In this section, we break the mapping policy above into its relevant parts.

mapping (2):
The mapping policy is always contained in a single top-level mapping object.
version (3):
The version key identifies the version of the mapping policy language. It is a required attribute and should always have the value of RAX-1. The mapping policy language described here is based on the Mapping Combinations language by the OpenStack Keystone and the version name is used to differentiate a Rackspace Identity mapping policy from a Keystone mapping policy.
description (4):
The description key provides a human readable description of the mapping policy. This description is optional.
rules (6):
A mapping policy is made up of a collection of rules. These rules are encapsulated by the rules array. A policy is required to contain at least one rule.
rule (7–13):

Lines 7–13 contain a rule that drives the policy. A rule may contain a local and a remote section. Both local and remote sections are optional (in this case, we don’t need a remote), however, there should be at least one rule with a local section.

It’s important to note that things are local or remote from the perspective of Rackspace Identity. For example, the local section contains statements about the user within Rackspace Identity (the local user). The remote section contains statements about the user as its presented by the IDP (the remote user).

Lines 8–13 describe what the local user should look like—in other words they describe the attributes of the local user. Here, we specify each of the required identity attributes and describe how they can be obtained from an XPath.

Using XPath in the Mapping Policy#

You’ll note that the XPaths in the mapping policy are contained within something that looks like this {Pts()}—this is known as an XPath substitution. There are various types of substitutions in the mapping policy language each of which is encapsulated by curly braces {}. Substitutions are only allowed in the local part of the mapping policy, they help set values for local attributes in that they are always replaced (or substituted) by other content. In the case of the XPath substitution it is replaced by the result of the XPath within the parenthesis when executed against the SAML Assertion.

It is important to note that spaces matter in a substitution. In order to be interpreted correctly there should be no spaces between the prologue of the substitution {Pts( and the epilogue of the substitution )}.

XPath Substitutions#
XPath Substitution Valid?
{Pts(//saml2:AttributeValue[1])} valid
{Pts(  //saml2:AttributeValue[1]  )} valid
{Pt s(//saml2:AttributeValue[1]  )} invalid
{Pts(//saml2:AttributeValue[1]) } invalid

You’ll notice that the XPath substitution refers to elements in certain namespaces (saml2p, saml2). These namespace prefixes, which are common to SAML, are always predefined. The table below describes how namespace prefixes are mapped by default:

Default Namespace Prefix Mapping#
Prefix Namespace URI
saml2 urn:oasis:names:tc:SAML:2.0:assertion
saml2p urn:oasis:names:tc:SAML:2.0:protocol
xs http://www.w3.org/2001/XMLSchema
xsi http://www.w3.org/2001/XMLSchema-instance
mapping http://docs.rackspace.com/identity/api/ext/MappingRules

It is possible for a SAML Assertion to refer to elements in extended namespaces not listed here. You can define additional prefixes by adding a namespaces key to the mapping policy. In XPath, it’s the namespace URI that uniquely identifies an element—the prefix is simply a short had for this URI. In the example below, we replace the saml2p prefix with foo. Because the namespace URI bound to element is the same as the previous example the two mapping policies will produce the exact same result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
mapping:
  version: RAX-1
  description: |-
    Simple policy where we select required attributes via an XPath.
  namespaces:
    foo: urn:oasis:names:tc:SAML:2.0:protocol
  rules:
  - local:
      user:
        domain: "{Pts(/foo:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='domain']/saml2:AttributeValue[1])}"
        name:   "{Pts(/foo:Response/saml2:Assertion/saml2:Subject/saml2:NameID)}"
        email:  "{Pts(/foo:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='email']/saml2:AttributeValue[1])}"
        roles:  "{Pts(/foo:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='roles']/saml2:AttributeValue)}"
        expire: "{Pts(/foo:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter)}"

Using the {Pt()} Substitution#

Notice that the XPath for retrieving the domain ends with saml2:AttributeValue[1] while the XPath for retrieving the list of roles ends saml2:AttributeValue. This is because we want a single value for a domain, but we want a list of roles. Without the [1] the XPath would return every AttributeValue in a SAML Assertion for a domain—the [1] signifies that we are only interested in the first value we find [2].

Because it is common to want to retrieve a single value, there’s an alternative XPath substitution ({Pt()}) that always returns the first value of an XPath result. This is useful in cases where we expect a single value and we want to automatically protect against the off chance that we’ll receive multiple values in a SAML assertion.

Given this new substitution, we can rewrite the mapping policy as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
mapping:
  version: RAX-1
  description: |-
    Simple policy where we select required attributes via an XPath.
    We use {Pt()} instead of {Pts()} in single value attributes to
    avoid having to select the first attribute value in XPath.
  rules:
  - local:
      user:
        domain: "{Pt(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='domain']/saml2:AttributeValue)}"
        name:   "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID)}"
        email:  "{Pt(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='email']/saml2:AttributeValue)}"
        roles:  "{Pts(/saml2p:Response/saml2:Assertion/saml2:AttributeStatement/saml2:Attribute[@Name='roles']/saml2:AttributeValue)}"
        expire: "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter)}"

Using the mapping:get-attributes Call#

At this point, the XPaths for retrieving a domain, email, and roles all look almost exactly the same as each other. They are all retrieving the list of values for an attribute in the AttributeStatement of the assertion by name. Because this is a common case, there is a predefined XPath function called mapping:get-attributes that takes the name of a SAML attribute as a string, and returns the attribute values associated with that name.

We can rewrite the mapping policy using this function as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
mapping:
  version: RAX-1
  description: |-
    Simple policy where we select required attributes via an
    XPath. Here we use the mapping:get-attributes call to return
    attribute values.
  rules:
  - local:
      user:
        domain: "{Pt(mapping:get-attributes('domain'))}"
        name:   "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID)}"
        email:  "{Pt(mapping:get-attributes('email'))}"
        roles:  "{Pts(mapping:get-attributes('roles'))}"
        expire: "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter)}"

Using the {Ats()} and {At()} Substitutions#

Having an XPath function that retrieves SAML attribute values is handy when you want to process those values in some way before returning a result. In some cases, however, you simply want to return the value (or values) of a SAML attribute as is. The {Ats()} and {At()} substitutions, do just that. These are known as attribute substitutions. As with their XPath counterparts, the {Ats()} substitution returns all values for a specific attribute name and the {At()}.

Given these substitutions we can rewrite the policy as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
---
mapping:
  version: RAX-1
  description: |-
    Simple policy where we select required attributes. We use At
    instead of Pts as a simple means of accessing an name SAML
    attribute.
  rules:
  - local:
      user:
        domain: "{At(domain)}"
        name:   "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:NameID)}"
        email:  "{At(email)}"
        roles:  "{Ats(roles)}"
        expire: "{Pt(/saml2p:Response/saml2:Assertion/saml2:Subject/saml2:SubjectConfirmation/saml2:SubjectConfirmationData/@NotOnOrAfter)}"

Notice that in this case the name of the attribute is not in single quotes. We are no longer directly using XPath with attribute substitutions, although, under the hood, these substitutions are also interpreted as XPath statements.

Using the {D} Substitution#

There are certain default places where a mapping policy may look for a particular identity attribute value. The default substitution ({D}) is always replaced with the value at the default location. Unlike other substitutions the default substitution is interpreted differently depending on where the substitution is placed. For example, if the substitution is placed as a value of the name attribute it will replace itself with the default value for name. Likewise, if {D} is placed in the email attribute it will be replaced with the default value for email and so on.

What are the default locations in a SAML assertion for the five attributes Rackspace Identity expects? It turns out that the SAML assertion in this chapter has all of the values in the default places! So it’s possible, in this case, to write a mapping policy that looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
---
mapping:
  version: RAX-1
  description: |-
    The default policy.  All attributes are in the expected location
    in the SAML assertion.
  rules:
  - local:
      user:
        domain: "{D}"
        name:   "{D}"
        email:  "{D}"
        roles:  "{D}"
        expire: "{D}"

Next Steps#

So far we’ve learned about the SAML Assertions and seen how to use XPath, attribute names, and expected defaults to write mapping polices. All of the polices we’ve created, however, have described simple direct mappings. In the chapters that follow we’ll start looking at writing polices in cases where things don’t align so perfectly.

[1]Later versions of XPath allow extracting data from JSON documents as well!
[2]The first index in XPath is 1 not 0. This logical and makes sense unless you’re a software developer.

Important

This document in a DRAFT. The information contained herein is subject to change.