Authentication with p12 key give error User cannot access account - c#

I have a MVC web-application that update the products on the eCommerce site. Now we enrolled into the google merchant center and my objective is to update the products at the same time. I am using the Google.Apis.ShoppingContent.v2_1 API.
This is my API Credentials
This is my API service account
I have used the google account email address for the user as well as the service account email but with the same result.
I have the following
static string[] Scopes = { ShoppingContentService.Scope.Content};
static string P12Secret = #"~\Content\XXXXXX-5cab03fb904a.p12";
static string userName = "serviceaccount#gserviceaccount.com";
static public async Task RunTest2()
{
var certificate = new X509Certificate2(P12Secret, "notasecret", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(userName)
{
Scopes = Scopes
}.FromCertificate(certificate));
var service = new ShoppingContentService(new BaseClientService.Initializer
{
ApplicationName = ApplicationName,
HttpClientInitializer = credential
});
try
{
var result = await service.Products.List("My MerchantID").ExecuteAsync();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
When I execute var result = await service.Products.List("My MerchantID").ExecuteAsync(); I get the error
e.Message = "Google.Apis.Requests.RequestError\nUser cannot access account 123456789 [401]\r\nErrors [\r\n\tMessage[User cannot access account 123456789 ] Location[ - ] Reason[auth/account_access_denied] Domain[content.ContentErrorDomain]\r\n]\r\n"

Documentaiton
Service accounts are special Google accounts that can be used by applications to access Google APIs programmatically via OAuth 2.0. A service account uses an OAuth 2.0 flow that does not require human authorization. Instead, it uses a key file that only your application can access. This guide discusses how to access the Content API for Shopping with service accounts.
Service accounts need to be pre authorized. If its not then it doesnt have access to any data.
User cannot access account 123456789
Means that it does not have access you have forgotten to grant it access. Check the Documentaiton look for the section below follow all of the steps.

Related

Create Google Calendar Event With Credentials To Impersonate User with Google Calendar API Library .NET 7 C#

In .NET 7, without specifying a User in the
ServiceAccountCredential.Initializer(original.Id)
block, my calendar event can be created with no errors. But if I specify a user as shown below, I get this error:
Error: unauthorized_client,
Description: Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
My service account has domain-wide delegation, allowed scope of
calendar via OAuth2, and I have specifically granted the SA access to the user's calendar (when I try on accounts that have not specifically granted access, I can't create a calendar entry at all).
How do I get appropriate credentials for the service account to impersonate the end user within my domain?
Credit for getting this far to creating-a-serviceaccountcredential-for-a-user-from-a-systems-account
Previous answers don't seem to be working on latest release.
In my API code, which calls the authentication service then creates the event:
Google.Apis.Calendar.v3.CalendarService CS = await SA.AuthenticateServiceAccountSO();
var result = CS.Events.Insert((Event)newEvent, clientemail).Execute();
In Service AuthenticateServiceAccountSO():
public async Task<CalendarService> AuthenticateServiceAccountSO()
{
await Task.Delay(1);
string? serviceAccountCredentialFilePath = _config.GetValue<string>("ServiceAccountFilePath");
string? ClientEmail = _config.GetValue<string>("TestingUserAccountEmail");
string[] scopes = new string[] { CalendarService.Scope.Calendar };
ServiceAccountCredential original = (ServiceAccountCredential)GoogleCredential.FromFile(serviceAccountCredentialFilePath).UnderlyingCredential;
var initializer = new ServiceAccountCredential.Initializer(original.Id)
{
User = ClientEmail,
Key = original.Key,
Scopes = scopes
};
var credential = new ServiceAccountCredential(initializer);
CalendarService service = new(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar_Appointment event Using Service Account Authentication"
});
return service;
}
You're currently copying relatively little of the service account credential - in particular, there's no KeyId. But you can do it all much more simply anyway:
var credential = GoogleCredential.FromFile(serviceAccountCredentialFilePath)
.CreateScoped(CalendarService.Scope.Calendar)
.CreateWithUser(ClientEmail);

Getting All Users and Mailboxes with One API

I am supporting an IT admin, who is himself facilitating the use of compliance software. To that end, I have written some C# code that iterates through all users in a directory, and performs operations on their messages. My current solution uses two different APIs to accomplish this (code snippet below), but obviously it would be better to only use one API. Having scanned through other posts here, I failed to find a satisfactorily clear answer on how to make that happen. My app is a service account, with Google Workspace domain-wide delegation enabled. How can I use only one API to accomplish what I am doing with two?
[working code snippet]
string domain; // domain name
string adminEmail; // admin e-mail
string directoryClientEmail; // client e-mail for Directory API
string directoryPrivateKey; // private key for Directory API
string directoryPrivateKeyId; // private key ID for Directory API
string gmailClientEmail; // client e-mail for Gmail API
string gmailPrivateKey; // private key for Gmail API
string gmailPrivateKeyId; // private key ID for Gmail API
CancellationToken cancellationToken; // a cancellation token
DirectoryService directoryClient = new DirectoryService(
new BaseClientService.Initializer
{
HttpClientInitializer = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(creds.DirectoryClientEmail)
{
User = adminEmail,
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
Key = RSA.Create(directoryPrivateKey),
KeyId = directoryPrivateKeyId
}.FromPrivateKey(directoryPrivateKey))
});
UsersResource.ListRequest userListRequest = directoryClient.Users.List();
userListRequest.Domain = domain;
Users userList = await userListRequest.ExecuteAsync(cancellationToken);
foreach (User user in userList)
{
GmailService gmailClient = new GmailService(
new BaseClientService.Initializer
{
HttpClientInitializer = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(gmailClientEmail)
{
User = user.PrimaryEmail,
Scopes = new[] { GmailService.Scope.MailGoogleCom },
Key = RSA.Create(gmailPrivateKey),
KeyId = gmailPrivateKeyId
}.FromPrivateKey(gmailPrivateKey))
});
ListRequest listRequest = new ListRequest(gmailClient, "me");
ListMessageResponse listMessageResponse = await listRequest.ExecuteAsync(cancellationToken);
foreach (Message message in listMessageResponse.Messages)
{
// do stuff
}
}
To achieve what you want, you can't only use one API. As the Gmail API will not give you the users in the domain, but you can get a user's messages with it; which you need. So Gmail API is a requirement.
Then if you want an up-to-date domain users list, then you need to use the Directory API, so unless you have a list of users somewhere else, you require this API too.

B2B access in multi tenant application

I have succesfully setup a multi tenant application.
For now, I am able to authenticate the user and use tokens to access other resources. (Microsoft Graph & Microsoft AD Graph)
Now I want to get B2B working.
Current flow:
- User signs in
- AuthorizationCodeReceived gets the acquires the token (via $commonAuthority endpoint)
- When requesting a token for the Ad Graph, I am using the $tenantAuthority
This works perfectly when $tenantAuthority is the same tenant authority as the one where the account was created in.
However, if I login with another user (from another tenant, given trust to the actual tenant) and use $tenantAuthority = trusted authority, then I always the following error:
Failed the refresh token:
AADSTS65001: The user or administrator has not consented to use the application with ID
If I change $tenantAuthority to the 'source' tenant authority where the user was created in, everything works fine.
Any help would be greatly appreciated.
Update: Code sample
App has two tenants (tenantA en tenantB) and I will use a user from tenantB with tenantA given a trust to this user.
AuthorizationCodeReceived = async context =>
{
TenantContext.TenantId = "someguid";
var tenantId =
TenantContext.TenantId;
// get token cache via func, because the userid is only known at runtime
var getTokenCache = container.Resolve<Func<string, TokenCache>>();
var userId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var tokenCache = getTokenCache(userId);
var authenticationContext = new AuthenticationContext($"{configuration.Authority}",
tokenCache);
await authenticationContext.AcquireTokenByAuthorizationCodeAsync(
context.Code,
new Uri(context.Request.Uri.GetLeftPart(UriPartial.Authority)),
new ClientCredential(configuration.ClientId, configuration.ClientSecret),
configuration.GraphResourceId);
}
This code works perfectly. Login in with a user from both tenants works perfectly.
But when I need the Graph Service Client or ActiveDirectoryClient, I need to obtain access tokens to been able to address an api for a certain tenant. I retrieve the access tokens like this:
public IGraphServiceClient CreateGraphServiceClient()
{
var client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage =>
{
Logger.Debug("Retrieving authentication token to use in Microsoft Graph.");
string token;
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var authenticationContext =
new AuthenticationContext($"{_configuration.TenantAuthorityPrefix}{currentUserHomeTenantId}",
_tokenCacheFactoryMethod(currentUserObjectId));
var clientCredential = new ClientCredential(_configuration.ClientId, _configuration.ClientSecret);
try
{
token = await GetTokenSilently(authenticationContext, _configuration.GraphResourceId, currentUserObjectId);
}
catch (AdalSilentTokenAcquisitionException e)
{
Logger.Error("Failed to retrieve authentication token silently, trying to refresh the token.", e);
var result = await authenticationContext.AcquireTokenAsync(_configuration.GraphResourceId, clientCredential);
token = result.AccessToken;
}
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AuthenticationHeaderKeys.Bearer, token);
}));
return client;
}
public IActiveDirectoryClient CreateAdClient()
{
var currentUserHomeTenantId = TenantContext.TenantId;
var currentUserObjectId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.ObjectIdentifier).Value;
var graphServiceUrl = $"{_configuration.AdGraphResourceId}/{currentUserHomeTenantId}";
var tokenCache = _tokenCacheFactoryMethod(currentUserObjectId);
var client = new ActiveDirectoryClient(new Uri(graphServiceUrl),
() => GetTokenSilently(
new AuthenticationContext(
$"{_configuration.TenantAuthorityPrefix}{ClaimsPrincipal.Current.FindFirst(ClaimTypes.TenantId).Value}", tokenCache
),
_configuration.AdGraphResourceId, currentUserObjectId
));
return client;
}
When I do a request with one of the two client SDK's, I got the following error:
Failed the refresh token: AADSTS65001: The user or administrator has not consented to use the application with ID.
Changing the catch method when retrieving the Token did the trick:
if(e.ErrorCode == "failed_to_acquire_token_silently")
{
HttpContext.Current.Response.Redirect(authenticationContext.GetAuthorizationRequestUrlAsync(resourceId, _configuration.ClientId, new Uri(currentUrl),
new UserIdentifier(currentUserId, UserIdentifierType.UniqueId), string.Empty);
}
I don't see that you mention that so: in a B2B collaboration you've to invite user from other tenant first. The steps are like that:
invite and authorize a set of external users by uploading a comma-separated values - CSV file
Invitation will be send to external users.
The invited user will either sign in to an existing work account with Microsoft (managed in Azure AD), or get a new work account in Azure AD.
After signed in, user will be redirected to the app that was shared with them
That works perfectly in my case.
Regarding some problems which I've detect:
Trailing "/" at the end of the active directory resource - try to remove it as this may cause problems. Bellow you will find some code to get authentication headers:
string aadTenant = WebServiceClientConfiguration.Settings.ActiveDirectoryTenant;
string clientAppId = WebServiceClientConfiguration.Settings.ClientAppId;
string clientKey = WebServiceClientConfiguration.Settings.ClientKey;
string aadResource = WebServiceClientConfiguration.Settings.ActiveDirectoryResource;
AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant);
ClientCredential clientCredential = new ClientCredential(clientAppId, clientKey);
UserPasswordCredential upc = new UserPasswordCredential(WebServiceClientConfiguration.Settings.UserName, WebServiceClientConfiguration.Settings.Password);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(aadResource, clientAppId, upc);
return authenticationResult.CreateAuthorizationHeader();
Applications provisioned in Azure AD are not enabled to use the OAuth2 implicit grant by default. You need to explicitly opt in - more details can be found here: Azure AD OAuth2 implicit grant

How do I connect a server service to Dynamics Online

