Jorge's Quest For Knowledge!

All About Identity And Security On-Premises And In The Cloud – It's Just Like An Addiction, The More You Have, The More You Want To Have!

Archive for the ‘Claims Rule Language’ Category

(2018-11-01) Transform Rules In ADFS – Send E-Mail Address Value For E-mail, Otherwise Send UPN Value For E-Mail

Posted by Jorge on 2018-11-01


A few days ago I got the following question:

  • Some accounts have a mailbox, some do not;
  • For accounts that do have a mailbox I need to send the e-mail address value as the claim value in the e-mail address claim type;
  • For accounts that do not have a mailbox I need to send the upn value as the claim value in the e-mail address claim type;

How can I achieve that through the claims rule language in ADFS?

It is not that difficult. See below for an example as the answer to the question above:

RULENAME1: User Identity Claims – Windows Account Name
COMMENT1: if the windows account name claim exists and has a value send it through

c:[Type == "
http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
=> issue(claim = c);

RULENAME2: User Identity Claims – upn
COMMENT2: if the UPN claim exists and has a value send it through

c:[Type == "
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> issue(claim = c);

RULENAME3: User Identity Claims – E-Mail Address
COMMENT3: get mail from AD and put it in claim

c:[Type == "
http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), query = ";mail;{0}", param = c.Value);

RULENAME4: System Claims – Check E-Mail Address Existence
COMMENT4: check if the mail address claim exist or not

NOT EXISTS([Type == "
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"])
=> add(Type = "http://temp.org/system/claims/eMailAddressExistance", Value = "False");

RULENAME5: User Identity Claims – upn To E-Mail Address (When E-Mail Address DOES NOT Exist)
COMMENT5: send the UPN value into mail address claim if no mail claim address exists

c1:[Type == "
http://temp.org/system/claims/eMailAddressExistance", Value == "False"] &&
c2:[Type == "
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value = c2.Value);

Make sure to test this first in a TEST environment!

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Active Directory Federation Services (ADFS), Claims, Claims Rule Language | Leave a Comment »

(2017-09-07) Claims Rules Set For Your AAD/O365 RP Trust To Support User And Device Authentication

Posted by Jorge on 2017-09-07


On the page “How to configure hybrid Azure Active Directory joined devices” Microsoft explains how to setup Domain Join ++, currently a.k.a. Hybrid AAD Join. This post is about, hopefully giving additional, clarification on how to setup the claims rules in ADFS. All the scenarios listed below work, it just on your scenario. Feel free to leave any comments if unclear. The import of the rules below should replace your current rules for the AAD RP Trust, and where applicable add rules to your AD CP trust. A final note, is that these rule sets are based upon Microsoft’s rule in the previous article.

Make sure to test this first in your test environment!

You may also want to have a look at the following blog post:

(2016-12-16) Automatic Azure AD Join With ADFS v3.0 And Higher And Conditional Access – What You Really Need In Detail

[OPTION 1]

The following works if you have claims rules in place on the AD CP trust that output the following claim types. This is true if you are using the default claim rule set in ADFSv3/ADFSv4:

Assumptions here:

  • You have one ADFS environment servicing all the federated domains in AAD
  • The federation identifier being used on your federated domains in AAD is default
  • The primary group was not changed for any user or any computer
  • The objectGUID is the attribute being used for the Immutable ID for users
  • The objectGUID is the attribute being used for the Immutable ID for computers
  • On-premises UPN is also the UPN used in AAD
  • Whether or not you have multiple federated domains, the config below for the IssuerID for users works as long as the UPN of the user matches one of the federated domains in AAD
  • You will replace FEDERATED-DOMAIN with one of the federated domains in AAD. It does not matter which one
  • You will replace NAME-OF-AAD/O365-RP-TRUST with the actual name of the RP trust for AAD/O365

Remarks:

  • Leave a comment if you have any deviation of the assumptions above

Thoughts here:

  • It is making multiple LDAP calls to AD, and is therefore not that optimized. It works though!
  • Still using groupsid claim type

Link to claims rules below: https://www.dropbox.com/s/vgzvnwhku2adp39/AzureAD-RP-Trust-Issuance-Rules1.txt

$aadRPTrustIssuanceRules = @"

@RuleName = "Issue UPN And ImmutableID (Domain User Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Value =~ "-513$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"%5D

=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/claims/UPN", "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"), query = ";userPrincipalName,objectGUID;{0}", param = c2.Value);

@RuleName = "Issue IssuerID (Domain User Only)"

c:[Type == "http://schemas.xmlsoap.org/claims/UPN"%5D

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = regexreplace(c.Value, ".+@(?<domain>.+)", http://${domain}/adfs/services/trust/));

@RuleName = "Issue ImmutableID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID&quot;), query = ";objectguid;{0}", param = c2.Value);

@RuleName = "Issue Account Type (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype&quot;, Value = "DJ");

@RuleName = "Issue ObjectGUID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/identity/claims/onpremobjectguid&quot;), query = ";objectguid;{0}", param = c2.Value);

@RuleName = "Issue ObjectSID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(claim = c2);

@RuleName = "Issue IssuerID (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = http://FEDERATED-DOMAIN/adfs/services/trust/);

@RuleName = "Issue NameID (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"%5D

=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;, Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"%5D = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issue AuthN Methods References (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/claims/authnmethodsreferences"%5D

=> issue(claim = c);

"@

Set-AdfsRelyingPartyTrust -TargetName "NAME-OF-AAD/O365-RP-TRUST" -IssuanceTransformRules $aadRPTrustIssuanceRules

[OPTION 2]

The following works if you have claims rules in place on the AD CP trust that output the following claim types. This is true if you are using the default claim rule set in ADFSv3/ADFSv4:

Assumptions here:

  • You have one ADFS environment servicing all the federated domains in AAD
  • The federation identifier being used on your federated domains in AAD is default
  • The primary group was not changed for any user or any computer
  • The objectGUID is the attribute being used for the Immutable ID for users
  • The objectGUID is the attribute being used for the Immutable ID for computers
  • On-premises UPN is also the UPN used in AAD
  • Whether or not you have multiple federated domains, the config below for the IssuerID for users works as long as the UPN of the user matches one of the federated domains in AAD
  • You will replace FEDERATED-DOMAIN with one of the federated domains in AAD. It does not matter which one
  • You will replace NAME-OF-AAD/O365-RP-TRUST with the actual name of the RP trust for AAD/O365

Remarks:

  • Leave a comment if you have any deviation of the assumptions above

Thoughts here:

  • Although a few LDAP calls less, it is still making LDAP calls to AD. It works though!
  • Still using groupsid claim type

Link to claims rules below: https://www.dropbox.com/s/udn1rudm5bgv7jq/AzureAD-RP-Trust-Issuance-Rules2.txt

$aadRPTrustIssuanceRules = @"

@RuleName = "Issue UPN (Domain User Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-513$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"%5D

=> issue(Type = "http://schemas.xmlsoap.org/claims/UPN&quot;, Issuer = c2.Issuer, OriginalIssuer = c2.OriginalIssuer, Value = c2.Value, ValueType = c2.ValueType);

@RuleName = "Issue IssuerID (Domain User Only)"

c:[Type == "http://schemas.xmlsoap.org/claims/UPN"%5D

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = regexreplace(c.Value, ".+@(?<domain>.+)", http://${domain}/adfs/services/trust/));

@RuleName = "Issue Account Type (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype&quot;, Value = "DJ");

@RuleName = "Issue ObjectGUID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/identity/claims/onpremobjectguid&quot;), query = ";objectguid;{0}", param = c2.Value);

@RuleName = "Issue ObjectSID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(claim = c2);

@RuleName = "Issue IssuerID (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = http://FEDERATED-DOMAIN/adfs/services/trust/);

@RuleName = "ImmutableID (Domain User And Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"%5D

=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID&quot;), query = ";objectGUID;{0}", param = c.Value);

@RuleName = "Issue NameID (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"%5D

=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;, Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"%5D = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issue AuthN Methods References (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/claims/authnmethodsreferences"%5D

=> issue(claim = c);

"@

Set-AdfsRelyingPartyTrust -TargetName "NAME-OF-AAD/O365-RP-TRUST" -IssuanceTransformRules $aadRPTrustIssuanceRules

[OPTION 3]

The following works if you have claims rules in place on the AD CP trust that output the following claim types. This is true if you are using the default claim rule set in ADFSv3/ADFSv4:

For the last claim type you would need the following claim rule on the AD CP trust if you are not already extracting additional data from AD

$additionalADCPTrustRule = @"
@RuleTemplate = "LdapClaims"
@RuleName = "Extract Extra Data From AD"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]
  => issue(store = "Active Directory", types = ("http://temp.org/identity/claims/objectGUID&quot;), query = ";objectguid;{0}", param = c.Value);
"@

$existingADCPtrustAcceptRuleSet = (Get-AdfsClaimsProviderTrust -Name "Active Directory").AcceptanceTransformRules

$newADCPtrustAcceptRuleSet = $existingADCPtrustAcceptRuleSet + $additionalADCPTrustRule

Set-AdfsClaimsProviderTrust -TargetName "Active Directory" -AcceptanceTransformRules $newADCPtrustAcceptRuleSet

If you are already extracting additional data from AD, you need to add the claim type and attribute to that existing extraction

Assumptions here:

  • You have one ADFS environment servicing all the federated domains in AAD
  • The federation identifier being used on your federated domains in AAD is default
  • The primary group was not changed for any user or any computer
  • The objectGUID is the attribute being used for the Immutable ID for users
  • The objectGUID is the attribute being used for the Immutable ID for computers
  • On-premises UPN is also the UPN used in AAD
  • Whether or not you have multiple federated domains, the config below for the IssuerID for users works as long as the UPN of the user matches one of the federated domains in AAD
  • You will replace FEDERATED-DOMAIN with one of the federated domains in AAD. It does not matter which one
  • You will replace NAME-OF-AAD/O365-RP-TRUST with the actual name of the RP trust for AAD/O365

Remarks:

  • Leave a comment if you have any deviation of the assumptions above

Thoughts here:

  • No LDAP calls!
  • Still using groupsid claim type

Link to claims rules below: https://www.dropbox.com/s/8cicjpyusapsarj/AzureAD-RP-Trust-Issuance-Rules3.txt

$aadRPTrustIssuanceRules = @"

@RuleName = "Issue UPN (Domain User Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-513$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"%5D

=> issue(Type = "http://schemas.xmlsoap.org/claims/UPN&quot;, Issuer = c2.Issuer, OriginalIssuer = c2.OriginalIssuer, Value = c2.Value, ValueType = c2.ValueType);

@RuleName = "Issue IssuerID (Domain User Only)"

c:[Type == "http://schemas.xmlsoap.org/claims/UPN"%5D

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = regexreplace(c.Value, ".+@(?<domain>.+)", http://${domain}/adfs/services/trust/));

@RuleName = "Issue Account Type (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype&quot;, Value = "DJ");

@RuleName = "Issue ObjectGUID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://temp.org/identity/claims/objectGUID"%5D

=> issue(Type = "http://schemas.microsoft.com/identity/claims/onpremobjectguid&quot;, Issuer = c2.Issuer, OriginalIssuer = c2.OriginalIssuer, Value = c2.Value, ValueType = c2.ValueType);

@RuleName = "Issue ObjectSID (Domain Joined Computer Only)"

c1:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(claim = c2);

@RuleName = "Issue IssuerID (Domain Joined Computer Only)"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "-515$", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = http://FEDERATED-DOMAIN/adfs/services/trust/);

@RuleName = "ImmutableID (Domain User And Domain Joined Computer)"

c:[Type == "http://temp.org/identity/claims/objectGUID"%5D

=> issue(Type = "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

@RuleName = "Issue NameID (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"%5D

=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;, Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"%5D = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issue AuthN Methods References (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/claims/authnmethodsreferences"%5D

=> issue(claim = c);

"@

Set-AdfsRelyingPartyTrust -TargetName "NAME-OF-AAD/O365-RP-TRUST" -IssuanceTransformRules $aadRPTrustIssuanceRules

[OPTION 4]

The following works if you have claims rules in place on the AD CP trust that output the following claim types.  This is true if you are using the default claim rule set in ADFSv3/ADFSv4 for the first 2 claim types, but not for the last 2 claim types. My main reason for not using groupsid is that the amount of values can be so huge it may not fit on a cookie and the browser chokes. Because of that, when not using the groupsid claim type, you need to design a better model to extract group data from AD and use authorization based claims types from that data. If you are not using the groupsid claim type you can also not identify the difference between a user and a computer. That’s why you can then specifically extract the primaryGroupID.:

For the last 2 claim types you would need the following claim rule on the AD CP trust if you are not already extracting additional data from AD

$additionalADCPTrustRule = @"
@RuleTemplate = "LdapClaims"
@RuleName = "Extract Extra Data From AD"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]
  => issue(store = "Active Directory", types = ("http://temp.org/identity/claims/objectGUID&quot;,"http://temp.org/identity/claims/primaryGroupID&quot;), query = ";objectguid,primaryGroupID;{0}", param = c.Value);
"@

$existingADCPtrustAcceptRuleSet = (Get-AdfsClaimsProviderTrust -Name "Active Directory").AcceptanceTransformRules

$newADCPtrustAcceptRuleSet = $existingADCPtrustAcceptRuleSet + $additionalADCPTrustRule

Set-AdfsClaimsProviderTrust -TargetName "Active Directory" -AcceptanceTransformRules $newADCPtrustAcceptRuleSet

If you are already extracting additional data from AD, you need to add the claim type and attribute to that existing extraction

Assumptions here:

  • You have one ADFS environment servicing all the federated domains in AAD
  • The federation identifier being used on your federated domains in AAD is default
  • The primary group was not changed for any user or any computer
  • The objectGUID is the attribute being used for the Immutable ID for users
  • The objectGUID is the attribute being used for the Immutable ID for computers
  • On-premises UPN is also the UPN used in AAD
  • Whether or not you have multiple federated domains, the config below for the IssuerID for users works as long as the UPN of the user matches one of the federated domains in AAD
  • You will replace FEDERATED-DOMAIN with one of the federated domains in AAD. It does not matter which one
  • You will replace NAME-OF-AAD/O365-RP-TRUST with the actual name of the RP trust for AAD/O365

Remarks:

  • Leave a comment if you have any deviation of the assumptions above

Thoughts here:

  • No LDAP calls!
  • No groupsid claim type!

Link to claims rules below: https://www.dropbox.com/s/6nbwuuxl677wb3p/AzureAD-RP-Trust-Issuance-Rules4.txt

$aadRPTrustIssuanceRules = @"

@RuleName = "Issue UPN (Domain User Only)"

c1:[Type == "http://temp.org/identity/claims/primaryGroupID&quot;, Value =~ "^513$"] &&

c2:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"%5D

=> issue(Type = "http://schemas.xmlsoap.org/claims/UPN&quot;, Issuer = c2.Issuer, OriginalIssuer = c2.OriginalIssuer, Value = c2.Value, ValueType = c2.ValueType);

@RuleName = "Issue IssuerID (Domain User Only)"

c:[Type == "http://schemas.xmlsoap.org/claims/UPN"%5D

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = regexreplace(c.Value, ".+@(?<domain>.+)", http://${domain}/adfs/services/trust/));

@RuleName = "Issue Account Type (Domain Joined Computer Only)"

c:[Type == "http://temp.org/identity/claims/primaryGroupID&quot;, Value =~ "^515$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2012/01/accounttype&quot;, Value = "DJ");

@RuleName = "Issue ObjectGUID (Domain Joined Computer Only)"

c1:[Type == "http://temp.org/identity/claims/primaryGroupID&quot;, Value =~ "^515$"] &&

c2:[Type == "http://temp.org/identity/claims/objectGUID"%5D

=> issue(Type = "http://schemas.microsoft.com/identity/claims/onpremobjectguid&quot;, Issuer = c2.Issuer, OriginalIssuer = c2.OriginalIssuer, Value = c2.Value, ValueType = c2.ValueType);

@RuleName = "Issue ObjectSID (Domain Joined Computer Only)"

c1:[Type == "http://temp.org/identity/claims/primaryGroupID&quot;, Value =~ "^515$"] &&

c2:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"]

=> issue(claim = c2);

@RuleName = "Issue IssuerID (Domain Joined Computer Only)"

c:[Type == "http://temp.org/identity/claims/primaryGroupID&quot;, Value =~ "^515$"]

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/issuerid&quot;, Value = http://FEDERATED-DOMAIN/adfs/services/trust/);

@RuleName = "ImmutableID (Domain User And Domain Joined Computer)"

c:[Type == "http://temp.org/identity/claims/objectGUID"%5D

=> issue(Type = "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

@RuleName = "Issue NameID (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"%5D

=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier&quot;, Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"%5D = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");

@RuleName = "Issue AuthN Methods References (Domain User and Domain Joined Computer)"

c:[Type == "http://schemas.microsoft.com/claims/authnmethodsreferences"%5D

=> issue(claim = c);

"@

Set-AdfsRelyingPartyTrust -TargetName "NAME-OF-AAD/O365-RP-TRUST" -IssuanceTransformRules $aadRPTrustIssuanceRules

Hopefully this blog post gives you the information you need to understand what is needed

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Active Directory Federation Services (ADFS), Azure AD / Office 365, Azure AD Join, Claim Types, Claims, Claims Rule Language, Windows Azure Active Directory | 2 Comments »

(2017-06-12) Changing The Identity Type Displayed On The MFA Page In ADFS

Posted by Jorge on 2017-06-12


By default when you hit the MFA page in ADFS 2012 R2, the identity type displayed is similar to DOMAIN\SAMACCOUNTNAME as you can see in figure 1 below. The MFA page in ADFS 2012 R2 by default uses the value from the name claim type (“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name”) to display that same value in the MFA page in ADFS 2012 R2.

The name claim type is configured by default in an Acceptance Transform Rule on the “Active Directory” CP trust, and it is most likely read from the “msDS-PrincipalName” attribute in AD.

The Acceptance Transform Rule on the “Active Directory” CP trust for the name claim type may be similar to

@RuleTemplate = "PassThroughClaims"
@RuleName = "Pass through all Name claims"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;, Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c);

image

Figure 1: MFA Page In ADFS 2012 R2 With The Default Value For The Name Claim Type

However, with all the cloud development going on and everything focusing on the mail address of a user instead, you may want to display the mail address of the user instead as displayed in figure 2 below.

To make this change you need to delete the default Acceptance Transform Rule on the “Active Directory” CP trust for the name claim type and create a new rule where you will flow the value of the mail claim value (MAIL@ADDRESS.COM) into the name claim type. The new Acceptance Transform Rule on the “Active Directory” CP trust would look like:

@RuleTemplate = "MapClaims"
@RuleName = "E-mail Address To Name"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"%5D
=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

image

Figure 2: MFA Page In ADFS 2012 R2 With The Custom Value For The Name Claim Type

Is there a catch to this? Yes there is, or better yes there are!

[Catch 1]

By default the e-mail address is NOT extracted from AD by ADFS! Because of that you need to create your own Acceptance Transform Rule where you do that. The Acceptance Transform Rule you need at least is or looks similar to:

@RuleTemplate = "LdapClaims"
@RuleName = "E-mail Address"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress&quot;), query = ";mail;{0}", param = c.Value);

Because the name claim type depends on this Acceptance Transform Rule, this Acceptance Transform Rule for the mail claim type must be processed before the Acceptance Transform Rule for the name claim type

[Catch 2]

Some applications connected to ADFS may expect to receive a name claim value that looks like DOMAIN\SAMACCOUNTNAME instead of MAIL@ADDRESS.COM. By implementing the new Acceptance Transform Rule for the name claim type you will impact those applications.

To mitigate that impact before making those changes, you would need reconfigure the RP trust of the application that needs the name claim value. If RP trust has an Issuance Transform Rule that is similar to or looks like:

@RuleTemplate = "PassThroughClaims"
@RuleName = "Pass through all Name claims"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"%5D => issue(claim = c);

….you would need to delete that Issuance Transform Rule and replace it with:

@RuleTemplate = "MapClaims"
@RuleName = "Windows Account Name To Name"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"%5D
=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

[Catch 3]

By default when you hit the MFA page in ADFS 2016, the identity type displayed is similar to UPN@ADDRESS.COM as you can see in figure 1 below. The MFA page in ADFS 2016 by default uses the value from the upn claim type (“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn”) to display that same value in the MFA page in ADFS 2016. However, if there is no value for the upn claim, it will fall back to the value of the name claim. Unless you have removed it, by default ADFS has an Acceptance Transform Rule on the “Active Directory” CP trust for the upn claim type. And if you look carefully, the text also changed (compare figure 1 and figure 3).

As you can see in figure 3, it now really shows my upn (jorge@iamtec.net) whereas mail e-mail address uses a different suffix (jorge@iamtec.nl). With the use of Azure AD, the federated/managed domain in AAD is iamtec.nl and not iamtec.net.

image

Figure 3: MFA Page In ADFS 2016 With The Default Value For The UPN Claim Type

To be able to logon with the mail address I have 2 options, either update the userPrincipalName value to match the mail address value in AD or keep it as is and use the alternate logon ID feature in ADFS to target the mail address field in AD. If you choose the first option the value in the MFA page is also updated automatically and you do not need to update Acceptance Transform Rules. If you choose the second option you may need to delete the default Acceptance Transform Rule for the upn claim type and replace it with the following Acceptance Transform Rule:

@RuleTemplate = "MapClaims"
@RuleName = "E-Mail Address To UPN"

c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"%5D
=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

This new Acceptance Transform Rule for the upn claim type would result in the following for on the MFA page in ADFS 2016

image

Figure 4: MFA Page In ADFS 2016 With The Custom Value For The UPN Claim Type

Is there a catch to the catch? Unfortunately there is! As mentioned earlier for the name claim type, the same would apply to the upn clam type. Some applications connected to ADFS may expect to receive a upn claim value that looks like UPN@ADDRESS.COM instead of MAIL@ADDRESS.COM. By implementing a new Acceptance Transform Rule for the upn claim type you will impact those applications.

To mitigate that impact before making those changes, you would need reconfigure the “Active Directory” CP trust and implement an Acceptance Transform Rule that is similar to or looks like:

@RuleTemplate = "MapClaims"
@RuleName = "Original UPN To Custom UPN Claim"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"%5D
=> issue(Type = "
http://temp.org/identity/claims/orgUPN&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

…and you would need reconfigure the RP trust of the application that needs the original upn claim value. If RP trust has an Issuance Transform Rule that is similar to or looks like:

c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"%5D
=> issue(claim = c);

….you would need to delete that Issuance Transform Rule and replace it with:

@RuleTemplate = "MapClaims"
@RuleName = "Custom UPN Claim To Original UPN"
c:[Type == "http://temp.org/identity/claims/orgUPN""http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"%5D
=> issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

Cheers,
Jorge

 

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Active Directory Federation Services (ADFS), Azure AD MFA Adapter, Claim Types, Claims, Claims Rule Language, Federation Trusts | Leave a Comment »

(2014-07-19) Understanding Claim Rule Language In AD FS 2.0 And Higher

Posted by Jorge on 2014-07-19


In addition to previous blog posts that I had found regarding the claims rule language, I found yet another great article about the same topic. Kudos and credits of course go to the writer/contributors of the wiki post/article.

SOURCE: Understanding Claim Rule Language in AD FS 2.0 & Higher

<QUOTE SOURCE=”Understanding Claim Rule Language in AD FS 2.0 & Higher”>

Introduction

Claims Rules follow a basic pipeline.  The rules define which claims are accepted, processed, and eventually sent to the relying party.  Claim Rules are defined as a property of the Claims Provider Trust (incoming claims) and the Relying Party Trust (outgoing claims).  Basic claim passing and transformations can be handled using the built in Claim Rule Templates.

Understanding Claim Sets

It is important to understand claim sets as part of the claims pipeline.  When claims come in, they are a part of the incoming claim set.  After claims are processed by claim rules, they become part of the outgoing claim set.  An important piece to understand is there is an incoming and outgoing claim set for the Claims Provider Trust and for the Relying Party Trust, so there are two places claims can be processed before leaving AD FS.

  1. Claims come into the Claims Provider Trust as the incoming claim set
  2. Claims are processed using claim rules and become part of the outgoing claim set
  3. The outgoing claim set is passed to the Relying Party Trust as the incoming claim set for the Relying Party Trust
  4. The set of claims are processed using claim rules and become part of the final outgoing claim set

Read more about the claims pipeline here

General Syntax of the Claim Rule Language

There are two parts to each rule:

  • Condition statement
  • Issuance statement

If the condition statement is true, the issuance statement will be executed.  If the condition statement is false, the engine will move on to the next rule.

Example: Simple Claims Rule Syntax

c:[Type == "http://contoso.com/department"%5D
=>issue(Type = “http://adatum.com/department”, Value = c.Value);

This example takes an incoming claim http://contoso.com/department and issues a new claim http://adatum.com/department with the same value as the incoming claim.  These claim types are URIs in the HTTP format but can also be in the URN format. URIs are not URLs and do not need to be actual pages on the Internet or intranet.

Condition Statements

Condition statements look at all incoming claims and determine if there is one that matches the condition. The following properties can be queried in an incoming claim:

  • Type
  • Value
  • Issuer
  • OriginalIssuer
  • ValueType

The format for querying an incoming claim is c:[query] where the variable c represents a claim in the incoming claim set.  The query can be more specific and check for more than one property.  See some of the examples below to get an idea of how the format works.  The two examples below are not complete syntax, as they are missing the issuance statement.

Example: Check for an incoming claim type http://contoso.com/department

c:[type == http://contoso.com/department]

Example: Check for an incoming claim type http://contoso.com/department with a value of sales

c:[type == "http://contoso.com/department&quot;, value == "sales"]

Condition statements are optional in the claims rule language.  By leaving the condition statement blank, the claim rule will always evaluate as true.

Example: Issue a claim http://contoso.com/partner with the value of adatum to all incoming claim sets

=>issue(Type = “http://contoso.com/partner”, Value = "adatum");

Issuance Statements

There are two types of issuance statements to use.

  • Add – adds the claim to the incoming claim set
  • Issue – adds the claim to the outgoing claim set

The ADD issuance statement is used to add additional claims to the incoming claim set so that subsequent claim rules can use them for processing. The ISSUE issuance statement is used to add claims to the outgoing claim.

Example: Issue a claim http://contoso.com/department to the outgoing claim set

=> issue(type = "http://contoso.com/department&quot;, value = "marketing");

Example: Add a claim http://contoso.com/partner to the incoming claim set

=> add(type = "http://contoso.com/partner&quot;, value = "adatum");

Example: Check for an incoming claim type http://contoso.com/email and if found, issue a claim http://contoso.com/role with the value of Exchange User

c:[type == "http://contoso.com/emailaddress"%5D
=> issue(type = "http://contoso.com/role&quot;, value = "Exchange User");

The entire incoming claim can be passed on or certain values inside the claim can be used in the outgoing claim. Use the variable c in the issuance statement to pass the entire claim or parts of the claim.

Example: Check for an incoming claim type http://contoso.com/role and if found, issue the exact same claim to the outgoing claim set

c:[type == "http://contoso.com/role"%5D
=> issue(claim = c);

Example: Check for an incoming claim type http://contoso.com/role and if found, issue a claim http://adatum.com/role with the same value of the incoming claim

c:[type == "http://contoso.com/role"%5D
=> issue(type = "http://adatum.com/role&quot;, value = c.Value);

Multiple Conditions

Another possibility is to have multiple conditions, and if all conditions evaluate to true, run the issuance statement.  Each condition is joined using the && special operator.  There is not a logical OR operator.  To accomplish an OR, create separate claim rules.

Example: Check for an incoming claim type http://contoso.com/role with a value of Editor and separate incoming claim type  http://contoso.com/role with a value of Manager. If both are found, issue a claim http://contoso.com/role with the value of Managing Editor

c1:[type == "http://contoso.com/role&quot;, value=="Editor"] &&
c2:[type == "http://contoso.com/role&quot;, value=="Manager"]
=> issue(type = "http://contoso.com/role&quot;, value = "Managing Editor");

Combining Values

The values of each individual incoming claim can be accessed and joined using the special operator + in the issuance statement.

Example: Check for an incoming claim type http://contoso.com/location and separate incoming claim type http://contoso.com/role. If both are found, issue a claim http://contoso.com/targetedrole combining the values of the incoming roles

c1:[type == "http://contoso.com/location"%5D &&
c2:[type == "http://contoso.com/role"%5D
=> issue(type = "http://contoso/targetedrole&quot;, value = c1.Value + " " c2.Value);

Example Incoming Claims:

"http://contoso.com/location&quot; is “Seattle”

“http://contoso.com/role” is “Editor”

Example Outgoing Claim:

“http://contoso.com/targetedrole” is “Seattle Editor”

Aggregate Functions

Typical claims rules will issue an output claim for each match it finds.  Aggregate functions will issue or add a single claim regardless of the number of matches. The EXISTS function serves this purpose.


EXISTS

Example: Claims rule without an Aggregate Function

c:[type == "http://contoso.com/emailaddress"%5D
=> issue(type = "http://contoso.com/role&quot;, value = "Exchange User");

This example would issue multiple http://contoso.com/role claims if the incoming claim set had multiple email addresses.  If that is not desired, use the EXISTS function as shown below.

Example: Check for any incoming claims with the type http://contoso.com/emailaddress and if any are found, issue a single claim type http://contoso.com/role with the value of Exchange User:

EXISTS([type == "http://contoso.com/emailaddress"%5D)
=> issue(type = "http://contoso/role&quot;, value = "Exchange User");

NOT EXISTS

There is also an option to use NOT EXISTS to issue claims if there is no incoming claim that matches the condition. This can be useful for subsequent rules that combine values.

Example: Claim rule that will only work if the incoming claim set has both claims

c1:[type == "http://contoso.com/location"%5D &&
c2:[type == "http://contoso.com/role"%5D
=> issue(type = "http://contoso/targetedrole&quot;, value = c1.Value + " " c2.Value);

This claim rule will only trigger if the incoming claim set has a http://contoso.com/location and a http://contoso.com/role incoming claim. If the location claim is not present, the outgoing set will not contain a http://contoso.com/targetedrole claim. If it is desired that all outgoing claim sets have this particular claim, the NOT EXISTS function can be used in a separate claim rule.

Example: Claim rule that uses the NOT EXISTS Aggregate Function

NOT EXISTS([type == "http://contoso.com/location"%5D)
=> add(type = "http://contoso/location&quot;, value = "Unknown");

Here is an example set of incoming and outgoing claims if the NOT EXISTS Aggregate function claim rule is included with the multiple condition claim rule.

Example Incoming Claims:

“http://contoso.com/role” is “Editor”

Example Outgoing Claim:

“http://contoso.com/targetedrole” is “Unknown Editor”

Another good use for the NOT EXISTS aggregate function is to restrict access to certain applications based on group membership.

Example: Issuance Authorization claim rule that uses the NOT EXISTS Aggregate Function

NOT EXISTS([type == "http://contoso.com/group&quot;,  Value =~ "^(?i)ADFSUser"])
=> issue(type = "http://schemas.microosft.com/authorization/claims/deny&quot;, value = "DenyUsersWithClaim");

This claim rule will deny users access to the relying party if they are not a member of a group that starts with ADFSUser.  It group name evaluation is not case sensitive. The syntax uses Regular Expressions (regex) which is explained in more detail in the next section.

COUNT

Another aggregate function available in AD FS 2.0 is the COUNT function.  The claim will only be issued if the condition statement is true.

Example: Claim Rule that uses the COUNT Aggregate Function

COUNT([type == http://contoso.com/proxyAddresses"%5D) >= 2
=> issue(type = "http://contoso.com/MultipleEmails&quot;, value = "True");

This claim rule will issue the claim if the user has two or more proxy address claims.

Using Regular Expressions

Regular Expressions (regex) can be used in the condition or issuance statements.  In a condition statement, regex allows similar matches to evaluate true.  In issuance statements, regex allows parts of the string values to be used in the outgoing claim. Regular Expressions use special characters to perform various tasks inside a string.

Character Description Examples
$ Matches the end of a string contoso.com$ matches a string that ends with "contoso.com"
bob@contoso.com would evaluate true
bob@contoso2.com would evaluate false
^ Matches the beginning of a string ^bob matches a string that starts with "bob"
bob.smith@contoso.com would evaluate true
bonny.smith@contoso.com would evaluate false

Example: Using the $ expression.  Matches strings that end in "contoso.com"

c:[type == "http://contoso.com/email&quot;, Value =~ "contoso.com$"]
=> issue (claim = c);

Example: Using the ^ expression.  Matches strings that start with "bob"

c:[type == "http://contoso.com/email&quot;, Value =~ "^bob"]
=> issue (claim = c);

Example: Matches strings that contain "bob"

c:[type == "http://contoso.com/email&quot;, Value =~ "bob"]
=> issue (claim = c);

The string matching in the above examples are case-sensitive.  To perform a string match that ignores case, use a pattern (?i) in front of the string.

Example: Matches strings that contain "bob" regardless of case

c:[type == "http://contoso.com/email&quot;, Value =~ "(?i)bob"]
=> issue (claim = c);

For more advanced RegEx examples, view this article: http://social.technet.microsoft.com/wiki/contents/articles/16161.ad-fs-2-0-using-regex-in-the-claims-rule-language.aspx

Querying Attribute Stores

Active Directory is the default store created when AD FS 2.0 is installed.  SQL attribute stores and LDAP attribute stores can also be defined.  The condition statement remains the same, but the issuance statement changes depending on which attribute store is used.

SQL Attribute Stores

If user data is located in a SQL database, the Claim Rule Language can query the database and generate claims based on the information in the database.

Example: Claim rule using a SQL Attribute Store

c:[type == "http://contoso.com/emailaddress"%5D
=> issue (store = "Custom SQL Store", types = ("http://contoso.com/age&quot;, "http://contoso.com/purchasinglimit&quot;), query = "SELECT age,purchasinglimit FROM users WHERE email={0}",param = c.value);

This rule looks for an incoming http://contoso.com/emailaddress claim, then queries the SQL store Custom SQL Store for the age and purchasing limit associated with the value of the claim (email address).  It then issues two claims, http://contoso.com/age and http://contoso.com/purchasinglimit with the values stored in the SQL database.
As the example shows, multiple claims can be issued from a single rule.  The query is a standard transact-SQL statement.  The {0} variable is associated with the first param value.  If there are multiple param values, they will be associated in order {0}, {1}, {2}, etc.

LDAP Attribute Stores
If user data is located in a LDAP store, the Claim Rule Language can query it and generate claims based on the information in the store.
Example: Claim rule using an LDAP Attribute Store

c:[type == "http://contoso.com/emailaddress"%5D
=> issue (store = "Custom LDAP Store", types = ("http://contoso.com/age&quot;, "http://contoso.com/purchasinglimit&quot;), query = "mail={0};age,purchasinglimit", param = c.value);

The example shown is similar to the SQL attribute example.  The difference is the query parameter.

Format of an LDAP query in a claim rule: QUERY = "<query_filter>;<attributes>"

Read more about Attribute Stores here.

Links to Additional Content

There are many good articles that supplement the data in this article.

Advanced Topics

Here are some articles that go over more advanced topics:

</QUOTE SOURCE=”Understanding Claim Rule Language in AD FS 2.0 & Higher”>

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language | Leave a Comment »

(2014-02-10) Bare Minimum Acceptance Transform Rules For The Default Claims Provider Trusts In ADFS v3.0 (Update 1)

Posted by Jorge on 2014-02-10


In this blog post I wrote and concluded about the bare minimum acceptance transform rules for the default claims provider trust (Active Directory) in ADFS v3.0 (or ADFS 2012 R2). What I wrote is still correct, however, unfortunately the final conclusion is inaccurate. I do not understand what went wrong when I tested that. While both ADFS v2.0 and ADFS v2.1, at a minimum require the "Primary SID" claim, ADFS v3.0 does not only require the "Windows Account Name" claim as I stated in the previous post. It requires an additional claim, being the "UPN" claim, at a minimum. Continue reading to understand and see what goes wrong if any of the two required claims is missing.

To enable debug tracing, before trying this yourself see: (2014-02-05) Enabling Debug Tracing In ADFS v2.1 and v3.0

If you only have the following claims rule in the acceptance transform rules list of the Active Directory CP trust…

image

Figure 1: Only Using The Claims Rules For The "Windows Account Name" Claim

…and you access, for example, https://<FQDN Federation Service>/adfs/ls/IdPInitiatedSignOn, you will an error similar to the one below

image

Figure 2: The ADFS Error Page

Looking at the ADFS Admin Event Log you will something similar to the figure below.. Pay specific attention to the text "ID4250: The ClaimValue cannot be null". That means, the "UPN" claim is missing or does not have a value.

image

Figure 3: The Error "System.IO.InvalidDataException: ID4250: The ClaimValue Cannot Be Null" In The ADFS Admin Event Log

Looking at the ADFS Debug Tracing Event Log you will something similar to the figure below. Now it really tells you what’s wrong! Just like I said earlier.

image

Figure 4: The Error "Unable To Find Upn Claim In The Incoming Identity Or Claim Value Is Empty" In The ADFS Debug Tracing Event Log

Looking at the ADFS Debug Tracing Event Log you will something similar to the figure below, which is almost the same error as mentioned earlier.

image

Figure 5: The Error "Exception: ID4250: The ClaimValue Cannot Be Null" In The ADFS Debug Tracing Event Log

You will also see the following error, which is not really helpful.

image

Figure 6: The Error "Passive Pipeline Error" In The ADFS Debug Tracing Event Log

Now, if you only have the following claims rule in the acceptance transform rules list of the Active Directory CP trust…

image

Figure 7: Only Using The Claims Rules For The "UPN" Claim

…and you access, for example, https://<FQDN Federation Service>/adfs/ls/IdPInitiatedSignOn, you will an error similar to the one below

image

Figure 8: The ADFS Error Page

Looking at the ADFS Admin Event Log you will something similar to the figure below.. Pay specific attention to the text "SessionSecurityToken does not contain a single AnchorID claim". That means, the "Windows Account Name" claim is missing or does not have a value.

image

Figure 9: The Error "SessionSecurityToken Does Not Contain A Single AnchorID Claim" In The ADFS Admin Event Log

Looking at the ADFS Debug Tracing Event Log you will something similar to the figure below. It is telling you the same as before without really telling you what is actually wrong.

image

Figure 10: The Error "SessionSecurityToken Does Not Contain A Single AnchorID Claim" In The ADFS Debug Tracing Event Log

You will also see the following error, which is not really helpful.

image

Figure 11: The Error "Token Is Invalid" In The ADFS Debug Tracing Event Log

You will also see the following error, which is not really helpful.

image

Figure 12: The Error "Exception: MSIS7012: An Error Occurred While Processing The Request" In The ADFS Debug Tracing Event Log

You will also see the following error, which is not really helpful.

image

Figure 13: The Error "Passive Pipeline Error" In The ADFS Debug Tracing Event Log

Now, if you have both the following claims rule in the acceptance transform rules list of the Active Directory CP trust…

image

Figure 14: Only Using The Claims Rules For The The "Windows Account Name" Claim And The "UPN" Claim

…and you access, for example, https://<FQDN Federation Service>/adfs/ls/IdPInitiatedSignOn, you will an error similar to the one below

image

Figure 15: The ADFS SignIn Page

Voila it works! Smile

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language, Federation Trusts, Transform Rules | 4 Comments »

(2013-10-10) An ADFS Claims Rules Adventure By ASKDS

Posted by Jorge on 2013-10-10


The guys from the ASKDS Team Blog have written another great article about the Claims Rule Language in ADFS. Kudos and credits of course go to the writer of the post on the AskDS Team Blog. Other articles about the claims rules language can be found through the following links: (2011-10-24) AD FS 2.0 Claims Rule Language Primer From The ASKDS Team and (2013-06-15) AD FS 2.0 Claims Rule Language Primer From The ASKDS Team (Part 2)

SOURCE: An ADFS Claims Rules Adventure

<QUOTE SOURCE=”An ADFS Claims Rules Adventure”>

[Editor’s Note: This is a guest post from Steve Halligan, a Senior Premier Field Engineer.]

Notes from the Field

Customers can come up with some fairly complex requirements for access control. It can be a challenge to accommodate these requirements in an Office 365 world. The ADFS claims rule system in ADFS 2.0 UR1 provides some powerful options to implement these controls, and some limitations.

The requirements:

  1. No one shall access email via Outlook when off the corporate network
  2. Members of a specific security group may not use ActiveSync
  3. Members of a specific security group may not access OWA off the corporate network
  4. All OWA users must log in via a forms based login

It is important to note that the rule processing system always processes all rules. It is NOT a first match system. Because of this, the first rule is most always an “allow everything” rule followed by additional rules that block some access. Schematically, it could look like this:

  1. Allow everyone
  2. Block members of the group “Bigwigs” from access to OWA
  3. Allow the CEO access to OWA

When the CEO attempts to log in to OWA: Rule #1 allows him, Rule #2 blocks him, and then Rule #3 allows him. It is the state of the last matching rule that determine his final outcome. If the CIO were to attempt to log in, he would only match rules 1 and 2 and therefore would be blocked.

Requirement #1: No one shall access email via Outlook when off the corporate network

This is a fairly common requirement in a lot of enterprise Exchange environments. Security folks get a bit freaked out by the thought that a user could set up Outlook on a home machine. Meeting this challenge is pretty straightforward with ADFS 2.0 claims rules.

First, let’s review a bit how ADFS claims work in an Office 365 deployment. There are two flavors of ADFS claims requests: Active and Passive. When a claims request is created and submitted by the service (O365) it is an “active” request. This seems kind of counter-intuitive since you (the client) don’t have to do anything for these requests. The active/passive nature of the request refers to the participation of the _service_, not the client. Examples of O365 services that use active claims requests are Outlook 2007/2010 (RPC + HTTPS, EWS), Outlook 2011 for Mac (EWS), ActiveSync, Autodiscover and Lync Online.

A passive claims request is when the service sends you off to get the claim yourself. The main examples of this in O365 are OWA and Sharepoint Online. When you try to log in to OWA in O365 (and you are using federated identity) your browser is redirected to your ADFS endpoint. There you provide your credentials (either via a web form, basic authentication pop-up or integrated auth.), get your token and then return to OWA with your token in hand.

Back to the issue at hand: Blocking Outlook when client is not connected to the corporate network. To translate that into ADFS speak—We need to block active ADFS claims for the RPC+HTTPS and EWS services if the IP address doesn’t match a known set of corporate addresses.

Logically, the rule will look like this:

If {
     {ClientApplication is RPC Or ClientApplication is EWS}
    
AND
     ClaimType is Active
     AND
     ClientIPAddress is not in <corporate ip address list>

}
THEN DENY THE CLAIM

Now let’s translate that to ADFS Claim Rule language:

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy"%5D) &&
exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path&quot;, Value == "/adfs/services/trust/2005/usernamemixed"]) &&
exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application&quot;, Value == "Microsoft.Exchange.RPC|Microsoft.Exchange.WebServices"]) &&
NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip&quot;, Value =~ “<public NAT addresses>"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

Makes perfect sense, right? Oh…it doesn’t? Allow me to break it down:

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy"%5D)

The ‘Type’ x-ms-proxy exists. This simply means that the claim came through an ADFS Proxy server (or other compatible proxy). Note: we are not checking the ‘value’ for this type, just that the type exists.

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path&quot;, Value == "/adfs/services/trust/2005/usernamemixed"])

The type x-ms-endpoint-absolute-path exists and has a value of usernamemixed. This is the name of the endpoint for _Active_ ADFS Claims. Summary: This is an active ADFS claim.

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application&quot;, Value == "Microsoft.Exchange.RPC|Microsoft.Exchange.WebServices"])

ClientApplication is RPC or WebServices. We can use this ‘or’ (using the ‘|’ character) syntax to check the value field.

NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip&quot;, Value =~ “\b192\.168\.4\.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9])\b|\b10\.3\.4\.5\b"])

The value for the type x-ms-forwarded-client-ip has a value that DOES NOT MATCH the regular expression “<public NAT addresses>”. That brings up two important questions:

  1. Where does that “x-ms-forwarded-client-ip” come from and what values should I expect to see there?
  2. What does the format of the regular expression look like?

According to TechNet (http://technet.microsoft.com/en-us/library/hh526961(v=ws.10).aspx):

“This AD FS claim represents a “best attempt” at ascertaining the IP address of the user (for example, the Outlook client) making the request. This claim can contain multiple IP addresses, including the address of every proxy that forwarded the request. This claim is populated from an HTTP header that is currently only set by Exchange Online, which populates the header when passing the authentication request to AD FS.”

So, the value is going to be the border IP address that Exchange Online (EXO) sees for the client. That would be either the border firewall doing NAT/PAT or the border Proxy server. Exchange Online add this IP to the ADFS claim request. Perfect for our Outlook scenario here: Outlook attempt to connect to EXO, EXO builds up a claims request that includes the client IP and heads out to the ADFS endpoint to submit the request.

The second question is a bit easier (or perhaps a bit harder – regular expressions can get complicated) due to the fact that the regular expression format follows the general rules for regular expressions. The Internet is full of regular expression examples to filter IP addresses. For example, let’s say that your network has one block of addresses in use in a NAT pool: 192.168.4.0-192.168.4.255. You also have one satellite office with a single public IP address: 10.3.4.5. An expression you may use could be:

  • “\b192\.168\.4\.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9])\b|\b10\.3\.4\.5\b”

To break that down:

  • \b192\.168\.4\.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9])\b applies to the 192.168.4.0-255 network.
  • \b192\.168\.4\. matches 192.168.4.
  • [1-9] matches address ending in 1-9
  • [1-9][0-9] matches 10-99
  • 1[0-9][0-9] matches 100-199
  • 2[0-5][0-9] matches 200-259 (yeah…I know a few more than needed)
  • The ‘|’ represent “or”
  • \b10\.3\.4\.5\b applies to the 10.3.4.5 address.

These can get tricky. I recommend you use a regular expression verification tool and test.

Finally, if ALL of these conditions are true:

=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");

We deny the claim.

If any one of the elements of the rule evaluate to false, the entire rule is skipped. So, if the client IS coming from one of the addresses that match the regular expression, they do not match this rule.

Requirement #2: Members of a specific security group may not use ActiveSync

The previous example illustrated how to allow or block users based upon where they are. This is a simple example of how to block users based upon who they are.

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy"%5D) &&
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "Group SID value of allowed AD group"]) &&
exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application, Value == “Microsoft.Exchange.ActiveSync”])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

The new element here from the previous example is the “groupsid” type. Yeah, you need to dive into AD and hunt down the SID of the group in question. As is hinted by the “=~” operator, you could create a regular expression that would match more than one group. You could also use the “==” operator and the “|” to do a multiple “or” match.

That one was easy – sets us up well for the next. Which gets a bit…complicated.

Requirement #3: Members of a specific group may only use OWA on the corporate network

We built a rule above that did something very similar for Outlook, couldn’t we just add on or slightly alter that rule? Nope, we can’t. OWA login uses a passive ADFS claim, so the behavior is different. With Outlook, or other active claims, all requests come from O365 and land on the external ADFS proxy. To determine if a user is internal or external, we have to examine the “x-ms-forwarded-client-ip” value. With a passive claim request, like OWA, the client’s browser will be connecting directly to the ADFS endpoint (sts.contoso.com for example). So, we can control who gets in based upon where they are asking. If they ask the external proxies (and they are in the specified group) we say “no”. If they ask the internal ADFS servers we say “yes”.

image

Figure 1: Office 365 OWA Either From The Internal Network Or The External Network

The rule to accomplish this would look something like this:

exists([Type == http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy%5D)
&& exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "S-1-5-21-299502267-1364589140-1177238915-114465"])
&& exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path&quot;, Value == "/adfs/ls/"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

Line 1: Request went through a proxy AND

Line 2: User is a member of the specified group AND

Line 3: This is a passive claim to the /adfs/ls/ endpoint THEN

Line 4: Deny

If they hit the internal ADFS servers directly, line 1 would be false and they would be allowed in.

Requirement #4: Oh…your solution to #3 doesn’t work because we want everyone to use forms-based authentication

Well…shoot. Easy enough to fix, right? Send everyone to the ADFS proxy and add in a line to the above rule that specifies which client IP addresses are allowed. Something like:

exists([Type == http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy]) &&
exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid
", Value =~ "S-1-5-21-299502267-1364589140-1177238915-114465"]) &&
exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path
", Value == "/adfs/ls/"]) &&
NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip&quot;, Value =~ “\b192\.168\.4\.([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-5][0-9])\b|\b10\.3\.4\.5\b"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

I hope the blaring red font illustrates the point that this will not work. Why not? Scroll up a bit to the section on blocking Outlook based on IP address. Notice what fills in that x-ms-forwarded-client-ip? EXO. In this example we are dealing with _passive_ ADFS claims. EXO is not creating this claim—the user is hitting the ADFS login page directly. If you turn on this rule, everyone in the specified group will be blocked no matter where they are coming from. The x-ms-forwarded-client-ip type does not exist at all, so that line will evaluate to true. In order to make it false (and thereby stopping the deny rule from firing on someone) the element would need to exist AND the value need to match the regular expression.

If we can’t use the client’s source IP as a filter, how can we solve this problem?

The answer lies in an ADFS claim element that we have been checking all along, but not checking it fully. Let’s look at the debug log (for more on turning on debugging for ADFS see: http://technet.microsoft.com/en-us/library/adfs2-troubleshooting-configuring-computers(v=WS.10).aspx).

Event ID 151 AD FS 2.0 Tracing:

<Claims>
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname Value CONTOSO\USER1 ValueType http://www.w3.org/2001/XMLSchema#string Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY
ClaimType http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name Value CONTOSO\USER1 ValueType http://www.w3.org/2001/XMLSchema#string Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid Value S-1-5-21-3640651473-4051545122-2937135913-1136 ValueType http://www.w3.org/2001/XMLSchema#string Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid Value S-1-5-15 ValueType http://www.w3.org/2001/XMLSchema#string Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY Value S-1-5-11 Value S-1-5-2 Value S-1-5-32-545 Value S-1-1-0 Value S-1-5-21-3640651473-4051545122-2937135913-513
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid Value S-1-5-21-3640651473-4051545122-2937135913-513 ValueType http://www.w3.org/2001/XMLSchema#string Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod Value
http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password ValueType http://www.w3.org/2001/XMLSchema#string Issuer LOCAL AUTHORITY OriginalIssuer LOCAL AUTHORITY
ClaimType http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant Value 2012-04-19T17:32:41.459Z ValueType http://www.w3.org/2001/XMLSchema#dateTime Issuer AD AUTHORITY OriginalIssuer AD AUTHORITY
ClaimType http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy Value adfs01p ValueType http://www.w3.org/2001/XMLSchema#string Issuer CLIENT CONTEXT OriginalIssuer CLIENT CONTEXT
</Claims>

We have been checking for the existence of the x-ms-proxy element, but we haven’t looked into its value. The value identifies the name of the proxy server that the request passed through. What if we could tell if a user was internal or external based upon which proxy server they came through?

image

Figure 2: Leveraging Internal And External ADFS Proxies

With this change, internal OWA users will land on internal ADFS Proxy servers and external OWA users will land on external ADFS Proxy servers. That will allow us to add a rule like this:

exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy&quot;, Value =~ "\badfsp[0-9][0-9]\b"])
&& exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "S-1-5-21-299502267-1364589140-1177238915-114465"])
&& exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path&quot;, Value == "/adfs/ls/"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

Line 1: User is coming through an ADFS Proxy and that proxy has a name that matches ADFSP## AND

Line 2: User is in the specified group AND

Line 3: User is hitting the passive endpoint THEN

Line 4: Deny the claim

If a user in the specified group presents a claim to ADFS from outside the network, all elements of this rule will be true and the claim will be denied. If the same user is inside the network and is using one of the internal proxies, line 1 will be false (the proxy name will not match ADFSP##) and the claim will be allowed.

For illustration purposes, we can express the same thing in a slightly different way:

NOT exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy&quot;, Value =~ "\badfspi[0-9][0-9]\b"])
&& exists([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid&quot;, Value =~ "S-1-5-21-299502267-1364589140-1177238915-114465"])
&& exists([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path&quot;, Value == "/adfs/ls/"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny&quot;, Value = "true");

Line 1: The user is NOT coming through an ADFS Proxy that matches ADFSPI##

While you may not need to meet access control requirements this complex, I hope that these notes provide some enlightenment into the ADFS claim rule language.

Steve Halligan, PFE

</QUOTE SOURCE=”An ADFS Claims Rules Adventure”>

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language | 1 Comment »

(2013-10-09) Bare Minimum Acceptance Transform Rules For The Default Claims Provider Trusts In ADFS v3.0

Posted by Jorge on 2013-10-09


UPDATE: See a newer and more up-to-date and accurate version of this blog post here.

This post is about the bare minimum acceptance transform rules for the default claims provider trust (Active Directory) in ADFS v3.0. To read about the same topic in ADFS v2.0 see the following blog post: (2011-09-13) Bare Minimum Acceptance Transform Rules For The Default Claims Provider Trusts In ADFS v2.0

Right after installing ADFS v3.0, by default it will have ONE Claims Provider Trust configuration for AD as the one and only supported authentication store. ADFS v3.0, like ADFS v2.0, does not support any other authentication store besides AD. That Claims Provider Trust will also be configure with a default set of Acceptance Transform Rules as shown below in the picture.

image

Figure 1: Default List Of Acceptance Transform Rules For The Default Claims Provider Trust (AD) In ADFS v3.0

It is of course possible to adjust the default set of Acceptance Transform Rules by removing existing default rules and/or adding your own required rules. Assuming you would do such a thing, you could for example replace the default set of Acceptance Transform Rules, with the Acceptance Transform Rules as shown below. If you look carefully I removed the default Acceptance Transform Rules and put in my own Acceptance Transform Rules.

image

Figure 2: Custom List Of Acceptance Transform Rules For The Default Claims Provider Trust (AD) In ADFS v3.0

If I now try to access a Claims Based SharePoint site that I created and configured on SharePoint 2010, you would see something similar to what you would see below.

The default Home Realm Discovery window with the selection list. The logo you see is something that I configured. I  get to this screen because I have multiple Claims Provider Trusts configured, one for the default authentication store being AD and one for an IdP-STS at some other organization.

image

Figure 3: The Default Home Realm Discovery Page In ADFS v3.0 Using A Selection List Of The Configured Claims Provider Trusts (a.k.a. Identity Providers)

After selecting the one you see, which represents in this case the Claims Provider Trust for AD, and clicking “Continue To Sign In”, you will see something similar to the sign-in page for which I already filled in some credentials:

image

Figure 4: The Default Sign-In Web Page For Forms AuthN In ADFS v3.0 To Collect Credentials For Authentication

After clicking “Sign In”, you will see something similar to:

image

Figure 5: Some Error Stating Something Went Wrong

After errors like this, the next step is to check the ADFS Event Logs. Let’s try the Admin log first! Note the detailed information!

You should see something similar like:

Event ID 364…

image

Figure 6: Event ID 364 In the ADFS v3.0 Admin Event Log Stating The Session Security Token Does Not Contain A Single AnchorID Claim

Encountered error during federation passive request.

Additional Data

Protocol Name:
wsfed

Relying Party:
urn:app:sharepointclaimsappwap

Exception details:
Microsoft.IdentityServer.RequestFailedException: MSIS7012: An error occurred while processing the request. Contact your administrator for details. —> Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnIdentityInvalidException: SessionSecurityToken does not contain a single AnchorID claim.
   at Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnTokenHelper.ValidateToken(SessionSecurityToken token, SessionSecurityToken currentToken)
   at Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnTokenHelper.AddToken(SessionSecurityToken newToken)
   at Microsoft.IdentityServer.Web.Protocols.PassiveProtocolHandler.UpdateSingleSignOnTokenInContext(ProtocolContext context, SecurityToken ssoToken)
   — End of inner exception stack trace —
   at Microsoft.IdentityServer.Web.Protocols.PassiveProtocolHandler.UpdateSingleSignOnTokenInContext(ProtocolContext context, SecurityToken ssoToken)
   at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.BuildSsoSecurityToken(WSFederationSignInContext context, SecurityToken securityToken, SecurityToken deviceSecurityToken, SecurityToken& ssoSecurityToken)
   at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.BuildSignInResponseCoreWithSecurityToken(WSFederationSignInContext context, SecurityToken securityToken, SecurityToken deviceSecurityToken)
   at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.BuildSignInResponse(WSFederationSignInContext federationPassiveContext, SecurityToken securityToken, SecurityToken deviceSecurityToken)
   at Microsoft.IdentityServer.Web.Protocols.WSFederation.WSFederationProtocolHandler.Process(ProtocolContext context)
   at Microsoft.IdentityServer.Web.PassiveProtocolListener.ProcessProtocolRequest(ProtocolContext protocolContext, PassiveProtocolHandler protocolHandler)
   at Microsoft.IdentityServer.Web.PassiveProtocolListener.OnGetContext(WrappedHttpListenerContext context)

Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnIdentityInvalidException: SessionSecurityToken does not contain a single AnchorID claim.
   at Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnTokenHelper.ValidateToken(SessionSecurityToken token, SessionSecurityToken currentToken)
   at Microsoft.IdentityServer.Web.SessionTokenManager.SingleSignOnTokenHelper.AddToken(SessionSecurityToken newToken)
   at Microsoft.IdentityServer.Web.Protocols.PassiveProtocolHandler.UpdateSingleSignOnTokenInContext(ProtocolContext context, SecurityToken ssoToken)

"Does not contain a single AnchorID claim"????? Oh yeah, wait! In ADFS v2.0 you were required to have the objectSID (a.k.a. the PrimarySID) as the minimum claim. So let’s add that!

image

Figure 7: Custom List Of Acceptance Transform Rules For The Default Claims Provider Trust (AD) In ADFS v3.0, But NOW With The Primary SID Claim

So, let’s try again!

No good, as I got the same error as shown in figure 5 and 6. Hey something changed here!

In a previous blog post "(2013-10-07) Restoring The Default Acceptance Transform Rules For The AD CP Trust In ADFS v3.0" I explain how to restore the default list of Acceptance Transform Rules for the default Claims Provider Trust (AD) in ADFS v3.0 if it is broken and you cannot get it to work anymore. It is really easy to “restore” the default list of Acceptance Transform Rules.

However, after some testing I found out that ADFS v3.0 requires AT LEAST one identifier claim that allows an authenticated user to request a security token. In ADFS v2.0, the identifier (a.k.a. the AnchorID Claim) was the "Primary SID" claim, but in ADFS v3.0 that was changed to the "Windows Account Name" claim!!!

image

Figure 8: The Bare Minimum Acceptance Transform Rules List Required For ADFS v3.0 To Issue A Security Token

AND…. there must be only one "Windows Account Name" claim. There should NOT be multiple values for that claim, either passing through or extracted by an LDAP query as shown below.

image

Figure 9: Custom List Of Acceptance Transform Rules For The Default Claims Provider Trust (AD) In ADFS v3.0, But NOW With MULTIPLE "Windows Account Name" Claims

If you have multiple "Windows Account Name" claims, which resulted from claims rules as shown in figure 9, it will not work and throw similar errors as shown in figure 5 and 6. Read it carefully, It clearly states: "SessionSecurityToken does not contain a single AnchorID claim". SINGLE, not multiple claims for the AnchorID claim, which is the "Windows Account Name" claim!

So in my scenario having the following claims rules worked like a charm!

image

Figure 10: Combination Of Acceptance Transform Rules From Figure 2 And Figure 8

Now trying to access my claims based SharePoint web site again will result in the following:

image

Figure 11: Successful Access To A Claims Based SharePoint Web Site Hosted SharePoint 2010 That Includes A Custom Web Part To Show The Claims Processed By SharePoint 2010

So……to summarize

For even ADFS v3.0 to be able to issue a security token, the Acceptance Transform Rule List Must AT LEAST have the claim rule specified as shown in figure 8! The value for the "Windows Account Name" claim must be in the format <NetBIOS Domain Name>\<sAMAccountName> (e.g. ADCORP\ADM.ROOT) as shown in figure 11. When you upgrade, or better migrate from ADFS v2.0 to ADFS v3.0 you need to be aware of this change!

UPDATE: See a newer and more up-to-date and accurate version of this blog post here.

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language, Federation Trusts, Transform Rules | 2 Comments »

(2013-10-07) Restoring The Default Acceptance Transform Rules For The AD CP Trust In ADFS v3.0

Posted by Jorge on 2013-10-07


In the following post, you can see how to restore the default acceptance transform rules for Active Directory claims provider trust in either ADFS v2.0 (on W2K8 or W2K8R2) or ADFS v2.1 (on W2K12): AD FS 2.0: How to Restore the Default Acceptance Transform Rules for the Active Directory Claims Provider Trust. The only difference between W2K8(R2) and W2K12 is that in W2K8(R2) you need to load a PowerShell snap-in (Add-PSSnapin Microsoft.Adfs.PowerShell) and in W2K12 (and higher) you need to load a PowerShell module (Import-Module ADFS)

In this post you can see how to restore the default acceptance transform rules for Active Directory claims provider trust in ADFS v3.0 (on W2K12R2). Has the procedure changed? No not really. Instead of a snap-in you now need to load a module and the default acceptance transform rules have changed a bit. For completeness I have described the procedure in full in this blog post.

So if you have modified the default acceptance transform rules for the Active Directory claims provider trust in ADFS v3.0, and you want to restore the defaults you can use this procedure. You need to perform this procedure on an ADFS STS server with write access to the ADFS configuration database. When using SQL you can use any ADFS STS server, but when using WID you must use the primary ADFS STS server.

[1] Copy the following text into a file and save that file as C:\TEMP\CP_ActiveDirectory_AcceptanceTransformRules_Default.txt

@RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Windows account name claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Name claims" c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Primary SID claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Group SID claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Primary group SID claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Deny only group SID claims" c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Deny only primary SID claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Deny only primary group SID claims" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all Enhanced Key Usage claims" c:[Type == "http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c); @RuleTemplate = "PassThroughClaims" @RuleName = "Pass through all UPN claims" c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn", Issuer =~ "^(AD AUTHORITY|SELF AUTHORITY|LOCAL AUTHORITY)$"] => issue(claim = c);

[2] Open a PowerShell Command Prompt window

[3] Execute the following commands:

[a] Import-Module ADFS

[b] Set-AdfsClaimsProviderTrust -TargetName "Active Directory" -AcceptanceTransformRulesFile "C:\TEMP\CP_ActiveDirectory_AcceptanceTransformRules_Default.txt"

[4] To verify your changes:

[a] Open the AD FS Management MMC

[b] Navigate to the node called "Trust Relationships" and select the sub-node "Claims Provider Trusts"

[c] Right-click the "Active Directory" CP trust and select "Edit Claim Rules"

You should now see something similar to:

image

Figure 1: Default List Of Acceptance Transform Rules For The Default Claims Provider Trust (AD) In ADFS v3.0

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language, Federation Trusts, Transform Rules | 2 Comments »

(2013-06-15) AD FS 2.0 Claims Rule Language Primer From The ASKDS Team (Part 2)

Posted by Jorge on 2013-06-15


The guys from the ASKDS Team Blog have written a great follow-up article about the Claims Rule Language in ADFS v2.x. Kudos and credits of course go to the writer of the post on the AskDS Team Blog. The first part can be found through the following link: (2011-10-24) AD FS 2.0 Claims Rule Language Primer From The ASKDS Team

SOURCE: AD FS 2.0 Claims Rule Language Primer (Part 2)

<QUOTE SOURCE=”AD FS 2.0 Claims Rule Language Primer (Part 2)”>

Hello, Joji Oshima here to dive deeper into the Claims Rule Language for AD FS. A while back I wrote a getting started post on the claims rule language in AD FS 2.0. If you haven’t seen it, I would start with that article first as I’m going to build on the claims rule language syntax discussed in that earlier post. In this post, I’m going to cover more complex claim rules using Regular Expressions (RegEx) and how to use them to solve real world issues.

An Introduction to Regex

The use of RegEx allows us to search or manipulate data in many ways in order to get a desired result. Without RegEx, when we do comparisons or replacements we must look for an exact match. Most of the time this is sufficient but what if you need to search or replace based on a pattern? Say you want to search for strings that simply start with a particular word. RegEx uses pattern matching to look at a string with more precision. We can use this to control which claims are passed through, and even manipulate the data inside the claims.

Using RegEx in searches

Using RegEx to pattern match is accomplished by changing the standard double equals "==" to "=~" and by using special metacharacters in the condition statement. I’ll outline the more commonly used ones, but there are good resources available online that go into more detail. For those of you unfamiliar with RegEx, let’s first look at some common RegEx metacharacters used to build pattern templates and what the result would be when using them.

Symbol Operation Example Rule
^ Match the beginning of a string

c:[type == "http://contoso.com/role&quot;, Value =~ "^director"]

=> issue (claim = c);

Pass through any role claims that start with "director"

$ Match the end of a string

c:[type == "http://contoso.com/email&quot;, Value =~ "contoso.com$"]

=> issue (claim = c);

Pass through any email claims that end with "contoso.com"

| OR

c:[type == "http://contoso.com/role&quot;, Value =~ "^director|^manager"]

=> issue (claim = c);

Pass through any role claims that start with "director" or "manager"

(?i) Not case sensitive

c:[type == "http://contoso.com/role&quot;, Value =~ "(?i)^director"]

=> issue (claim = c);

Pass through any role claims that start with "director" regardless of case

x.*y "x" followed by "y"

c:[type == "http://contoso.com/role&quot;, Value =~ "(?i)Seattle.*Manager"]

=> issue (claim = c);

Pass through any role claims that contain "Seattle" followed by "Manager" regardless of case.

+ Match preceding character

c:[type == "http://contoso.com/employeeId&quot;, Value =~ "^0+"]

=> issue (claim = c);

Pass through any employeeId claims that contain start with at least one "0"

* Match preceding character zero or more times Similar to above, more useful in RegExReplace() scenarios.

Table 1: RegEx Metacharacters

Using RegEx in string manipulation

RegEx pattern matching can also be used in replacement scenarios. It is similar to a "find and replace", but using pattern matching instead of exact values. To use this in a claim rule, we use the RegExReplace() function in the value section of the issuance statement.

The RegExReplace() function accepts three parameters:

  1. The first is the string in which we are searching.
    1. We will typically want to search the value of the incoming claim (c.Value), but this could be a combination of values (c1.Value + c2.Value).
  2. The second is the RegEx pattern we are searching for in the first parameter
  3. The third is the string value that will replace any matches found.

Example:

c:[type == "http://contoso.com/role"%5D
=> issue (Type = "http://contoso.com/role&quot;, Value = RegExReplace(c.Value, "(?i)director", "Manager");

Pass through any role claims. If any of the claims contain the word "Director", RegExReplace() will change it to "Manager". For example, "Director of Finance" would pass through as "Manager of Finance".

Real World Examples

Let’s look at some real world examples of regular expressions in claims rules.

# Problem 1:

We want to add claims for all group memberships, including distribution groups.

# Solution:

Typically, group membership is added using the wizard and selecting Token-Groups Unqualified Names and map it to the Group or Role claim. This will only pull security groups, not distribution groups, and will not contain Domain Local groups.

Figure 1: Retrieving Groups From AD And Sending Claims Out For Each

<COMMENT BY JORGE>

Using “memberOf”, you:

  • Cannot get nested group memberships
  • Can only get direct group memberships (security groups, distribution groups, universal groups, global groups, domain local groups)
  • Get the groups in DN format

Using “Token-Groups – Unqualified Names” or “Token-Groups – Qualified By Domain Name” or “Token-Groups – Qualified By Long Domain Name”, you:

  • Can get direct and nested group memberships
  • Can get security groups
  • Cannot get distribution groups
  • Can get universal groups, global groups
  • Cannot get domain local groups
  • Get the groups in naming format, with or with the (long) domain name (depends on which token groups was used)

</COMMENT BY JORGE>

We can pull from memberOf, but that will give us the entire distinguished name, which is not what we want. One way to solve this problem is to use three separate claim rules and use RegExReplace() to remove unwanted data.

Phase 1: Pull memberOf, add to working set "phase 1"

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]
=> add(store = "Active Directory", types = ("http://test.com/phase1&quot;), query = ";memberOf;{0}", param = c.Value);

Example: "CN=Group1,OU=Users,DC=contoso,DC=com" is put into a phase 1 claim.

Phase 2: Drop everything after the first comma, add to working set "phase 2"

c:[Type == "http://test.com/phase1"%5D
=> add(Type = "http://test.com/phase2&quot;, Value = RegExReplace(c.Value, ",[^\n]*", ""));

Example: We process the value in the phase 1 claim and put "CN=Group1" into a phase 2 claim.

Digging Deeper: RegExReplace(c.Value, ",[^\n]*", "")

  • c.Value is the value of the phase 1 claim. This is what we are searching in.
  • ",[^\n]*" is the RegEx syntax used to find the first comma, plus everything after it
  • "" is the replacement value. Since there is no string, it effectively removes any matches.

Phase 3: Drop CN= at the beginning, add to outgoing claim set as the standard role claim

c:[Type == "http://test.com/phase2"%5D

=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role&quot;, Value = RegExReplace(c.Value, "^CN=", ""));

Example: We process the value in phase 2 claim and put "Group1" into the role claim

Digging Deeper: RegExReplace(c.Value, "^CN=", "")

  • c.Value is the value of the phase 1 claim. This is what we are searching in.
  • "^CN=" is the RegEx syntax used to find "CN=" at the beginning of the string.
  • "" is the replacement value. Since there is no string, it effectively removes any matches.

# Problem 2:

We need to compare the values in two different claims and only allow access to the relying party if they match.

# Solution:

In this case we can use RegExReplace(). This is not the typical use of this function, but it works in this scenario. The function will attempt to match the pattern in the first data set with the second data set. If they match, it will issue a new claim with the value of "Yes". This new claim can then be used to grant access to the relying party. That way, if these values do not match, the user will not have this claim with the value of "Yes".

c1:[Type == "http://adatum.com/data1"%5D &&

c2:[Type == "http://adatum.com/data2"%5D

=> issue(Type = "http://adatum.com/UserAuthorized&quot;, Value = RegExReplace(c1.Value, c2.Value, "Yes"));

Example: If there is a data1 claim with the value of "contoso" and a data2 claim with a value of "contoso", it will issue a UserAuthorized claim with the value of "Yes". However, if data1 is "adatum" and data2 is "fabrikam", it will issue a UserAuthorized claim with the value of "adatum".

Digging Deeper: RegExReplace(c1.Value, c2.Value, "Yes")

  • c1.Value is the value of the data1 claim. This is what we are searching in.
  • c2.Value is the value of the data2 claim. This is what we are searching for.
  • "Yes" is the replacement value. Only if c1.Value & c2.Value match will there be a pattern match and the string will be replaced with "Yes". Otherwise the claim will be issued with the value of the data1 claim.

# Problem 3:

Let’s take a second look at potential issue with our solution to problem 2. Since we are using the value of one of the claims as the RegEx syntax, we must be careful to check for certain RegEx metacharacters that would make the comparison mean something different. The backslash is used in some RegEx metacharacters so any backslashes in the values will throw off the comparison and it will always fail, even if the values match.

# Solution:

In order to ensure that our matching claim rule works, we must sanitize the input values by removing any backslashes before doing the comparison. We can do this by taking the data that would go into the initial claims, put it in a holding attribute, and then use RegEx to strip out the backslash. The example below only shows the sanitization of data1, but it would be similar for data2.

Phase 1: Pull attribute1, add to holding attribute "http://adatum.com/data1holder&quot;

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]

=> add(store = "Active Directory", types = ("http://adatum.com/data1holder&quot;), query = ";attribute1;{0}", param = c.Value);

Example: The value in attribute 1 is "Contoso\John" which is placed in the data1holder claim.

Phase 2: Strip the backslash from the holding claim and issue the new data1 claim

c:[Type == "http://adatum.com/data1holder&quot;, Issuer == "AD AUTHORITY"]

=> issue(type = "http://adatum.com/data1&quot;, Value = RegExReplace(c.Value,"\\","");

Example: We process the value in the data1holder claim and put "ContosoJohn" in a data1 claim

Digging Deeper: RegExReplace(c.Value,"\\","")

  • c.Value is the value of the data1 claim. This is what we are searching in.
  • "\\" is considered a single backslash. In RegEx, using a backslash in front of a character makes it a literal backslash.
  • "" is the replacement value. Since there is no string, it effectively removes any matches.

An alternate solution would be to pad each backslash in the data2 value with a second backslash. That way each backslash would be represented as a literal backslash. We could accomplish this by using RegExReplace(c.Value,"\\","\\") against a data2 input value.

# Problem 4:

Employee numbers vary in length, but we need to have exactly 9 characters in the claim value. Employee numbers that are shorter than 9 characters should be padded in the front with leading zeros.

# Solution:

In this case we can create a buffer claim, join that with the employee number claim, and then use RegEx to use the right most 9 characters of the combined string.

Phase 1: Create a buffer claim to create the zero-padding
=> add(Type = "Buffer", Value = "000000000");

Phase 2: Pull the employeeNumber attribute from Active Directory, place it in a holding claim

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]

=> add(store = "Active Directory", types = ("ENHolder"), query = ";employeeNumber;{0}", param = c.Value);

Phase 3: Combine the two values, then use RegEx to remove all but the 9 right most characters.

c1:[Type == "Buffer"]

&& c2:[Type == "ENHolder"]

=> issue(Type = "http://adatum.com/employeeNumber&quot;, Value = RegExReplace(c1.Value + c2.Value, ".*(?=.{9}$)", ""));

Digging Deeper: RegExReplace(c1.Value + c2.Value, ".*(?=.{9}$)", "")

  • c1.Value + c2.Value is the employee number padded with nine zeros. This is what we are searching in.
  • ".*(?=.{9}$)" represents the last nine characters of a string. This is what we are searching for. We could replace the 9 with any number and have it represent the last "X" number of characters.
  • "" is the replacement value. Since there is no string, it effectively removes any matches.

# Problem 5:

Employee numbers contain leading zeros but we need to remove those before sending them to the relying party.

# Solution:

In this case we can pull employee number from Active Directory and place it in a holding claim, then use RegEx to use the strip out any leading zeros.

Phase 1: Pull the employeeNumber attribute from Active Directory, place it in a holding claim

c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname&quot;, Issuer == "AD AUTHORITY"]

=> add(store = "Active Directory", types = ("ENHolder"), query = ";employeeNumber;{0}", param = c.Value);

Phase 2: Take the value in ENHolder and remove any leading zeros.

c:[Type == "ENHolder"]

=> issue(Type = "http://adatum.com/employeeNumber&quot;, Value = RegExReplace(c.Value, "^0*", ""));

Digging Deeper: RegExReplace(c.Value, "^0*", "")

  • c1.Value is the employee number. This is what we are searching in.
  • "^0*" finds any leading zeros. This is what we are searching for. If we only had ^0 it would only match a single leading zero. If we had 0* it would find any zeros in the string.
  • "" is the replacement value. Since there is no string, it effectively removes any matches.

Conclusion

As you can see, RegEx adds powerful functionality to the claims rule language. It has a high initial learning curve, but once you master it you will find that there are few scenarios that RegEx can’t solve. I would highly recommend searching for an online RegEx syntax tester as it will make learning and testing much easier. I’ll continue to expand the TechNet wiki article so I would check there for more details on the claims rule language.

Understanding Claim Rule Language in AD FS 2.0

AD FS 2.0: Using RegEx in the Claims Rule Language

Regular Expression Syntax

AD FS 2.0 Claims Rule Language Primer

Until next time,

Joji "Claim Jumper" Oshima

</QUOTE SOURCE=”AD FS 2.0 Claims Rule Language Primer (Part 2)”>

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language | 2 Comments »

(2011-10-24) AD FS 2.0 Claims Rule Language Primer From The ASKDS Team

Posted by Jorge on 2011-10-24


The guys from the ASKDS Team Blog have written a great article about the Claims Rule Language in ADFS v2.x. Kudos and credits of course go to the writer of the post on the AskDS Team Blog

SOURCE: AD FS 2.0 Claims Rule Language Primer

<QUOTE SOURCE=”AD FS 2.0 Claims Rule Language Primer”>

Hi guys, Joji Oshima here again. On the Directory Services team, we get questions regarding the Claims Rule Language in AD FS 2.0 so I would like to go through some of the basics. I’ve written this article for those who have a solid understanding of Claims-based authentication.

If you would like to read up on the fundamentals first, here are some good resources.

Claims Rules follow a basic pipeline. The rules define which claims are accepted, processed, and eventually sent to the relying party. You define claims rules as a property of the Claims Provider Trust (incoming) and the Relying Party Trust (outgoing).

image

Figure 1: Basic Flowchart For The Claims Pipeline Taken From TechNet

There is also an authorization stage checks if the requestor has access to receive a token for the relying party. You can choose to allow all incoming claims through by setting the Authorization Rules to Permit All. Alternately, you could permit or deny certain users based on their incoming claim set. You can read more about authorization claim rules here and here.

You can create the majority of claims issuance and claims transformations using a Claim Rule Template in AD FS 2.0 Management console, but there are some situations where a custom rule is the only way to get the results you need. For example, if you want to combine values from multiple claims into a single claim, you will need to write a custom rule to accomplish that. To get started, I would recommend creating several rules through the Claim Rule Templates and view the rule language generated. Once you save the template, you can click the View Rule Language button from the Edit Rule window to see how the language works.

image

Figure 2: Configuring A Claim Rule Using The “Transform An Incoming Claim” Rule Template

image

Figure 3: Viewing The Actual Claims Rule Language

In the screenshot above, the rule translates as follows:

If (there is an incoming claim that matches the type "http://contoso.com/department&quot;)

Then (issue a claim with the type "http://adatum.com/department&quot;, using the Issuer, Original Issuer, Value, and ValueType of the incoming claim)

The claims "http://contoso.com/department&quot; and "http://adatum.com/department&quot; are URIs. These claims can be in the URN or HTTP format. The HTTP format is NOT a URL and does not have to specifically link to actual content on the Internet or intranet.

Claims Rule Language Syntax

Typically, the claims rule language is structured similarly to an “if statement” in many programming languages.

If (condition is true)

Then (issue a claim with this value)

What this says is “if a condition is true, issue this claim”. A special operator “=>” separates the condition from the issuance statement and a semicolon ends the statement.

Condition statement => issuance statement;

Review some of the claims you created and look at the structure. See if you can pick out each part. Here is the one we looked at in the first section. Let’s break it down in to the basic parts.

image

Figure 4: Viewing The Actual Claims Rule Language

The “if statement” condition:

c:[Type == http://contoso.com/department%5D

The special operator:

=>

The issuance statement:

issue(Type = "http://adatum.com/department&quot;, Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);

For each rule defined, AD FS checks the input claims, evaluates them against the condition, and issues the claim if the condition is true. You probably notice the variable “C” in the syntax. Think of “C” as an incoming claim that you can check conditions against, and use values from it to add to an outgoing claim. In this example, we are checking if there is an incoming claim that has a type that is “http://contoso.com/department”. We also use the values in this claim to assign the value of Issuer, OriginalIssuer, Value, and ValueType to the outgoing claim.

REMARK: There are exceptions to this that are discussed later (using ADD instead of ISSUE and issuing a claim without a condition statement).

Issue a claim to everyone

In the Claims Rule Language, the condition part is optional. Therefore, you can choose to issue or add a claim regardless of what claims are incoming. To do this, start with the special operator “=>”.

Syntax:

=> issue(type = "http://contoso.com/partner&quot;, value = "Adatum");

This syntax will issue a claim type “http://contoso.com/partner” with a value of “Adatum”

You could set similar rules for each Claims Provider Trust so that the Relying Party (or application) can know where the user came from.

Using a Single Condition:

In this example, we will look at a single condition statement. A basic claim rule checks to see if there is an incoming claim with a certain type and if so, issue a claim.

c:[Type == "http://contoso.com/role"%5D

=> issue(claim = c);

This syntax will check to see if there is an incoming claim with the type “http://contoso.com/role” and, if so, issue the exact same claim going out.

You can create this claim rule using the GUI. Choose the template named “Pass Through or Filter an Incoming Claim” and choose the appropriate incoming claim type.

image

Figure 5: Configuring A Claim Rule Using The “Pass Through or Filter an Incoming Claim” Rule Template

You may also check for multiple values within your condition statement. For example, you can check and see if there is an incoming claim with a specific value. In the following example, we will check for an incoming claim with the type “http://contoso.com/role” that has the value of “Editors” and, if so, issue the exact same claim.

c:[Type == "http://contoso.com/role&quot;, Value=="Editors"]

=> issue(claim = c);

You can create this claim rule using the GUI as well. Choose “Pass Through or Filter an Incoming Claim”, choose the appropriate incoming claim type, select “Pass though only a specific claim value”, then enter the appropriate value.

image

Figure 6: Configuring A Claim Rule Using The “Pass Through or Filter an Incoming Claim” Rule Template

Using Multiple Conditions:

Say you want to issue a claim only if the user has an Editor and has an Email claim and, if so, issue the Editor Role claim. To have multiple conditions, we will use multiple “C” variables. We will join the two condition statements with the special operator “&&”.

c1:[Type == "http://contoso.com/role&quot;, Value=="Editors"] &&

c2:[Type == "http://contoso.com/email"%5D

=> issue(claim = c1);

The first condition (c1) checks to see if you have an incoming role claim with the value of Editors. The second condition (c2) checks to see if there is an incoming email claim. If both conditions are met, it will issue an outgoing claim identical to the incoming c1 claim.

Combining Claim Values:

Say you want to join information together from multiple incoming claims to form a single outgoing claim. The following example will check for an incoming claim type of "http://contoso.com/location&quot; and “http://contoso.com/role”. If it has both, it will issue a new claim, “http://contoso.com/targeted”, combining the two values.

c1:[Type == "http://contoso.com/location"%5D &&

c2:[Type == "http://contoso.com/role"%5D

=> issue(Type="http://contoso.com/targeted&quot;, Value=c1.value+" "+c2.value);

The resulting value is the value of the first claim (c1), plus a space, plus the value of the second claim (c2). You can combine static strings with the values of the claims using the special operator “+”. The example below shows a sample set of incoming claims, and the resulting output claim.

Example Incoming Claims:

"http://contoso.com/location&quot; is "Seattle"

"http://contoso.com/role&quot; is "Editor"

Example Outgoing Claim:

"http://contoso.com/targeted&quot; is "Seattle Editor"

Using ADD instead of ISSUE:

As mentioned in an earlier section, you can ADD a claim instead of ISSUE a claim. You may be wondering what the difference between these two statements are. Using the ADD command instead of the ISSUE command will add a claim to the incoming claim set. This will not add the claim to the outgoing token. Use this for adding placeholder data to use in subsequent claims rules.

image

Figure 7: Processing The Claims Rules Illustration Taken From TechNet

In figure 7 you can see that the first rule adds a role claim with the value of Editor. It then uses this newly added claim to create a greeting claim. Assuming these are the only two rules, the outgoing token will only have a greeting claim, not a role claim.

I’ve outlined another example below.

Sample Rule 1:

c:[Type == "http://contoso.com/location&quot;, Value=="NYC"]

=> add(Type = "http://contoso.com/region&quot;, Value = "East");

Sample Rule 2:

c:[Type == "http://contoso.com/location&quot;, Value=="LAX"]

=> add(Type = "http://contoso.com/region&quot;, Value = "West");

Sample Rule 3:

c1:[Type == "http://contoso.com/location"%5D &&

c2:[Type == "http://contoso.com/region"%5D

=> issue(Type="http://contoso.com/area&quot;, Value=c1.value+" "+c2.value);

In this example, we have two rules that ADD claims to the incoming claim set, and one that issues a claim to the outgoing claim set. This will add a region claim to the incoming claim set and use that to create combine the values to create an area claim. The ADD functionality is very useful with the next section for aggregate functions.

Using aggregate functions (EXISTS and NOT EXISTS):

Using aggregate functions, you can issue or add a single output claim instead of getting an output claim for each match. The aggregate functions in the Claims Rule Language are EXISTS and NOT EXISTS.

Say we want to use the location claim, but not all users have it. Using NOT EXISTS, we can add a universal location claim if the user does not have one.

In Sample Rule 1, we will add a location claim with the value of “Unknown” if the user does not have a location claim. In Sample Rule 2, we will use that value to generate the “http://contoso.com/targeted” claim.

Sample Rule 1:

NOT EXISTS([Type == "http://contoso.com/location"%5D)

=> add(Type = "http://contoso.com/location&quot;, Value = "Unknown");

Sample Rule 2:

c1:[Type == "http://contoso.com/location"%5D &&

c2:[Type == "http://contoso.com/role"%5D

=> issue(Type="http://contoso.com/targeted&quot;, Value=c1.value+" "+c2.value);

This way, users without the "http://contoso.com/location&quot; claim can still get the "http://contoso.com/targeted&quot; claim.

Claims Rule Language, beyond this post:

There is more you can do with the Claims Rule Language that goes beyond the scope of this blog post. If you would like to dig deeper by using Custom Attribute Stores and using Regular Expressions in the language, I’ve put up a TechNet Wiki article that contains these advanced topics and other sample syntax. In addition, some other articles may help with these topics.

Conclusion:

Creating custom rules with the Claims Rule Language gives you more flexibility over the standard templates. Syntax familiarization takes a while, but with some practice, you should be able to write custom rules in no time. Start by writing custom rules instead of using the templates in your lab environment and build on those.

Joji “small claims court” Oshima

</QUOTE SOURCE=”AD FS 2.0 Claims Rule Language Primer”>

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Claims Rule Language | 3 Comments »

 
%d bloggers like this: