Authorization_RequestDenied: Insufficient privileges to complete the operation." error with app, only on thirdparty AD - validation

I am trying to search for users in my own and a third party Azure Active Directoriy via the application access flow.
I use the following to get a valid token.
string authority = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}", "<AD>.onmicrosoft.com");
AuthenticationContext authContext = new AuthenticationContext(authority);
ClientCredential clientCredential = new ClientCredential(<clientId>, <appKey>);
AuthenticationResult result = await authContext.AcquireTokenAsync("https://graph.windows.net" , clientCredential);
string TokenForApplication = result.AccessToken;
I use this method to search for users with a given name.
public async Task<List<IUser>> UsersSearch(IActiveDirectoryClient client, string searchString)
{
List<IUser> usersList = null;
IPagedCollection<IUser> searchResults = null;
IUserCollection userCollection = client.Users;
searchResults = await userCollection.Where(user =>
user.UserPrincipalName.StartsWith(searchString) ||
user.GivenName.StartsWith(searchString)).Take(10).ExecuteAsync();
usersList = searchResults.CurrentPage.ToList();
return usersList;
}
This all works fine on the Azure AD where I first setup the app.
But when when I try to use the app in another Azure Active directory I get the error: Authorization_RequestDenied: Insufficient privileges to complete the operation."
In my original Azure AD I have set all the permissions I need for the app to access the graph API and search for users:
In the third party Azure AD I have gone through the Admin flow and granted the app all the needed permissions:
As far as I can see I get a valid token to each Azure AD, but I keep getting the same error whenever I try to access the third party Azure AD.
The way I change what AD I am trying to access is by changing <AD> in
string authority = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}", "<AD>.onmicrosoft.com");
I keep everything else the same.

From your screenshot , the selected permissions is for Microsoft Graph API(https://graph.microsoft.com) , but according to your code , you are acquiring token for Azure AD Graph api(https://graph.windows.net) .
If you want to use Azure AD Graph api , you should add permissions for Windows Azure Active Directory in Required permissions blade of your multi-tenant app , and do admin consent in other AAD .
if you want to use Microsoft Graph API , you should modify your code , use https://graph.microsoft.com instead of https://graph.windows.net .

Related

Google Admin API with service account -- bad credentials

I'm trying to write console app code to update Google Directory with values pulled from a SQL database. I can't seem to make the API connect successfully when using a service account. Any ideas? I've whittled the code down to just the essentials here.
static void Main(string[] args)
{
try
{
// Create a ServiceAccountCredential credential
var xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("saxxxxxxxx#directorysync-xxxxxx.iam.gserviceaccount.com")
{
Scopes = new[] {
DirectoryService.Scope.AdminDirectoryUser,
DirectoryService.Scope.AdminDirectoryUserReadonly
}
}.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMI...p9XnI4DZFO/QQJc=\n-----END PRIVATE KEY-----\n"));
// Create the service
DirectoryService service = new DirectoryService(
new BaseClientService.Initializer()
{
HttpClientInitializer = xCred,
}
);
var listReq = service.Users.List();
listReq.Domain = "mycompany.com";
listReq.MaxResults = 100;
listReq.OrderBy = UsersResource.ListRequest.OrderByEnum.Email;
Users results = listReq.Execute();
// process the users list here...
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
The error happens at the .Execute() line:
Google.Apis.Requests.RequestError
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
I've tried code seen elsewhere (How to login to Google API with Service Account in C# - Invalid Credentials) to bring in the whole contents of the .JSON file that contains the credentials for the service account; that made no difference. I'm not the google domain admin, but the admin built the credential and promises that it does, indeed, have rights to the user resources. I'm utterly lost at what's not right.
Either:
You're missing the email address of an admin user to impersonate.
An Admin of the domain needs to assign user management privileges to the service account.
Background
There's two HTTP requests involved in making a Google API request with a service account:
Using the service account's credentials to request an access token from the Google OAuth 2.0 server. A HTTP POST is sent with a JWT signed with the private key of the service account. If successful, an access token is returned which is valid for one hour.
Making the service API request (Admin SDK Directory API in this case) using the OAuth access token obtained from the last step.
The error message you provided is not listed in the JWT error codes page so step 1 is working, and the error is coming from step 2 - the request to Directory API.
You should be able to confirm this using an HTTPS request interceptor like mitmproxy.
You'd get a 403 error for the Directory API users.list method for a few reasons:
You've authenticated as a service account, but that service account has no admin privileges which are sufficient for the request.
You've authenticated as a user in the domain (either using a service account with impersonation with the sub parameter in the JWT request, or using three-legged interactive OAuth), but that user has no admin privileges which are sufficient for the request.
You've authenticated as a service account or user with sufficient privileges, but the domain parameter is not owned by the customer for which the service account or user is associated with.
In your sample code, there is no email address of a domain user (admin) specified (the email address for ServiceAccountCredential.Initializer is actually the OAuth client name of the service account, not a real email address), so it is either case 1 or 3.
To address case 1
A service account has no association with a domain or access privileges by default.
An admin can open Admin console, go to "Account > Admin roles > "User Management Admin" > Admins > Assign service accounts", then:
Add the service account name (looks like an email address, ending #gserviceaccount.com).
Click "Assign role".
Now, the service account does not need to impersonate another admin in the domain, it will directly have admin privileges.
A couple of side-effects of this:
The service account cannot be "suspended" as such, only removed from the admin role.
Audit logging of the actions will show the service account name in the "Reporting > Audit > Admin" section of Admin console.
To address case 2
You may have meant to impersonate an admin user in the domain, which you could do by adding:
, User = "admin#example.com"
- after the Scopes array passed to ServiceAccountCredential.Initializer.
This adds the email address of a user to the JWT request to retrieve an access token for that user (by adding the sub field to the claim-set of the JWT assertion).
To address case 3
Replace the "mycompany.com" on the line:
listReq.Domain = "mycompany.com";
- with a domain that is associated with the customer, or instead, remove that line and add:
listReq.Customer = "my_customer";
(literally my_customer - see users.list query-parameters)
- Which will list users on all domains associated with the customer (Google Workspace and Cloud Identity customers can have many secondary domains).

How can I authenticate against ADXProxy using app key authentication?

I am trying to access an Azure Application Insights resource via Redash, using the (preview) ADXProxy feature.
I've created an App Registration in Azure, and I've got some proof-of-concept python code which can successfully access my Application Insights resource and execute a Kusto query (traces | take 1) using an application token:
import azure.kusto
import azure.kusto.data.request
import msal
cluster = 'https://ade.applicationinsights.io/subscriptions/<MY_SUBSCRIPTION>/resourcegroups/<MY_RESOURCE_GROUP>/providers/microsoft.insights/components/<MY_APP_INSIGHTS_RESOURCE>'
app_id = '<MY_APP_ID>'
app_key = '<MY_SECRET>'
authority_id = '<MY_AAD_SUBSCRIPTION_ID>'
def run():
app = msal.ConfidentialClientApplication(
client_id=app_id,
client_credential=app_key,
authority='https://login.microsoftonline.com/<MY_AAD_SUBSCRIPTION_ID>')
token = app.acquire_token_for_client(['https://help.kusto.windows.net/.default'])
kcsb = azure.kusto.data.request.KustoConnectionStringBuilder.with_aad_application_token_authentication(
connection_string=cluster,
application_token=token['access_token']
)
client = azure.kusto.data.request.KustoClient(kcsb)
result = client.execute('<MY_APP_INSIGHTS_RESOURCE>', 'traces | take 1')
for res in result.primary_results:
print(res)
return 1
if __name__ == "__main__":
run()
However, Redash doesn't support application token authentication: it uses application key authentication, making a call like:
kcsb = azure.kusto.data.request.KustoConnectionStringBuilder.with_aad_application_key_authentication(
connection_string = cluster,
aad_app_id = app_id,
app_key = app_key,
authority_id = '<MY_AAD_SUBSCRIPTION_ID>'
)
I can't successfully connect to my App Insights resource using this type of flow. If I substitute this KustoConnectionStringBuilder into my program above, I get an exception telling me:
The resource principal named https://ade.applicationinsights.io was not found in the tenant named <MY_AAD_SUBSCRIPTION_ID>. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
Is there something I can do in code or Azure Portal configuration to connect my 'tenant' to the ade.applicationinsights.io resource principal and get this connection working?
Adxproxy supports only tokens minted by Azure Active Directory (AAD). The token must be created for an Azure Data Explorer cluster (ADX), that you own. If you don't have your own ADX cluster, and for whatever reason you want to access your Application Insights resources via Adxproxy, you can always authenticate to 'https://help.kusto.windows.net' and use that token.

Google Drive API's file Export endpoint is failing with API Key authentication?

Has anything changed recently with the Google Drive APIs and specifically the Export function, which would cause it to fail while using API Key access after 27-Mar-2018?
I have a Windows Service that creates and sends daily course emails for an educational group. The source content for each email is stored in a Google Drive, as a Google Doc, so that the faculty can update the course content easily.
This has been working flawlessly for the past year, but suddenly stopped working about 27-Mar-2018. Since then, I can retrieve the file details;
_googleDriveHtmlContent.LoadFile(
fileId
);
But not the contents. When I Export the file as HTML, I immediately get a DownloadStatus.Failed from the ProgressChanged handler;
var request = _driveService.Files.Export(
fileId,
"text/html"
);
I'm using API keys for security, rather than OAuth, since it's a UI-less service. To do this I need to mark the file folders as publicly accessible - specifically I'm using "Accessible to everyone, with link." This has been working great.
I've updated to the latest API v3 libraries through NuGet, with no change in behavior.
Using Google's API Explorer, I'm seeing a similar behavior.
I can retrieve my file successfully using the API Explorer with the get endpoint.
https://developers.google.com/drive/v3/reference/files/get
fileId 1AIuGhzXsNuhhi0PMA1pblh0l5CCDaa1nPj8t_dasi_c
Authentication: API key (uses a "demo API key")
But with the export endpoint, I get an Internal Error (500)-
https://developers.google.com/drive/v3/reference/files/export
fileId 1AIuGhzXsNuhhi0PMA1pblh0l5CCDaa1nPj8t_dasi_c
mimeType: text/html
Authentication: API key (uses a "demo API key")
Changing the Authentication in the API Explorer to OAuth 2.0, and approving access, then returns a successful 200 result with the file HTML. However I'm unable to do that since I'm accessing the API via a UI-less service.
Has anything changed recently with the Google Drive APIs and specifically the Export function, which would cause it to fail while using API Key access after 27-Mar-2018?
Its possible but its most likely a stealth change that you will not get any official word on. Not that long ago i saw someone posting a similar question they were using an API key to update a Google sheet and it suddenly stopped working.
IMO if google has changed this its probably a good thing. API keys are meant for accessing public data. Setting a document to public is a really bad idea if anyone did manage to find the file ID of your document they would then be able to update your document.
Suggestion:
What you should be using is a Service account. Service accounts are dummy users by creating service account credentials on Google developer console and then taking the service account email address you can share the file on Google Drive with the service account granting it access to said file without the need of making the file public.
You havent specified what language you are using but you said you were making a windows service so i am going to assume you are using .net. Here is an example of service account authencation with the Google .net client library.
public static DriveService AuthenticateServiceAccount(string serviceAccountEmail, string serviceAccountCredentialFilePath, string[] scopes)
{
try
{
if (string.IsNullOrEmpty(serviceAccountCredentialFilePath))
throw new Exception("Path to the service account credentials file is required.");
if (!File.Exists(serviceAccountCredentialFilePath))
throw new Exception("The service account credentials file does not exist at: " + serviceAccountCredentialFilePath);
if (string.IsNullOrEmpty(serviceAccountEmail))
throw new Exception("ServiceAccountEmail is required.");
// For Json file
if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".json")
{
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(scopes);
}
// Create the Analytics service.
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive Service account Authentication Sample",
});
}
else if (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() == ".p12")
{ // If its a P12 file
var certificate = new X509Certificate2(serviceAccountCredentialFilePath, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromCertificate(certificate));
// Create the Drive service.
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive Authentication Sample",
});
}
else
{
throw new Exception("Unsupported Service accounts credentials.");
}
}
catch (Exception ex)
{
throw new Exception("CreateServiceAccountDriveFailed", ex);
}
}
}
code ripped from serviceaccount.cs. Assuming that you were already using the Google .net client library the service this method returns will be the same drive service you were using with an api key.
Once you have granted your service account access to the file it will be able to access the file when ever it needs there is no authentication needed as you have preauthorized it by sharing the file with it.

How to access Google API in combination with Azure AD single-sign on

I have a web application running on Azure. The web application authenticates the users via OpenID Connect from a Azure Active Directory tenant.
Azure Sample on GitHub.
On the Azure Active Directory tenant I have integrated Google Apps and configured single sing-on to Google Apps and automated user provisioning. Tutorial: How to integrate Google Apps with Azure Active Directory.
In my web application I would like to access user content from Google Apps (e.g. files on Google Drive) of the signed in user via Google API.
Is it possible to do this with the help of the setup single sign-on federation, so that the user only needs to sign in to the web application/Azure AD and for the Web API call there is no need for a further sign in, e.g. by using a token optained by Azure AD for accessing the Google Web API?
Tokens obtained from Azure AD cannot be used directly against Google API. However if you integrated Azure AD and Google Apps you should be able to go through the google token acquisition process without gathering user credentials again. You might want to go through an authorization code flow for getting tokens from google, and inject in the request information that would help to leverage your existing session. Typical examples are passing your user's UPN (via login_hint query parameter) and tenant (domain_hint). However I don't know if the google authorization endpoint will pass those along, you'll need to consult the google api documentation.
I ended up with two solutions:
a) Service Account:
Accessing the users data with a service account on behalf of a user.
For this you have to setup a service account: Using OAuth 2.0 for Server to Server Applications
private static ServiceAccountCredential GetServiceAccountCredential(string user)
{
const string privateKey = "<PRIVATEKEY>";
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("<SERVICEACOUNTEMAIL>")
{
Scopes = new[] {DriveService.Scope.Drive},
User = user
}.FromPrivateKey(privateKey));
return credential;
}
b) User:
Accessing the users data with the user. For this you have to register your app to get the client ID and secret: Using OAuth 2.0 for Web Server Applications
private static UserCredential GetUserCredential(string user)
{
ClientSecrets secrets = new ClientSecrets
{
ClientId = "<CLIENTID>",
ClientSecret = "<CLIENTSECRET>"
};
IDataStore credentialPersistanceStore = new FileDataStore("Drive.Sample.Credentials");
Task<UserCredential> result = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
new[] {DriveService.Scope.Drive},
user,
CancellationToken.None,
credentialPersistanceStore);
result.Wait();
UserCredential credential = result.Result;
return credential;
}
With the credentials I can request the files from Drive:
Claim emailClaim = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email);
IConfigurableHttpClientInitializer credential = GetServiceAccountCredential(emailClaim.Value);
//IConfigurableHttpClientInitializer credential = GetUserCredential(emailClaim.Value);
var service = new DriveService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "My App"
});
FileList list = service.Files.List().Execute();
I am not yet sure which option I will use. Maybe you have some advices or suggestions.

