Get Azure AD directory users in a Rails app - ruby-on-rails

I have a Rails 6 application and I want to use Azure Active Directory as an authentication system (with open id connect, saml2 and ldap).
The authentication is done.
Now I am trying to display user information like names or email addresses. I also want to be able to export all users of a directory.
I have tried to set a configuration up like so:
In my Rails app, in the admin panel, an admin can configure Azure AD for my application
in the config, the admin copies and pastes the configuration link provided by Azure AD (a JSON response)
Then, copies and pastes the app client_id
Then, the tenant_id (directory id)
Here is a piece of code that I expected to work:
def update_oidc
identity_provider = IdentityProvider.find_by(provider_type: 'open_id_connect', id: params[:id])
client_id = params[:client_id].strip
metadata_url = params[:metadata_url].strip
tenant_id = params[:tenant_id].strip
metadata = HTTParty.get(metadata_url).parsed_response
identity_provider.update(config: {
metadata: metadata,
metadata_url: metadata_url,
client_id: client_id,
tenant_id: tenant_id,
})
if tenant_id
directory_access_url = "https://graph.windows.net/#{tenant_id}/users?api-version=1.6"
result = HTTParty.get(directory_access_url).parsed_response
identity_provider.directories.find_or_create_by(tenant_id: tenant_id).update(
business_phones: result["business_phones"],
display_name: result["display_name"],
given_name: result["given_name"],
job_title: result["job_title"],
email: result["user_principal_name"],
mobile_phone: result["mobile_phone"],
office_location: result["office_location"],
surname: result["surname"]
)
end
redirect_to identity_provider
end
As the tenant_id is the directory id, i thought that we might be able to access user info this way (and following the Microsoft Docs). The thing is, it doesn't work because even though I'm connected to my Azure AD directory in my app, when I run result = HTTParty.get(directory_access_url).parsed_response, i have an authentication error telling me the token has expired or that i need to be connected.
I don't want to use PowerShell or anything like this. I want to be able to access directories data through my app.
Can someone tell me what i'm doing wrong or come up with an idea ?
Thanks

Just according to your code, I think you want to get the collection of users via the Azure AD Graph REST API Get users using jnunemaker/httparty library.
However, it seems to be missing the required header Authorization with its value like Bearer eyJ0eX ... FWSXfwtQ as the section Authentication and authorization of the offical document Operations overview | Graph API concepts said. Meanwhile, you have done the authentication with OpenID Connect, but Azure AD Graph API requires the access token as Authorization value from OAuth2 as the content below said.
The Graph API performs authorization based on OAuth 2.0 permission scopes present in the token. For more information about the permission scopes that the Graph API exposes, see Graph API Permission Scopes.
In order for your app to authenticate with Azure AD and call the Graph API, you must add it to your tenant and configure it to require permissions (OAuth 2.0 permission scopes) for Windows Azure Active Directory. For information about adding and configuring an app, see Integrating Applications with Azure Active Directory.
Azure AD uses the OAuth 2.0 authentication protocol. You can learn more about OAuth 2.0 in Azure AD, including supported flows and access tokens in OAuth 2.0 in Azure AD.
So I'm afraid you have to get the access token manually via OAuth2 for Azure AD again for using Graph API, or just simply refer to the sample code samples/authorization_code_example/web_app.rb using the adal library of GitHub repo AzureAD/azure-activedirectory-library-for-ruby for Ruby.

Related

PowerApps : Custom Connector with MS Graph : How can I use Application permissions in my custom connector?