I am modifying an internal management application to connect to our online hosted Dynamics 2016 instance.
Following some online tutorials, I have been using an OrganizationServiceProxy out of Microsoft.Xrm.Sdk.Client from the SDK.
This seems to need a username and password to connect, which works fine, but I would like to connect in some way that doesn't require a particular user's account details. I don't think the OAuth examples I've seen are suitable, as there is no UI, and no actual person to show an OAuth request to.
public class DynamicsHelper
{
private OrganizationServiceProxy service;
public void Connect(string serviceUri, string username, string password)
{
var credentials = new ClientCredentials();
credentials.UserName.UserName = username;
credentials.UserName.Password = password;
var organizationUri = new Uri(serviceUri);
this.service = new OrganizationServiceProxy(organizationUri, null, credentials, null);
}
}
Is there a way to connect with an application token or API key?
I've found that to do this successfully, you'll need to setup all of the following:
Create an application registration in Azure AD:
grant it API permissions for Dynamics, specifically "Access Dynamics 365 as organization users"
give it a dummy web redirect URI such as http://localhost/auth
generate a client secret and save it for later
Create a user account in Azure AD and give it permissions to Dynamics.
Create an application user record in Dynamics with the same email as the non-interactive user account above.
Authenticate your application using the user account you've created.
For step 4, you'll want to open an new incognito window, construct a url using the following pattern and login using your user account credentials in step 2:
https://login.microsoftonline.com/<your aad tenant id>/oauth2/authorize?client_id=<client id>&response_type=code&redirect_uri=<redirect uri from step 1>&response_mode=query&resource=https://<organization name>.<region>.dynamics.com&state=<random value>
When this is done, you should see that your Dynamics application user has an Application ID and Application ID URI.
Now with your ClientId and ClientSecret, along with a few other organization specific variables, you can authenticate with Azure Active Directory (AAD) to acquire an oauth token and construct an OrganizationWebProxyClient. I've never found a complete code example of doing this, but I have developed the following for my own purposes. Note that the token you acquire has an expiry of 1 hr.
internal class ExampleClientProvider
{
// Relevant nuget packages:
// <package id="Microsoft.CrmSdk.CoreAssemblies" version="9.0.2.9" targetFramework="net472" />
// <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="4.5.1" targetFramework="net461" />
// Relevant imports:
// using Microsoft.IdentityModel.Clients.ActiveDirectory;
// using Microsoft.Crm.Sdk.Messages;
// using Microsoft.Xrm.Sdk;
// using Microsoft.Xrm.Sdk.Client;
// using Microsoft.Xrm.Sdk.WebServiceClient;
private const string TenantId = "<your aad tenant id>"; // from your app registration overview "Directory (tenant) ID"
private const string ClientId = "<your client id>"; // from your app registration overview "Application (client) ID"
private const string ClientSecret = "<your client secret>"; // secret generated in step 1
private const string LoginUrl = "https://login.microsoftonline.com"; // aad login url
private const string OrganizationName = "<your organization name>"; // check your dynamics login url, e.g. https://<organization>.<region>.dynamics.com
private const string OrganizationRegion = "<your organization region>"; // might be crm for north america, check your dynamics login url
private string GetServiceUrl()
{
return $"{GetResourceUrl()}/XRMServices/2011/Organization.svc/web";
}
private string GetResourceUrl()
{
return $"https://{OrganizationName}.api.{OrganizationRegion}.dynamics.com";
}
private string GetAuthorityUrl()
{
return $"{LoginUrl}/{TenantId}";
}
public async Task<OrganizationWebProxyClient> CreateClient()
{
var context = new AuthenticationContext(GetAuthorityUrl(), false);
var token = await context.AcquireTokenAsync(GetResourceUrl(), new ClientCredential(ClientId, ClientSecret));
return new OrganizationWebProxyClient(new Uri(GetServiceUrl()), true)
{
HeaderToken = token.AccessToken,
SdkClientVersion = "9.1"
};
}
public async Task<OrganizationServiceContext> CreateContext()
{
var client = await CreateClient();
return new OrganizationServiceContext(client);
}
public async Task TestApiCall()
{
var context = await CreateContext();
// send a test request to verify authentication is working
var response = (WhoAmIResponse) context.Execute(new WhoAmIRequest());
}
}
With Microsoft Dynamics CRM Online or internet facing deployments
When you use the Web API for CRM Online or an on-premises Internet-facing deployment (IFD)
you must use OAuth as described in Connect to Microsoft Dynamics CRM web services using OAuth.
Before you can use OAuth authentication to connect with the CRM web services,
your application must first be registered with Microsoft Azure Active Directory.
Azure Active Directory is used to verify that your application is permitted access to the business data stored in a CRM tenant.
// TODO Substitute your correct CRM root service address,
string resource = "https://mydomain.crm.dynamics.com";
// TODO Substitute your app registration values that can be obtained after you
// register the app in Active Directory on the Microsoft Azure portal.
string clientId = "e5cf0024-a66a-4f16-85ce-99ba97a24bb2";
string redirectUrl = "http://localhost/SdkSample";
// Authenticate the registered application with Azure Active Directory.
AuthenticationContext authContext =
new AuthenticationContext("https://login.windows.net/common", false);
AuthenticationResult result =
authContext.AcquireToken(resource, clientId, new Uri(redirectUrl));
P.S: Concerning your method, it is a best practice to not to store the password as clear text, crypt it, or encrypt the configuration sections for maximum security.
See walkhrough here
Hope this helps :)
If I understand your question correctly, you want to connect to Dynamics 2016 (Dynamics 365) through a Registerd Azure Application with ClientId and Secret, instead of Username and Password. If this is correct, yes this is possible with the OrganizationWebProxyClient . You can even use strongly types assemblies.
var organizationWebProxyClient = new OrganizationWebProxyClient(GetServiceUrl(), true);
organizationWebProxyClient.HeaderToken = authToken.AccessToken;
OrganizationRequest request = new OrganizationRequest()
{
RequestName = "WhoAmI"
};
WhoAmIResponse response = organizationWebProxyClient.Execute(new WhoAmIRequest()) as WhoAmIResponse;
Console.WriteLine(response.UserId);
Contact contact = new Contact();
contact.EMailAddress1 = "jennie.whiten#mycompany.com";
contact.FirstName = "Jennie";
contact.LastName = "White";
contact.Id = Guid.NewGuid();
organizationWebProxyClient.Create(contact);
To get the AccessToken, please refer to the following post Connect to Dynamics CRM WebApi from Console Application.
Replace line 66 (full source code)
authToken = await authContext.AcquireTokenAsync(resourceUrl, clientId, new Uri(redirectUrl), new PlatformParameters(PromptBehavior.Never));
with
authToken = await authContext.AcquireTokenAsync( resourceUrl, new ClientCredential(clientId, secret));
You can also check the following Link Authenticate Azure Function App to connect to Dynamics 365 CRM online that describes how to secure your credentials using the Azure Key Vault.

How to retrive events from google calendar API fro c# .net using API key

I am able to retrieve event from google calendar API using OAuth authentication by code given here, but how to code for getting event information using API key.
API key is for use with PUBLIC data. For an API key to work with a calendar I would assume said calendar would have to be set to public.
var service = new CalendarService(new BaseClientService.Initializer()
{
ApiKey = "XXX",
ApplicationName = "xyz",
});
var events = service.Events.List("en.danish#holiday#group.v.calendar.google.com").Execute();
foreach (var myEvent in events.Items)
{
Console.WriteLine(string.Format("Event: {0} Start: {1} End: {2}", myEvent.Summary, myEvent.Start.DateTime.ToString(), myEvent.End.DateTime.ToString()));
}
I have an example / tutorial of accessing events on public calendars here
I think you should consider looking at a service account instead this will allow you to grant the service account access to your calendar and you wont need to go though the Oauth2 authentication which I suspect is what you don't want to be doing.
string[] scopes = new string[] {
CalendarService.Scope.Calendar, // Manage your calendars
CalendarService.Scope.CalendarReadonly // View your Calendars
};
var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail) {
Scopes = scopes
}.FromCertificate(certificate));
I have a tutorial on how to us a service account here

Categories

Resources