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.
Related
I have created an ASP.NET Core application that successfully authenticates users with Azure AD and have seen that for an authenticated user an instance of type System.Security.Claims.ClaimsPrincipal is returned containing a single identity of type System.Security.Claims.ClaimsIdentity with a Type property valued "preferred_username" that carries the user's UPN. Among the claims that are returned there is for example one with its Type property valued "name" which seems to contain the user's display name. I was wondering if there is a way to have other users' attributes/properties also returned in the form of claims.
I was wondering if there is a way to have other user's
attributes/properties also returned in the form of claims.
Obviously you can retrieve optional claims within your token using azure active directory. To achieve that you would need few settings in Token configuration under azure active directory blade.
What Additional Claim can be added:
Currently, you can include these optional claims in your both idToken, accessToken and saml2Token
Settings For Optional Claims:
If you would like to include additional claims within your token you ought to configure as following:
Select Token configuration in your app:
Select Add optional claim and Include your claims:
You should have profile access permission:
Check your Manifest if the claims added accordingly:
Now You can Call Optional Claims which are available ClaimTypes Class:
Note: You can check the available claims fields witin ClaimTypes Class here.
Code Snippet:
You can implement within your application as following:
var username = HttpContext.User.Identity.Name;
var givenName = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var country = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Country)?.Value;
Note: If you would like to know more details on optional claims you could check our official document here.
Here is the way authentication is set up.
- Client Browser sends the request (URL below) to client's ADFS server,
- Client ADFS then look at the nested relay state and forward the request to our ADFS server.
- Our ADFS look at the request and send the request to our APP.
URL is here.
https://clientadfs.clientdomain.com/adfs/ls/idpinitiatedsignon.aspx?RelayState=RPID%3Dhttps%3A%2F%2ouradfs.ourdomain.com%2Fadfs%2Fls%2F%26RelayState%3DRPID%3Dhttps%3A%2F%2ourapp.ourdomain.com%2Fvaruna%2Fconsole%2Fsso.aspx%3FsamISso%26lang%3Den_CA
The request produces a blank page with no error on the ADFS server.
I got the fiddler trace the client. Client uses users' email address to identify the users. I can see in the SAML token sent to Client's ADFS has this email address.
This SAML token goes to our ADFS server and I see the SAML response that come out of our ADFS server. This however does not have the user email address. I think that is the problem.
On our ADFS server, I have this Client's claim (on Claims Provider Trust) to handle user ID (which is their email):
Claim Rule name: Email
Incoming Claim type: Name ID
Incoming Name ID format: Email
Outgoing Name ID format: Email
Pass through all claim values.
Here is the claim in Claim Rule Language
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] == "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"]
=> issue(Type = "Email", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType);
On client's ADFS config, this is their email/Userid configuration:
IssuanceTransformRules : #RuleTemplate = "LdapClaims"
#RuleName = "Pass email"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccou
ntname", Issuer == "AD AUTHORITY"]
=> issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/20
05/05/identity/claims/nameidentifier"), query = ";mail;{0}", param = c.Value);
I have no idea what I am doing wrong. Can anyone spot my issue? or can you suggest where I should look at?
Thanks for your help!
RM
"to handle user ID (which is their email)"
So is the SAML assertion an assertion for a type of email or for a type of userID? i.e. what is the assertion name for this attribute.
On the ADFS side. to transform an email claim it expects a type of "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
Update
You need to transform NameId to email. NameId also has an "Incoming name ID format" which I'm guessing is "email". You need to verify this in the SAML metadata.
So your claim rule should look like:
c:[Type ==
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"]
== "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"] => issue(Type =
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value,
ValueType = c.ValueType);
Update 1
ADFS supports:
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
so the format can either be emailAddress, persistent or transient.
Try the Transform rule with all three formats and see.
Also, what claims is your application getting? You can dump them out via How to: Access Claims in an ASP.NET Page.
I have been trying to setup gerrit to work with LDAP authentication. I read documentation numerous time some link do not work to the examples documentation pulled from tuleap on subject comes up with different solutions and explanations I have found three different configurations for ldap.inc from tuleap this is very confusing lacking explanation too.
So i am stuck with this problem. I have spent hours reading and trying to sort this out. Can someone tell me what am I doing wrong? here is my
ldap.inc
<?php
// LDAP server(s) to query for more information on Tuleap users and
// for authentication.
// You may use a comma-separated list if there are several servers available
// (leave blank to disable LDAP lookup).
// To specify secure LDAP servers, use 'ldaps://servername'
$sys_ldap_server = 'techhub.lt';
// To enable LDAP information on Tuleap users, also define the DN
// (distinguised name) to use in LDAP queries.
// The ldap filter is the filter to use to query the LDAP directory
// (%name% are substituted with the value from the user table)
$sys_ldap_dn = 'dc=techhub,dc=lt';
// For LDAP systems that do not accept anonymous binding, define here
// a valid DN and password:
$sys_ldap_bind_dn = "cn=admin,dc=techhub,dc=lt";
$sys_ldap_bind_passwd = "pass";
// LDAP authentication:
// Tuleap only supports authentication with a attempt to bind with LDAP server
// with a DN and a password.
// As the DN is usually long (eduid=1234,ou=people,dc=tuleap,dc=com) people
// usually authenticate themself with a login. So we need to first look
// for the DN that correspond to the given login and once found attempt to bind
// with the given password.
// In order to autenticate successfully users you need to properly
// User login (authentication 1st step)
$sys_ldap_uid = 'uid';
// User unique identifier. It's probably not the uid (or login) because it
// may change. This is a value that never change whatever happens to the
// user (even after deletion). It correspond to ldap_id field in user table
// in database.
// (authentication 2st step)
$sys_ldap_eduid = 'eduid';
// User common name
$sys_ldap_cn = 'cn';
// User email address
$sys_ldap_mail = 'mail';
// Specific DN to look for people
// You may use more than one DN separated by ; if you want to use several branches.
// Example : 'ou=People, dc=st, dc=com ; ou=Extranet, dc=st, dc=com'
$sys_ldap_people_dn = 'ou=people,dc=techhub,dc=lt';
// Filter used to look for user. It should cover a wide selection of
// fields because it's aim to find a user whatever it's given (email, name,
// login, etc).
$sys_ldap_search_user='(|(uid=%words%)(cn=%words%)(mail=%words%))';
// By default tooltip search is using ($sys_ldap_cn=%words%*) search filter (Hardcoded)
// You can change for a more sophisticated search
// $sys_ldap_tooltip_search_user='(&(|(sn=%words%*)(givenName=%words%*)(uid=%words%*))(!(givenName=BoiteVocale))(uid=*))';
// By default tooltip search attrs are $sys_ldap_cn and $sys_ldap_uid (Hardcoded)
// You can choose the attributes the search will retrieve
// $sys_ldap_tooltip_search_attrs='uid;sn;givenName';
// On account creation, what it the default user status (A, R, ...)
$sys_ldap_default_user_status = 'A';
// Enable LDAP based authentication for SVN by default for new projects
$sys_ldap_svn_auth = 1;
// Enable LDAP daily synchronization
$sys_ldap_daily_sync = 1;
// Enable usage of LDAP for people management. For instance autocompletion on
// user list, automatic creation of users not already referenced in the forge.
$sys_ldap_user_management = 1;
// Enable ldap group management.
// This allows to mirror a LDAP group defined in LDAP directory within the forge
// Doesn't work yet with only works with OpenLDAP compatible directories yet.
$sys_ldap_grp_enabled = 1;
// Specific DN where the user groups are
$sys_ldap_grp_dn = 'ou=groups,dc=techhub,dc=lt';
// Field that reprsent group name
$sys_ldap_grp_cn = 'cn';
// Field that indicate the membership of a user in a group
$sys_ldap_grp_member = 'uniquemember';
?>
AND gerrit.conf
[gerrit]
basePath = git
canonicalWebUrl = http://techhub.lt:2401/
[database]
type = H2
database = db/ReviewDB
[sendemail]
smtpServer = localhost
[container]
user = root
javaHome = /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.45.x86_64/jre
[sshd]
listenAddress = *:29418
[httpd]
listenUrl = http://*:2401/
[cache]
directory = cache
[auth]
type = LDAP
[ldap]
server = ldap://techhub.lt
accountBase = ou=people,dc=cro,dc=techhub,dc=lt
groupBase = ou=group,dc=cro,dc=techhub,dc=lt
accountFullName = cn
That's 2 different problems.
Do you manage to authenticate with LDAP accounts on gerrit ?
If yes, I think the problem comes from "$sys_ldap_eduid = 'eduid';" parameter in tuleap ldap config. This attribute should be the unique identifier of one's account in ldap (either you have such an attribute of you can use 'uid' as a fallback.
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");
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.