I am having trouble creating a PowerApps Custom Connector which allows me to access Microsoft Graph using APPLICATION permissions.
I have created my Azure AD App Registration, with three API permissions:
Delegated: User.Read
Application: User.Read.All
Application: Directory.Read.All
I have confirmed the Redirect URI is correctly set for Power Apps (https://global.consent.azure-apim.net/redirect) and I have generated a Client Secret.
I have then created TWO different Power Apps custom connectors, and both of them give me exactly the same results.
Connector A: Auth Type: OAuth2.0
Identity Provider: Generic OAuth 2
Client ID & Client Secret (as expected)
Auth URL: https://login.microsoftonline.com/{{Tenant ID}/oauth2/v2.0/authorize
Token URL: https://login.microsoftonline.com/{{Tenant ID}/oauth2/v2.0/token
Scope: https://graph.microsoft.com/.default
Connector B: Auth Type: OAuth 2.0
Identify Provider: Azure Active Directory
Client ID & Client Secret (as expected)
Login URL: https://login.windows.net
Tenant ID: common (this is the default)
Resource URL: https://graph.microsoft.com
Scope: {blank} (this is the default)
I then setup (in both connectors) the same two actions:
/v1.0/me
/v1.0/users
In BOTH connectors the first action (/me) works .. and returns the details of the logged in user.
The second connector (/users) returns "Access Denied - Insufficient Privileges".
This is telling me it is using the Delegated permissions (which allows it to retrieve /me) and not the Application permissions (which is why it can't list all of the user accounts).
So .. is there any setting in Power Apps which allows me to specify that this connector should use Application requests rather than delegated?
Thank you!

Unable to access users calendar using application daemon token

I have been trying to access a user's data using the MS graph API. I have been following the official documentation and I have correctly configured the apps and the permission.
I am also following this code_sample and using this I have been able to fetch the daemon token for the application.
Using the token I am able to successfully fetch the data against the following endpoint:
https://graph.microsoft.com/v1.0/users/204e3e4f-xxxx-xxxx-xxxx-509934e1
The data looks like:
{"#odata.context":"https://graph.microsoft.com/v1.0/$metadata#users/$entity","businessPhones":[],"displayName":"developers#outlook.com Tiwari","givenName":"developers#outlook.com","jobTitle":null,"mail":null,"mobilePhone":null,"officeLocation":null,"preferredLanguage":null,"surname":"Tiwari","userPrincipalName":"developers_outlook.com#EXT##webdevelopers.onmicrosoft.com","id":"204e3e4f-x-xxxxx-xxxxxxx-509934e14fa8"}
However, I can't fetch any more data than this. All the other endpoints throw a 401 Unauthorised exception even when I have checked all the Application permission for my app.
I am trying to hit the following endpoints for fetching calendar of the above user:
https://graph.microsoft.com/v1.0/users/204e3e4f-xxxx-xx-xxxxz-509934e14fa8/calendar
https://graph.microsoft.com/v1.0/users/204e3e4f-xxx-xxx-xxxxx-509934e14fa8/calendars
both of these give 401.
Can someone suggest if I am missing something. I even decompiled my jwt token and it clearly shows all the following scopes added:
"roles": [
"Schedule.ReadWrite.All",
"OnlineMeetings.Read.All",
"Mail.ReadWrite",
"OnlineMeetings.ReadWrite.All",
"User.ReadWrite.All",
"Calendars.Read",
"Mail.ReadBasic.All",
"Files.ReadWrite.All",
"User.Read.All",
"Schedule.Read.All",
"Files.Read.All",
"Organization.ReadWrite.All",
"Mail.Read",
"Calendars.ReadWrite",
"Mail.Send",
"Organization.Read.All",
"Mail.ReadBasic"
],
Please help.
To get access to calendar events you should call the following endpoint:
GET /users/{id | userPrincipalName}/events
The documentations for calendars is here: https://learn.microsoft.com/en-us/graph/api/user-list-events
From your comment on Chris Johnson's answer, you're using a personal account. App-only tokens won't work to access a personal account, and you shouldn't be able to even obtain one using a personal account, since there's no concept of an admin.
App-only requires an admin to consent, and when she does, she grants access to all resources in her tenant. Personal accounts are not in work/school tenants, so they aren't in scope. Even if you've added that personal account as a guest in the work/school tenant, their calendar is hosted outside of the tenant, and is off limits.

How to simply get a bearer token to send requests to Azure DevOps API?

I need to create an organizational feed to host nuget packages shared among projects on our Azure DevOps environment.
After several unsuccesful attempts and research, I discovered that the only way to create an organizational feed is, by design from Microsoft mouth, the Azure DevOps API.
Source for the claim : This question on VS dev community
and The MS docs on project-scoped feeds
Basically, I just need to be able to perform a POST here :
https://feeds.dev.azure.com/{organization}/_apis/packaging/feeds?api-version=5.1-preview.1
with the body :
{
"name": "{myfeedname}",
"hideDeletedPackageVersions": true,
"upstreamEnabled": true
}
And of course, a Bearer token to authenticate myself. That's the point where I'm confused.
What is the simplest way to obtain one ? I'm logged in through my company Microsoft AD account on my computer browser on Azure DevOps. I don't see any Bearer token that I can "steal" to use in PostMan in my browser dev tools.
The API docs described some relevant info, but I'm confused on how to use it in Postman :
Security oauth2
Type: oauth2
Flow: accessCode
Authorization URL: https://app.vssps.visualstudio.com/oauth2/authorize&response_type=Assertion
Token URL: https://app.vssps.visualstudio.com/oauth2/token?client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
Scopes Name Description
vso.packaging_write Grants the ability to
create and read feeds and packages.
Here is the interface in Postman for OAuth2:
Ican see how the info in the docs relates to the fields 1 - 2 - 3 - 4, but then, what callback url should I use ? What credentials ? my Microsoft email + password from AD ?
I tried this, and all I seem to get is this from Postman :
{"$id":"1","innerException":null,"message":"A potentially dangerous Request.Path value was detected from the client (&).","typeName":"System.Web.HttpException, System.Web","typeKey":"HttpException","errorCode":0,"eventId":0}
TLDR
How do I properly proceed to get a token with Postman, or other tool to manually execute my one-time request to Azure DevOps REST API ?
notes :
Following info here : Unable to get Authorization code for Devops using Postman oAuth2.0
, leading here : https://github.com/Microsoft/azure-devops-auth-samples/tree/master/OAuthWebSample , I understand that I have to register and run a whole web application. Am I understanding this correctly ? I there a simpler way ?
I understand that I have to register and run a whole web application. Am I understanding this correctly ? I there a simpler way ?
Yes, you are right. You have to register whole web application.
As the interface in Postman for OAuth2, we need provide the CallbackUrl, ClientID, ClientSecret and so on. Then, we check the document Requesting an OAuth 2.0 token, we could to know the Callback URL is:
The client application callback URL redirected to after auth, and that
should be registered with the API provider.
So, we have to register an OAuth client app in Azure DevOps (https://app.vsaex.visualstudio.com/app/register), then we could get the following information, like:
You could check the document Authorize access to VSTS REST APIs with OAuth 2.0 for some more details.
AFAIK, there is currently no simpler way to get a bearer token to send requests to the Azure DevOps API.
Hope this helps.

How do I authenticate against the Dynamics 365 Data Export Service API?

I've set up something called the Data Export Service for Dynamics 365 so that it replicates into an Azure SQL database. This is working as expected.
I'm trying to find a way to be proactively notified if this service encounters any errors. There does not appear to be a native way to do this through the setup in CRM itself, but they do provide an API. The Swagger page outlining all methods can be found here.
I'm trying to call the GetProfilesByOrganizationId method using Postman:
https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=4ef7XXXX-XXXX-XXXX-XXXX-XXXXXX8a98f&status=true
I'm having issues with authentication and always receive the following error:
"Message": "Received unauthenticated requestRequest Url https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=4ef7XXXX-XXXX-XXXX-XXXX-XXXXXX8a98f&status=true"
I have registered an application in Azure that has permission to access Dynamics 365 on behalf of the authenticated user which in this case is me, the administrator.
I have set the Type to OAuth 2.0 on the Authorization tab of Postman. I have requested an Access Token using the Grant Type of Authorization Code against the above application successfully. This has added a header to the request:
Key: Authorization
Value: Bearer BIGLONGACCESSTOKEN
Despite this header being present I still get the error mentioned above.
The API documentation implies the authentication is OAuth2 Implicit Grant Flow (click on any red exclamation mark in the documentation) but I can't get this to work in Postman. When I try to request a token with this method I get the error:
unsupported_response_type
... in the Postman console.
Any ideas how to authenticate (with Implicit Grant?) against this API in Postman?
(I'd accept C# examples if they're more appropriate, but I'd be surprised if Postman can't show me what I need)
It looks like the code sample shown by Microsoft can work if updated with newer methods and with some extra configuration in Azure that's not documented.
Azure configuration
By installing the Data Export service (and assuming it's all working) you'll have a new Enterprise Application listed in Azure AD as Crm Exporter.
To take advantage of this application and authenticate with the Data Export API you must configure an app of your own.
Go to the App registrations tab in Azure AD and add a new application registration.
Give it a name and set the Application type to Native. The redirect URI doesn't typically matter as long as it's valid.
Click the Manifest button to edit the manifest, change the property oauth2AllowImplicitFlow to true and save the changes.
The only other important configuration is Required permissions which should be set as below:
Windows Azure Active Directory
Delegated permissions
Sign in and read user profile
Data Export Service for Microsoft Dynamics 365 (Crm Exporter)
Delegated permissions
Have access to Data Export Service for Microsoft Dynamics 365 API
You will then need to click Grant Permissions.
C# changes
The updated method looks like this:
using Microsoft.IdentityModel.Clients.ActiveDirectory;
string clientId = "11cfXXXX-XXXX-XXXX-XXXX-XXXXXXXXd020";
string user = "my.username#domain.com";
string password = "PASSWORD";
var authParam= await AuthenticationParameters.CreateFromResourceUrlAsync(
new Uri("https://discovery.crmreplication.azure.net/crm/exporter/aad/challenge")
);
var context = new AuthenticationContext(authParam.Authority, false);
var credentials = new UserPasswordCredential(user, password);
var token = await context.AcquireTokenAsync(authParam.Resource, clientId, credentials).AccessToken;
You can now query the Data Export API by providing the token as a header:
Authorization : Bearer eJ0y........Hgzk
curl -X GET --header 'Accept: application/json' 'https://discovery.crmreplication.azure.net/crm/exporter/profiles?organizationId=MyOrgId&status=true'

How to make API requests with an access_token for a Service Account

My end goal is to be able to retrieve place details from Google's API.
I need to do this as a Service Account, since this is kicked off as a background task on my server. Service Accounts require you to exchange a JWT (JSON Web Token) for an access_token. I finally got that working and am receiving an access_token. Phew.
Now however, I don't know what to do with this access_token.
The Place Details API says that the key parameter is required, but I don't have a key. Just an access_token. Using that value for key or changing the name of the paramater to access_token is not working.
Ultimately I need to be able to hit a URL like so:
https://maps.googleapis.com/maps/api/place/details/json?reference={MY_REFERENCE}&sensor=false&key={MY_ACCESS_TOKEN}
How do I use my Access Token to make a request to the Google Place Detail APIs?
Update 1
Still no success, but I thought I'd post the details of my request in case there's something wrong with what I'm submitting to Google.
I'm using the JWT Ruby library, and here are the values of my claim set:
{
:iss => "54821520045-c8k5dhrjmiotbi9ni0salgf0f4iq5669#developer.gserviceaccount.com",
:scope => "https://www.googleapis.com/auth/places",
:aud => "https://accounts.google.com/o/oauth2/token",
:exp => (Time.now + 3600),
:iat => Time.now.to_i
}
Looks sane to me.
Create the service account and its credentials
You need to create a service account and its credentials. During this procedure you need to gather three items that will be used later for the Google Apps domain-wide delegation of authority and in your code to authorize with your service account. These three items are your service account:
• Client ID.
• Private key file.
• Email address.
In order to do this, you first need a working Google APIs Console project with the Google Calendar API enabled. Follow these steps:
Go to the Google APIs Console.
Open your existing project or create a new project.
Go to the Service section.
Enable the Calendar API (and potentially other APIs you need access to).
You can now create the service account and its credentials. Follow these steps:
Go to the API Access section.
Create a client ID by clicking Create an OAuth 2.0 client ID...
Enter a product name, specify an optional logo and click Next.
Select Service account when asked for your Application type and click Create client ID.
At this point you will be presented with a dialog allowing you to download the Private Key as a file (see image below). Make sure to download and keep that file securely, as there will be no way to download it again from the APIs Console.
After downloading the file and closing the dialog, you will be able to get the service account's email address and client ID.
You should now have gathered your service account's Private Key file, Client ID and email address. You are ready to delegate domain-wide authority to your service account.
Delegate domain-wide authority to your service account
The service account that you created now needs to be granted access to the Google Apps domain’s user data that you want to access. The following tasks have to be performed by an administrator of the Google Apps domain:
Go to your Google Apps domain’s control panel. The URL should look like: www.google.com/a/cpanel/mydomain.com
Go to Advanced tools... > Manage third party OAuth Client access.
In the Client name field enter the service account's Client ID.
In the One or More API Scopes field enter the list of scopes that your application should be granted access to (see image below). For example if you need domain-wide access to the Google Calendar API enter: www.googleapis.com/auth/calendar.readonly
Click the Authorize button.
Your service account now has domain-wide access to the Google Calendar API for all the users of your domain, and potentially the other APIs you’ve listed in the example above.
Below is a description that uses a service account to access calendar data in PHP
The general process for service account access to user calendars is a follows:
• Create the Google client
• Set the client application name
• If you already have an Access token then check to see if it is expired
• If the Access token is expired then set the JWT assertion credentials and get a new token
• Set the client id
• Create a new calendar service object based on the Google client
• Retrieve the calendar events
Note: You must save the Access token and only refresh it when it is about to expire otherwise you will receive an error that you have exceeded the limit for the number of access tokens in a time period for a user.
Explanation of Google PHP Client library functions used:
The client object has access to many parameters and methods all of the following are accessed through the client object:
Create a new client object:
$client = new Google_Client();
Set the client application name:
$client->setApplicationName(“My Calendar App”);
Set the client access token if you already have one saved:
$client->setAccessToken($myAccessToken);
Check to see if the Access token has expired, there is a 30 second buffer, so this will return true if the token is set to expire in 30 seconds or less. The lifetime of an Access token is one hour. The Access token is actually a JSON object which contains the time of creation, it’s lifetime in seconds, and the token itself. Therefore no call is made to Google as the token has all of the information locally to determine when it will expire.
$client->isAccessTokenExpired();
If the token has expired or you have never retrieved a token then you will need to set the assertion credentials in order to get an Access token:
$client->setAssertionCredentials(new Google_AssertionCredentials(SERVICE_ACCOUNT_NAME,array(CALENDAR_SCOPE), $key,'notasecret','http://oauth.net/grant_type/jwt/1.0/bearer',$email_add));
Where:
SERVICE_ACCOUNT_NAME is the the service account email address setup earlier.
For example:’abcd1234567890#developer.gserviceaccount.com’
CALENDAR_SCOPE is the scope setup in the Google admin interface.
For example: ‘https://www.googleapis.com/auth/calendar.readonly’
$key is the content of the key file downloaded when you created the project in Google apps console.
$email_add is the Google email address of the user for whom you want to retrieve calendar data.
Set the client id:
$client-setClientId(SERVICE_CLIENT_ID);
Where:
SERVICE_CLIENT_ID is the service account client ID setup earlier.
For example: ‘abcd123456780.apps.googleusercontent.com’
Create a new calendar service object:
$cal = new Google_CalendarService($client);
Several options can be set for calendar retrieval I set a few of them in the code below, they are defined in the api document.
$optEvents = array('timeMax' => $TimeMax, 'timeMin' => $TimeMin, 'orderBy' => 'startTime', 'singleEvents' => 'True');
Get the list of calendar events and pass the above options to the call:
$calEvents = $cal->events->listEvents('primary', $optEvents);
Loop through the returned event list, the list is paged so we need to fetch pages until the list is exhausted:
foreach ($calEvents->getItems() as $event) {
// get event data
$Summary = $event->getSummary();
$description = $event->getDescription();
$pageToken = $calEvents->getNextPageToken();
if ($pageToken) { // if we got a token the fetch the next page of events.
$optParams = array('pageToken' => $pageToken);
$calEvents = $cal->events->listEvents('primary', $optParams);
} else {
break;
}
}
Get the Access token:
$myAccessToken=$client->getAccessToken();
Save the access token to your permanent store for the next time.
The language isn't important php, ruby, .net, java the process is the same. The api's console shows the Places API as supporting service accounts so it should be possible to access it.
As far as using the token please have a look at https://code.google.com/p/google-api-ruby-client/ code as the usage is clearly defined in the code repository. Doesn't make any difference if the access token is for a service account or a single user the process for using the token is the same. See the section titled "Calling a Google API" in the following link: https://developers.google.com/accounts/docs/OAuth2InstalledApp
The access token is sent in the http authorization header along with the request.For a calendar request it would look something like the following:
GET /calendar/v3/calendars/primary HTTP/1.1
Host: www.googleapis.com
Content-length: 0
Authorization: OAuth ya29.AHES6ZTY56eJ0LLHz3U7wc-AgoKz0CXg6OSU7wQA

Resources