Ruby - AD/LDAP auth - read user details - ruby-on-rails

I am trying LDAP/Active directory authentication. This code is working perfectly, I can authenticate.
My question is - How do I retrieve user information from AD? I want to read firstname, lastname, fullname, email etc from AD for the authenticated user.

In LDAP, users do not authenticate, connections are authenticated. Once the authorization state
of the connection has been established (by a successful bind request), code that desires to
retrieve information from the directory must transmit a search request to the directory server
and then interpret the response.
Search requests must contain a minimum the following parameters:
the base object at which the search starts (no objects above the base objects are returned)
the scope of the search: base is the base object itself, one is the base object and
one level below thw base object, sub is the base object and all entries below the base object.
a filter which limits the entries returned to those entries which match the assertion in the
filter
A list of attributes can also be supplied, though many, but not all, LDAP APIs will request all
user attributes if none are supplied in the search request.
see also
LDAP: Mastering Search Filters
LDAP: Search best practices
LDAP: Programming practices

Related

Identity Server - Multiple users with different access levels on different accounts

My application is composed of an API layer which is accessable by presenting a Bearer Token, obtained from our Identity Server. Each of our API has a level of scopes that the token must contain in order to be invoked: this means, for example, that if you want to make the API call which "creates a product" you need a write access to products (i.e. the scope "products") while you may just need the scope "products_read" if you only want to retrieve them.
Scopes are bound into the token when you authenticate onto the ID server.
Now, I need this user to be able to perform different operations on different "workspaces". Each workspace administrator can set which permissions each user have. This is, in fact, linked to the scopes that each user will have when operating on that particular workspace.
Right now we have implemented that, if you are trying to access a different workspace, the API layer will check:
if your bearer token is valid (by validating it on the ID server)
if you are authorized to access that workspace
changing associated claims by removing the original "scopes" (set into the token by the ID server) and overwriting with those assigned by the administrator of that workspace
This somehow works, but it stinks because I don't want my application layer (API) to have this kind of responsability and the opportunity to tamper with the token. I want the ID server to handle it and, after the user tries to enter into a different workspace, it generates a new crafted bearer token with correct claims (so the API will just need to trust it).
What's the best approach in doing that? I'm digging into the "custom grant type": may this be the right approach?
Scopes are fixed at design time and the same for all users. I like your use of products and products_read - that is very standard.
When you need dynamic behaviour, implement claims, which depend on who the user is. In your case I would use a workspaces array claim. Since this is a key vaue for authorization, it should be added to access tokens at the time of token issuance. Think in terms of your products scope being composed of claims.
It feels like workspaces and other permissions are part of your business data rather than your identity data. At the time of token issuance, IdentityServer should send identity attributes (eg subject claim) to an API endpoint you provide. which returns business attributes (workspaces). I believe in IdentityServer this is done via a custom profile service.
I'd try to avoid different tokens for different workspaces, since that will be awkward in terms of usability and code complexity. The standard way to do it would be to redirect the user each time.

Email aliases not coming back from graph API users endpoint

We are seeing a few users for which the graph API returns only a primary email despite knowing that there are multiple SMTP addresses configured for these users. We are observing this on the List Users endpoint as well as the Get User endpoint.
When using the $select parameter to include the otherMails and proxyAddresses properties, both come back as empty arrays. Microsoft365 admins for the tenant to which these users belong have provided us with screenshots demonstrating that some of these users have at least one email alias configured in addition to their primary email.
I'm curious if there's any scenario in which it would be expected that otherMails and proxyAddresses would be empty despite the presence of aliases on a user? Or if there's a particular way in which the request to the List/Get users endpoints must be crafted to get these fields to come back non-empty. According to the docs it seems like a collision between SMTP addresses across directory objects might cause this, but I'm certain that's not the case for all of the users for which we're seeing this issue.

Multiple User Types - Spring Security Configuration

We have several user types
Internal Users (authenticated using Active Directory)
External Users/Clients (stored in the DB1)
External Users/Vendors (stored in the DB2)
We plan to use Spring Security OAuth2 to generate GWT token that can then be used to call set of webservices
I can use multiple AuthenticationProviders (LDAPAuthenticationProvider and two DAOAuthenticationProviders) but then we will loose ability to have user to be BOTH client and vendor for example (if they use SAME email for authentication). Since it will stop polling providers once authentication is successful.
I can also use profiles #Profile="vendor/client" and start auth server specifically for Client or Vendor authentication - but that means two different processes = more maintenance.
Any other ideas ? Anybody ran into something similar?
There are a couple of options I can think of:
1 - If each different type of user uses a different client ID, then set something in the client details when you load them to show how the user should be authenticated for the client. There's a getAdditionalInformation() method on ClientDetails that returns a Map you can use to store this info
/**
* Additional information for this client, not needed by the vanilla OAuth protocol but might be useful, for example,
* for storing descriptive information.
*
* #return a map of additional information
*/
Map<String, Object> getAdditionalInformation();
2 - Pass in a header or request param that the AuthenticationProvider can then use to determine how to authenticate that user. You'll need to configure your own implementation of WebAuthenticationDetails to retrieve this information from the request.
This should then be available by calling getDetails() on the Authentication object passed into the AuthenticationProvider's authenticate() method.