Getting a 403 - Forbidden for Google Service Account

I am trying to get an access token for Google Service Account. Following is my code -
String SERVICE_ACCOUNT_EMAIL = "edited#developer.gserviceaccount.com";
List scope = new ArrayList();
scope.add("https://www.googleapis.com/auth/admin.directory.user");
String keyFile = "C:\\edited-privatekey.p12";
HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(scope)
.setServiceAccountPrivateKeyFromP12File(new java.io.File(keyFile))
.build();
credential.refreshToken();
String accessTokens = credential.getAccessToken();
Although the code works fine and I do get an access token, when I try to use it to 'GET' a Google Apps User using the Google Directory APIs, I get a 403 - Forbidden response code. Could someone please help?
I know the code for GET user is correct because it works fine with the access token generated by Google Apps Admin.
You need to set an admin account with:
.setServiceAccountUser(some_admin_email)
And make sure your App (with the correct scopes) is granted access in the cpanel.
Proceed to https://admin.google.com . Login and add Security control if not exists from More Controls.
Click on Security->Advance Settings->Manage ThirdParty OAuth Client Access and check that those scopes are added(comma separated) for your xxxxxxxxxxxxxxxxxx.apps.googleusercontent.com service account id/client id.
You have to enable the specific api before using it inside https://console.developers.google.com/ library, to make it work with your api key.
watch the video https://www.youtube.com/watch?v=s_G5CnAu69M.

Resources