ADFS Rule Language

 

Overview

Claim Sets

* Incoming claim set

* Outgoing claim set

Claim Rules

Claim Rules For Claim Providers

* Acceptance Rules: accepting claims from claim provider trust

Claim Rules For Relying Parties

* Issuance Tranform Rules: issuing claims for relying party trust, e.g. send email as name id claim

* Issuance Authorization Rules: authorizing claims for relying party trust, e.g. permit all users

* Delegation Authorization Rules: impersonate users with an privileged account

Claim Rule Language

Syntax

* Each rule contains two parts:
– Condition statement (if true )
– Issuance statement (then issue/add claims)

* Operators:
Equal: ==
Assign: =
Regular Expression: =~

* For example:

# if incoming claim set contains
#   claim type "http://contoso.com/department"
c:[Type == "http://contoso.com/department"]
# then issue
#   claim type "http://adatum.com/department" 
#   with the same value as incoming claim
=> issue[Type = "http://adatum.com/department", Value = c.Value);

Condition Statements

* Properties that can be checked:
– Type
– Value
– Issuer
– OriginalIssuer
– ValueType

* Examples:

# Check for Type only
c:[Type == "http://contoso.com/department"]
=> issue[Type = "http://adatum.com/department", Value = c.Value);

# Check for both Type and Value
c:[Type == "http://contoso.com/department" Value == "Sales"]
=> issue[Type = "http://adatum.com/department", Value = c.Value);

# Blank or missing condition statement means always true
=> issue[Type = "http://adatum.com/department", Value = c.Value);

Issuance Statements

* Two types:
– Add: adds claim to incoming claim set
– Issue: adds cliam to outgoing claim set

* Issue examples:

# Issue a claim with
#   type: "http://adatum.com/department"
#   value: "Sales"
=> issue[Type = "http://adatum.com/department", Value = "Sales");

# Issue incoming claim as is
c:[Type == "http://contoso.com/department"]
=> issue(claim = c);

# Issue all available claims
c:[] => issue(claim = c);

* Add examples:

# Add a claim with
#   type: "http://adatum.com/department"
#   value: "Sales"
=> add[Type = "http://adatum.com/department", Value = "Sales");

Multiple Conditions

* AND multiple conditions: &&

* OR multiple conditions: create separate claims

* Combine multiple values: +

* Examples:

# If incoming claim set contains both
#  claim type: "http://contoso.com/location"
#  and cliam type: "http://contoso.com/role"
c1:[Type == "http://contoso.com/location"] &&
c2:[Type == "http://contoso.com/role"]
# Then issue a claim
#  type: "http://adatum.com/role"
#  value: c1.Value + " " + c2.Value
=> issue(Type = "http://adatum.com/role", Value = c1.Value + " " + c2.Value);

Aggregate Functions

EXISTS

* Check for the existence of a claim type without regard to number of times it finds

* Examples:

# Check for existence, i.e. any occurrance, of emailaddress claim type
EXISTS([type == "http://contoso.com/emailaddress"])
# Then issue a single role claim type
=> issue(type = "http://contoso.com/role", value="Exchange User");

NOT EXISTS

* Check for the abstence of a claim type

* Examples:

# If location claim type does not exist
NOT EXISTS([type == "type://contoso.com/location"])
# Then add location claim type with value of "Unknown"
=> add(type = "http://contoso/location", vlaue = "Unknown");

# If use does not have a group claim with value "ADFSUser"
NOT EXISTS([type == "http://contoso.com/group", Value =~ "^(?!)ADFSUser"])
$ Then deny user access
=> issue(type = "http://schemas.microsoft.com/authorization/claims/deny", value="DenyUsersWithClaim");

COUNT

* Count the number of occurance of a claim type

* Examples:

# If you have multiple email address claims
COUNT([type == "http://contoso.com/emailaddress"]) >= 2
# Then issue a MultipleEmails claim with value true
=> issue(type="http://contoso.com/Multipleemails", value="True");

RegEx

* Use =~ operator for regex, e.g.

# If role claim type value starts with CEO
c:[type == "http://contoso.com/role", Value =~ "^CEO"]
# Then issue claim as is
=> issue(claim = c);

RegEx Symbols

* Beginning of line: ^

* End of line: $

* Force case insensitive: (?!)

* OR values: |

# Pass through role claims that start with "ceo" or "coo", case insensitive
c:[type == "http://contoso.com/role", Value =~ "^(?!)ceo|coo$"]
=>issue(claim = c);

* Matches any characters zero or more times: .*

* Matches preceding character zero or more times: *

* Matches preceding character one or more times: +

Value =~ "(?!)DC.*Office" # matches "DC Office" or "DC Regional Office" etc. case insensitive
Value =~ "(?!)yaho*" # matches "yah" or "yaho" or "yahoooooo" etc. case insensitive
Value =~ "(?!)yaho+" # matches "yaho" (not "yah" though) or "yahoo" or "yahoooooo" etc. case insensitive

RegEx String Replacement

* Use RegExReplace function:

  RegExReplace(
	"string_to_search", 
	"string_to_match_regex_expression", 
	"string_used_to_replace_matches)

* Examples

RegExReplace(c.Value, "(?!)directory","Manager"); # relace all matching occurrance of "directory" (case insensitive) with "Manager" so "IT Directory" will become "IT Manager"

Query Attribute Stores

* Default attribute store: Active Directory

* Also supports:
– SQL attribute stores
– LDAP attribute stores

SQL Attribute Stores

* If user is located in SQL database attribute store, claim rule language can
– query SQL database
– and generate claims based on the information in the database

* Can only use string type as input and/or output parameters

* Examples:

# If incoming claims contains emailaddress claim type
c:[type == "http://contoso.com/emailaddress"]
# Query SQL database for age and puchasinglimit using the emailaddress value as input parameter
# and issue two outgoing claims using the values retured from database query
# with "Custom SQL Store" as the issuer
=> issue(store="Custom SQL Store", types = ("http://contoso.com/age", "http://contoso.com/purchasinglimit"), query = "SELECT age,purchasinglimit FROM users WHERE email={0}", param = c.value);

LDAP Attribute Stores

* Format:

# For generic LDAP
QUERY = "query_filter;"

# For AD only
QUERY = "QUERY_FILTER;ATTRIBUTES;DOMAIN_NAME\USERNAME"

# If query_filter is missing, it defaults to:
samAccountName={0}

* Examples:

# If incoming claims contains emailaddress claim type
c:[type == "http://contoso.com/emailaddress"]
# Query LDAP for age and puchasinglimit using the emailaddress value as input parameter
# and issue two outgoing claims using the values retured from LDAP query
# with "Custom LDAP Store" as the issuer
=> issue(store="Custom LDAP Store", types = ("http://contoso.com/age", "http://contoso.com/purchasinglimit"), query = "mail={0};age,purchasinglimit", param = c.value);

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

* Query Active Directory and send multiple claims in one query:

@RuleName = "Another example"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory",
    types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
             "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
             "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
             "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
             "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"), query = ";mail,displayName,userPrincipalName,givenName,sn;{0}", param = c.Value);

References

* Understanding Claim Rule Language in AD FS 2.0 & Higher

* Attribute Stores

* AD FS 2.0: Using RegEx in the Claims Rule Language

This entry was posted in Uncategorized. Bookmark the permalink.