Returning the network subdomain from Yammer

I have a ruby on rails application that authenticates the users through Yammer and then redirects them to the right tenant depending on their network name.
Workflow essentially goes like this:
User is presented with sign-in/sign-up page
Authenticate through the Yammer API and redirected back to a callback URL (yammer.example.com/auth)
The callback controller then looks at the auth response, and determines the network that the user belongs to.
I redirect to that tenant on the subdomain (eg my-network.example.com) and sign the user in
There are some other things that go on here in the background (creation of other users in the network, user matching etc.) but the problem I am having is with the actual network name and subdomain creation and redirection.
The way that the subdomain is currently parsed is to use the SLD of the users network name.
As an example network_name: "example.com" returns "example" as the subdomain for us to create/redirect to.
This was all working great until we started testing with paid Yammer accounts, their network names seem to not play nice with our current code.
This is what we are currently using:
if PublicSuffix.valid?(auth.extra.raw_info.network_name.mb_chars.normalize(:kc).to_s.downcase)
yammer_domain = PublicSuffix.parse(auth.extra.raw_info.network_name.mb_chars.normalize(:kc).downcase)
subdomain = yammer_domain.sld.gsub(/[^0-9A-Za-z]/, '')
else
subdomain = auth.extra.raw_info.network_name.downcase.gsub(/[^0-9A-Za-z]/, '')
end
I'll admit that this is not the cleanest at the moment because I have been hacking a little trying to catch when a network name is not a correct domain and fixing it there. I am normailizing all characters and then parsing the SLD using the PublicSuffix gem.
If it is not a valid domain then I try and normalize the characters and strip everything out that we don't need (so something like L'oreal would just become loreal).
This still seems to throw an error and not parse correctly.
So my question is:
Is there anything different about how the network names are set up with paid accounts vs. free accounts? Is there a more reliable way to return the network name to parse for subdomains using the Yammer API?
Is there anything different about how the network names are set up
with paid accounts vs. free accounts?
No there isn't.
Is there a more reliable way to return the network name to parse for
subdomains using the Yammer API?
If I understand what you're trying to do correctly, I don't think "network_name" is the correct JSON object to use. The network admin can decide to change the network name any time and that would screw your app.
I'd recommend you use "network_domains". The value of this JSON object contains a list of all yammer networks the logged-in user is a member of, and the first item in the list is ALWAYS the primary network of the user.
For example, the result of a GET request to api/v1/users/current.json would contain something like:
network_domains":["jet.usa.cc","jet.onmicrosoft.com","jet2.onmicrosoft.com"]
In the above example, jet.usa.cc is the primary network of the logged-in user. The network domain name cannot be changed, it's a constant. You may extract the value of the primary network in your RoR app and use it as you wish.

Posting data between Rails app

Ok so here's the big picture, I have 2 sites, call them SiteA and SiteB. SiteA sort of serves as a master site when it comes to ecommerce transactions (We only have one account with our Credit card processing company, so successful/declined transactions get redirected to SiteA)
So a user logs on to SiteB, goes through the buying process and submits the form with the credit card details which gets posted to the credit card verifying company, upon a successful transaction SiteA receives all the necessary info (in a POST method) sent by the Credit card processing company. At this point the code on SiteA, based on a param determines which site the transaction originated and again POSTS the data to that site using this code
Net::HTTP.post_form(URI.parse("http://#{params[:site_name]}/success"), params)
success is defined in routes.rb as
map.connect 'success', :controller => "some_controller", :action => "success"
The problem however is that although the user is logged in on SiteB, when SiteB receives the data POSTed by SiteA (which obviously doesn't know anything about SiteB's session_id), further processing of the data on SiteB fails due to lack of session information.
Both the sites are running exactly identical code.
My question, is there a way where in session data from SiteB can be requested and appended to the Post data when SiteA sends the data.
Many thanks
If these two sites are running on the same physical machine, you can always use something like Memcache as a simple way to exchange state information between two otherwise unrelated sites. If they are on separate machines, using a POST may be your only reasonable option though it ends up being more of a hassle to implement.
If SiteB must forward to SiteA for some processing, and SiteA needs to return the visitor back to SiteB, you need to create a private API on both applications. You can usually get by with creating a simple REST interface and dumping whatever you need in a simple serialized format such as YAML or JSON depending on your preference.
For instance, the procedure might be roughly as follows:
Visitor is forwarded from SiteB to SiteA via a HTTP redirect.
Visitor proceeds with transaction on SiteA and a record with a unique identifier is created in the database that reflects the outcome of this transaction.
SiteA forwards the visitor back to SiteB with this unique identifier as a parameter.
SiteB makes a request to SiteA to retrieve the details of this transaction.
SiteB updates its internal records as required and presents the outcome of the transaction to the visitor.
To be secure you should probably generate random unique identifiers, as something like UUID will prevent people from inspecting arbitrary orders by guessing numbers. You should also ensure that the call to SiteA to retrieve transaction details has some kind of access control even if it is only a secret token or passphrase. A more robust implementation would probably use TLS and SSL certificates to verify the origin of any request.

Resources