In our system, we have users registered from different domain (lets say their mail address are #gmail.com,#outlook.com,#yahoo.com). I have a requirement to restrict the user's access to RP based on the domain he comes from. For this setup, I tried to configure Issuance Authorization rule in ADFS (to allow users only from a particular domain) with the below rule
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value =~ "gmail.com$"]
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
I was passing email address claim through issuance authorization rule tab so I didn't initialized the input in the authorization ruleset as mentioned in the technet link (under Sending the execution output to the claims pipeline Section)
After the entire process has run for a give rule set (steps 1, 2, and 3), the newly issued outgoing claims (content of the output claim set) will be used as input to the next rule set in the claims pipeline. This allows for claims to flow from the output of one rule set to the input for another rule set, as shown in the following illustration.
But the authorization doesn't seem to work.
The claims configured in the first tab ( Issuance transform rules ) are not passed to the second tab (Issuance Authorization rules).
So we need to repeat the process again in the second tab (map emailaddresses -> emailaddress) as shown below.
Now the custom rule works perfectly.
If you need to add few more email domains to the acceptance criteria just use the "|" symbol:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value =~ "yahoo.com$|gmail.com$"]
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
Related
I'm developing validation against a swagger schema. I have a question regarding multiple authentications, imagine I have a security block
security:
- header
- cookie
Logic is like this - parse a user request, and put header/cookie inside some hashmap/dict V.
If a user has provided no header/cookie - return an error
If a user has provided the only header, add header value to V, V["header"] = request.header.value
If a user has provided the only cookie, add cookie value to V, V["cookie"] = request.cookie.value
But what should I do if a user has provided both cookie and header? Should I return an error (oneOf) or put the only header to the V variable (depends on the order of security) or put both header and cookie in the V or it depends on the application logic?
I have read https://swagger.io/docs/specification/authentication/, but it is still unclear to me.
I'm trying to make a Authorization Rule in ADFS 3.0, disabling some users in a specific "OU" form using a "relying party", with no success.
On the Issuance Transform Rules, i've configured the claim "http://schemas.microsoft.com/ws/2008/06/identity/claims/distinguishedname" to get the Distinguished name from AD.
Here is an example: CN=John Doe,OU=XYZ,OU=ABC,DC=CONTOSO,DC=com
This rule should deny access from users in the XPTO OU
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/distinguishedname", Value =~ "^[^,]*,OU=XPTO.*$"]
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/deny", Value = "true");
And this rule should permit access for all users outside the XPTO OU
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/distinguishedname", Value !~ "^[^,]*,OU=XPTO.*$"]
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");
But, instead these rules grant access to some users and deny others, they deny access to all users
Can you shed some light on this issue?
Well, the issue has been resolved.
The problem with this approach, was in the few documentation explaining the Issuance Rules workflow of ADFS Relying Party.
All I had to do was adding a first rule in the Issuance Authorization Rules, getting the user's DN.
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/distinguishedname"), query = ";DistinguishedName;{0}", param = c.Value);
Afterwards, the rules described above, worked like a charm.
A few pointers to all experiencing these Authorization configs:
If a permit claim is issued, then the user is allways allowed
If no permit or deny claims are issued, then the user is denied
This approach works when only one Organizational Unit is denied access. For other complex validations, a custom Attribute Store should be used
I had a couple of "Acceptance transform rules" defined for AD which suddenly stopped working.. It looks like the relyingpartytrustid-claim (http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid) don't exist in the "acceptance transform rules"-step in the ADFS (2012 R2) pipeline anymore. The strange thing is that it was there before, but now I can't see it until the next step in the ADFS claimstransformation pipeline (issuance transform rules for relying parties).
Does anyone know exactly when the relyingpartytrustid-claim is issued by ADFS? I can't find any information on how/when these claims are issued.
Claim-rule that worked before and then suddenly stopped working:
c:[Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid"]
=> add(store = "CustomAttributeStore", types = ("http://domain.no/context/FmApplicationId", "http://domain.no/context/AdfsApplicationId", "http://domain.no/context/CustomProperty"), query = "GetApplicationAttributes#FmApplicationId,AdfsApplicationId,CustomProperty", param = c.Value);
The following rule results in "temp" beeing issued if the rule is placed under "acceptance transform rules", but not when it is placed under "issuance transform rules"
NOT EXISTS([Type == "http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid"])
=> issue(Type = "http://domain.no/temp", Value = "dontexist");
The claim http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid is part of the Request Context provided by Web Application Proxy to AD FS during authentication of external users.
I'm trying to setup PingFederate as a claim provider in ADFS with the intention that I federate from a PF realm through ADFS to an ADFS RP. I want ADFS to add attributes from Active Directory to the assertion before sending it to the RP. PingFederate is only sending the user's Windows login ID. On the Claim Provider side I'm passing through Name ID. Just for testing, I have tried adding an attribute like this:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]
=> add(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", Value = "myemail#test.com");
I added this on the CP side both above and then below my pass through rule and nothing was added to my assertion. I also tried this on the RP side with no luck.
I guess my question is whether this is possible in ADFS. Ultimately I would like PingFed to send the user's login ID as the name ID, have ADFS lookup the user in Active Directory and add the email address as a claim then send the assertion to the RP. As for adding the attribute from Active Directory, I found this post technet.microsoft.com/en-us/library/ff678048.aspx. Problem is I can't even add a manual value.
For a manual value, use something like:
=> issue(type = "http://contoso.com/partner", value = "Adatum");
For the query, use something like:
Use the normal LDAP rule to produce a loginID claim and then
c:[Type == "http://company.com/claims/loginID", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), query = ";email;{0}", param = c.Value);
I figured this out. My first use case is to ensure that the AD account exists.
Essentially what is required are 3 claim rules on the CP side:
1 - perform the lookup based on the name ID. I created a custom rule to
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]
=> add(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"), query = "sAMAccountName={0};objectSID;{1}", param = c.Value, param = "MYDOMAI\" + c.Value);
The parameters required in for the query are:
LDAP query to locate the user
Attribute(s) to extract
User's login ID in the format DOMAIN\userid
2 - a claim rule to simply pass the name ID through
3 - a claim rule to simply pass the SID through
On the RP side, I have 2 claim rules to pass the name ID and the SID through. Then I have an Issuance Authorization Rule to ensure that the SID is present as a claim. This is a custom rules with the following:
EXISTS([Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"])
=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "PermitUsersWithClaim");
Seems convoluted but this is what I have. My second use case is to ensure the account is enabled, but I'm not sure if this is possible because the disabled attribute is stored as a bit in the userAccountControl attribute.
In case of LDAP authenticaion, what are the parameters that are generally used for authentication. I guess using DN would be a headache for users logging in via ldap because it is too large to remember.
How is the option of using uid or sAMAccountName for authentication where in my implementation, I retrieve the dn of the corresponding uid or sAMAccountName and proceed to authentication.
Am I going the right track?
In LDAP, a connection or session can be authenticated. When an LDAP client makes a new connection to an LDAP directory server, the connection has an authorization state of anonymous. The LDAP client can request that the authorization state be changed by using the BIND request.
A BIND request has two forms: simple and SASL. Simple uses a distinguished name and a password, SASL uses one of a choice of mechanisms, for example, PLAIN, LOGIN, CRAM-MD5, DIGEST-MD5, GSSAPI, and EXTERNAL - all of which except for GSSAPI and EXTERNAL are too weak to use in production scenarios or mission-critical areas.
To Use the simple BIND, construct a BIND request and transmit it to the LDAP directory server. The LDAP directory server will respond with a BIND response in which is contained a result code. The result code is an integer, anything other zero indicates that the BIND request failed. If the result code is zero, the BIND request succeeded and the session authorization state has been changed to that of the distinguished name used in the BIND request.
Each subsequent BIND request on the same connection/session causes the authorization state to be set to anonymous and each successive successful BIND request on the same connection/session causes the authorization state to be set to the authorization state associated with the authentication ID, which is the distinguished name in the case of the simple BIND, but might be something else entirely where SASL is used - modern professional quality servers can map the incoming names to different DNs.
Whichever language is used, construct a BIND request, transmit it to the server, and interpret the response.
Update:
If the distinguished name is not known, or is too cumbersome (often the case with web application users who don't know how they are authenticated and would not care if they did know), the LDAP application should search the directory for the user. A successful search response always contains the distinguished name, which is then used in a simple BIND.
The search contains at a minimum, the following:
base object: a distinguished name superior to the user, for example, dc=example,dc=com
a scope: base level, one level below base, or subtree below base. For example, if users are located subordinate to ou=people,dc=example,dc=com, use base object ou=people,dc=example,dc=com and a scope of one-level. These search parameters find entries like: uid=user1,ou=people,dc=example,dc=com
a filter: narrows down the possible search results returned to the client, for example (objectClass=inetOrgPerson)
a list of requested attributes: the attributes from an entry to return to the client. In this case, use 1.1, which means no attributes and returns on the DN (distinguished name), which is all that is required for the simple BIND.
see also
the links in the about section here
LDAP servers only understand LDAP queries; they don't have "usernames" like you and I are used to.
For LDAP, to authenticate someone, you need to send a distinguished name of that person's (or entity's) entry in LDAP; along with their password.
Since you mentioned sAMAccountName I am assuming you are working with Active Directory. Active Directory allows anonymous binds - this means you can connect to it without providing any credentials; but cannot do any lookups without providing credentials.
If you are using python-ldap and Cython (and not IronPython which has access to the various .NET APIs that make this process very easy); then you follow these steps.
Typically you use a pre-set user that has appropriate rights to the tree, and connect to the directory with that user first, and then use that user's access for the rest of the authentication process; which generally goes like this:
Connect to AD with the pre-set user.
Query active directory with the pre-set user's credentials and search for the distinguished name based on the sAMAccountName that the user will enter as their "username" in your form.
Attempt to connect again to Active Directory using the distinguished name from step 2, and the password that the user entered in their form.
If this connection is successful, then the user is authenticated.
So you need two main things:
The login attribute (this is the "username" that LDAP understands)
A LDAP query that fetches information for your users
Following is some rough code that can do this for you:
AD_USER = 'your super user'
AD_PASSWORD = 'your super user password'
AD_BIND_ATTR = 'userPrincipalName' # this is the "login" for AD
AD_URL = "ldap://your-ad-server"
AD_DN = "DC=DOMAIN,DC=COM"
AD_LOGIN_ATTR = 'sAMAccountName' # this is what you user will enter in the form
# as their "login" name,
# this is what they use to login to Windows
# A listing of attributes you want to fetch for the user
AD_ATTR_SEARCH = ['cn',
'userPrincipalName',
'distinguishedName',
'mail',
'telephoneNumber','sAMAccountName']
def _getbinduser(user):
""" This method returns the bind user string for the user"""
user_dn = AD_DN
login_attr = '(%s=%s)' % (AD_LOGIN_ATTR,user)
attr_search = AD_ATTR_SEARCH
conn = ldap.initialize(AD_URL)
conn.set_option(ldap.OPT_REFERRALS,0)
conn.set_option(ldap.OPT_PROTOCOL_VERSION,3)
try:
conn.bind(AD_USER,AD_PASSWORD)
conn.result()
except:
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
# Exit the script and print an error telling what happened.
sys.exit("LDAP Error (Bind Super User)\n ->%s" % exceptionValue)
try:
result = conn.search_s(user_dn,
ldap.SCOPE_SUBTREE,
login_attr, attr_search)
except:
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
# Exit the script and print an error telling what happened.
sys.exit("LDAP Error (Search)\n ->%s" % exceptionValue)
# Return the user's entry from AD, which includes
# their 'distinguished name'
# we use this to authenticate the credentials the
# user has entered in the form
return result[0][1]
def authenticate(user,password):
bind_attr = AD_BIND_ATTR
user_dn = AD_DN
login_attr = '(%s=%s)' % (AD_LOGIN_ATTR,user)
data = _getbinduser(user)
if len(data) == 1:
return None
# Information we want to return from the directory
# for each user, season to taste.
info = {}
info['name'] = data['cn'][0]
info['email'] = data['mail'][0]
try:
info['phone'] = data['telephoneNumber'][0]
except KeyError:
info['phone'] = 'Not Available'
conn = ldap.initialize(Config.AD_URL)
conn.set_option(ldap.OPT_REFERRALS,0)
conn.set_option(ldap.OPT_PROTOCOL_VERSION,3)
try:
# Now we have the "bind attribute" (LDAP username) for our user
# we try and connect to see if LDAP will authenticate
conn.bind(data[bind_attr][0],password)
conn.search(user_dn,ldap.SCOPE_SUBTREE,login_attr,None)
conn.result()
return info
except (ldap.INVALID_CREDENTIALS,ldap.OPERATIONS_ERROR):
return None
One small expansion on Terry's excellent comment. If you store all your users in the same part of the DIT, and use the same attribute to identify them, you can programmatically construct the DN, rather than searching for it.