GmailService create Watch() getting User not authorized error - c#

this this code snniped:
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,//read from client secret.json file
Scopes,
"user",
CancellationToken.None).Result;
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
UsersResource.LabelsResource.ListRequest request = service.Users.Labels.List("me");
WatchRequest body = new WatchRequest()
{
TopicName = "projects/push-notifications-ver3/topics/mytopic",
LabelIds = new[] {"INBOX"}
string userId = "me";
UsersResource.WatchRequest watchRequest = service.Users.Watch(body, userId);
WatchResponse test = watchRequest.Execute();
Getting Error:
Error sending test message to Cloud PubSub projects/push-notifications-ver3/topics/mytopic : User not authorized to perform this action. [403]
Topic was created with subscription, permission was given to current user as owner of topic
Any suggestion why user not authorized ?

Have you completed the OAuth process for the given user? Also, are you replacing the word "user" in the method AuthorizeAsync() with your authenticated user? If yes, then try to do it with new client secrets file and also check if PubSub Scope is present in the variable scope.
I face a similar issue and it turned out to be one of these issues. Might work for you as well.

Related

AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a role for the application '{API GUID}'(DEV-API)

I want to access one API by its Client Credential directly not via any web application
private async Task<string> GetAutheticationToken(string APITypeSelected, string APIKeySelected=null)
{
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:AADTenant"];
string appKey = ConfigurationManager.AppSettings[APIKeySelected];
string apiID = ConfigurationManager.AppSettings[APITypeSelected];
//appKey = HttpUtility.UrlEncode(appKey);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
using (HttpClient client = new HttpClient())
{
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext = null;
ClientCredential clientCredential = null;
authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(authority);
//encodeURIComponent(client_secret);
clientCredential = new ClientCredential(apiID, appKey);
AuthenticationResult authResult = null;
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
return authResult.AccessToken;
}
}
while executing I am getting bellow error(AADSTS501051) in this line
authResult = await authContext.AcquireTokenAsync(apiID, clientCredential);
AADSTS501051: Application '{API GUID}'(DEV-API) is not assigned to a
role for the application '{API GUID}'(DEV-API).
Do I have to give API permission to itself.
What I need to do.
Thanks,
First you need to make a user role for application if app assignment is required. if not there is no problem. If app assignment is required, Go back to api permission and in my api give permission for the created role, see Microsoft documentation url
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-registration
Ahh so you want an access token to the API itself? Not sure if that's possible..
If this in another app, it should be registered as another app in Azure AD.
It can then require application permissions on the API and call it via client credentials.
You can see how to define permissions here: https://joonasw.net/view/defining-permissions-and-roles-in-aad
If this is within the same app, it sounds odd that it would acquire a token for itself.
This error message indicates that you need to add an "App role" to your app registration. You can do so by first adding a new App role on {API GUID}
and then assign the app {API GUID} this role (don't forget to give admin consent)
Essentially what is happening here is that your app registration {API GUID} got a role on {API GUID} to create access tokens for the audience {API GUID}, so: itself.
When you use "authContext.AcquireTokenAsync(apiID, clientCredential);" to get the access token, you need to use identifierUri of your ad application as resource.
For example:
string tenantId = "your tenant id or name, for example: hanxia.onmicrosoft.com";
string clientId = "your client id";
string resource = "the identifierUri of your ad application ";
string clientSecret = "";
ClientCredentia clientCredentia = new ClientCredentia(clientId,clientSecret);
var context = new AuthenticationContext("https://login.microsoftonline.com/" + tenantId);
AuthenticationResult result = context.AcquireTokenAsync(resource, clientCredentia);
For more details, please refer to the document.

Integrating with Google Admin SDK in C#

I'm currently trying to integrate with the Google Admin SDK via C# so we can manage users via our own system. However, when running the project I get the error: Unauthorized Client.
Things I have already done via a super admin account:
Setup Service Account
Enabled GSuite domain-wide delegation on service Account
Enabled API Access
Added the Service Accounts client ID to API Client Access with the scope (https://www.googleapis.com/auth/admin.directory.user)
Here's the code that i'm using.
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(_googleServiceSettings.Client_Email)
{
ProjectId = _googleServiceSettings.Project_Id,
User = "superadmin#google.com",
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser }
}.FromPrivateKey(_googleServiceSettings.Private_Key));
var service = new DirectoryService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Test API"
});
var request = service.Users.Get("user#google.com");
var result = await request.ExecuteAsync();
The full error i'm getting is
An unhandled exception has occurred while executing the request.
Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.", Uri:""
Example code that will print some information about a user.
The important item is the class Google.Apis.Admin.Directory.directory_v1.Data.User
Documentation link.
Your error is caused by not creating the credentials correctly. Usually, an issue with scopes when creating the credentials. I am assuming that you have Domain-Wide Delegation setup correctly for the service account.
I am also assuming that the user that you are impersonating is a G Suite Super Admin. If not, you will see a 403 error for service.Users.Get().
The file service_account.json is a normal JSON file that you downloaded from the Google Console (or created with gcloud).
The user user1#example.com is the email address for the G Suite user for which information will be displayed.
The user admin#example.com is the G Suite Super Admin.
using Google.Apis.Auth.OAuth2;
using Google.Apis.Admin.Directory.directory_v1;
using Google.Apis.Admin.Directory.directory_v1.Data;
using Google.Apis.Services;
using System;
using System.IO;
// dotnet add package Google.Apis.Admin.Directory.directory_v1
// Tested with version 1.39.0.1505
// Google.Apis.Admin.Directory.directory_v1.Data.User
// https://developers.google.com/resources/api-libraries/documentation/admin/directory_v1/csharp/latest/classGoogle_1_1Apis_1_1Admin_1_1Directory_1_1directory__v1_1_1Data_1_1User.html
namespace Example
{
class Program
{
static void Main(string[] args)
{
// Service Account with Domain-Wide delegation
var sa_file = "service_account.json";
// G Suite User to impersonate
var user_email = "admin#example.com";
// G Suite User to get information about
var gs_email = "user1#example.com";
// Scopes
var scopes = "https://www.googleapis.com/auth/admin.directory.user";
var credential = GoogleCredential.FromFile(sa_file)
.CreateScoped(scopes)
.CreateWithUser(user_email);
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential
});
try {
var request = service.Users.Get(gs_email);
var result = request.Execute();
Console.WriteLine("Full Name: {0}", result.Name.FullName);
Console.WriteLine("Email: {0}", result.PrimaryEmail);
Console.WriteLine("ID: {0}", result.Id);
Console.WriteLine("Is Admin: {0}", result.IsAdmin);
} catch {
Console.WriteLine("User not found.");
}
}
}
}
If you want to use the service account you can authenticate with below code.
String serviceAccountEmail = "yourserviceaccountmail";
public GmailService GetService(string user_email_address)
{
var certificate = new X509Certificate2(#"yourkeyfile.p12",
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
User = user_email_address,
Scopes = new[] { GmailService.Scope.MailGoogleCom }
}.FromCertificate(certificate));
GmailService service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = AppName,
});
return service;
}
You can list users using this service. Its work for me.
And you can list userlist with below code. ( with DirectoryService)
public Users GetDirService()//UserList with DirectoryService
{
string Admin_Email = "yoursuperadminemail";
string domain = "yourdomain.com";
try
{
var certificate = new X509Certificate2(#"yourkeyfile.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credentialUsers = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
User = Admin_Email,
}.FromCertificate(certificate));
var serviceUsers = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentialUsers,
ApplicationName = AppName,
});
var listReq = serviceUsers.Users.List();
listReq.Domain = domain;
Users users = listReq.Execute();
return users;
}
catch (Exception ex)
{
MessageBox.Show("your mail address must be super admin authorized.", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
return null;
}
}
Ok I have solved the issue.
Adding the following scope via the security settings within the Google Portal has solved the issue. This is strange as their own example doesn't require this scope to be added ad their documentation doesn't say it's required for this method.
https://www.googleapis.com/auth/admin.directory.group

Getting access token and refresh token without asking for user permission

I want to access Google Drive to upload files using Google.Apis.Drive.v3.
Here is my code:
protected async void btnConnectGoogleDrive_Click(object sender, EventArgs e)
{
string[] Scopes = { DriveService.Scope.Drive };
UserCredential credential;
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = "",
ClientSecret = ""
},
Scopes,
"user",
CancellationToken.None);
InsertGoogleDriveToken(credential.Token.AccessToken, areaID, "accesstoken");
InsertGoogleDriveToken(credential.Token.RefreshToken, areaID, "refreshtoken");
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "SocialLadder",
});
GoogleDriveUpload(service, areaID);
}
Here I am able to get the AccessToken and RefreshToken but user is not redirected to permission page, so when I try to upload images to drive it gives me error that "permission_not_granted". Used same thing in MVC and that works great.
Please help me with this issue.
I suspect that you have changed your scopes. You need to authenticate the user. Do one of the following
Go to %appdata% and delete the creditlas file for this user.
change "user" to something else say "user1"
Your application should then require the user to authenticate again.

Failure of delegation of Google Drive access to a service account

I've been involved with building an internal-use application through which users may upload files, to be stored within Google Drive. As it is recommended not to use service accounts as file owners, I wanted to have the application upload on behalf of a designated user account, to which the company sysadmin has access.
I have created the application, along with a service account. There are two keys created for the service account, as I have tried both the JSON and PKCS12 formats trying to achieve this:
I have downloaded the OAuth 2.0 client ID details, and also have the .json and .p12 files for the service account keys (in that order as displayed above):
I had my sysadmin go through the steps detailed here to delegate authority for Drive API access to the service account: https://developers.google.com/drive/v2/web/delegation#delegate_domain-wide_authority_to_your_service_account
We found that the only thing that worked for the "Client name" in step 4 was the "Client ID" listed for the Web application (ending .apps.googleusercontent.com). The long hexadecimal IDs listed for the Service account keys were not what it required (see below):
Previously to the above, I had code which would create a DriveService instance that could upload directly to the service account, referencing the .json file for the service account keys:
private DriveService GetServiceA()
{
var settings = SettingsProvider.GetInstance();
string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
var scopes = new string[] { DriveService.Scope.Drive };
var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
var credential = GoogleCredential.FromStream(stream);
credential = credential.CreateScoped(scopes);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
That works for listing and uploading, though of course there's no web UI for access to the files, and it seems as though it doesn't handle things like permissions metadata or generation of thumbnails for e.g. PDFs. This is why I'm trying to use a standard account for the uploads.
Once the delegation was apparently sorted, I then attempted to adapt the code shown in the delegation reference linked above, combining with code from elsewhere for extracting the necessary details from the .json key file. With this code, as soon as I try to execute any API command, even as simple as:
FileList fileList = service.FileList().Execute();
I receive an error:
Exception Details: Google.Apis.Auth.OAuth2.Responses.TokenResponseException: Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""
The code for that effort is:
private DriveService GetServiceB()
{
var settings = SettingsProvider.GetInstance();
string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
string serviceAccountEmail = "<account-email>#<project-id>.iam.gserviceaccount.com";
var scopes = new string[] { DriveService.Scope.Drive };
var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
var reader = new IO.StreamReader(stream);
string jsonCreds = reader.ReadToEnd();
var o = JObject.Parse(jsonCreds);
string privateKey = o["private_key"].ToString();
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "designated.user#sameappsdomain.com"
}
.FromPrivateKey(privateKey)
);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
Finally, I created the second service account key to save a .p12 file in order to more closely match the code in the authority delegation documentation, but which results in the same exception:
private DriveService GetServiceC()
{
var settings = SettingsProvider.GetInstance();
string p12KeyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.p12");
string serviceAccountEmail = "<account-email>#<project-id>.iam.gserviceaccount.com";
var scopes = new string[] { DriveService.Scope.Drive }; // Full access
X509Certificate2 certificate = new X509Certificate2(
p12KeyFilePath,
"notasecret",
X509KeyStorageFlags.Exportable
);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "designated.user#sameappsdomain.com"
}
.FromCertificate(certificate)
);
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName"
});
return service;
}
The minimial relevant class where this method lives is:
public class GoogleDrive
{
public DriveService Service { get; private set; }
public GoogleDrive()
{
this.Service = this.GetService();
}
private DriveService GetService()
{
// Code from either A, B or C
}
public FilesResource.ListRequest FileList()
{
return this.Service.Files.List();
}
}
And that's used in this fashion:
var service = new GoogleDrive();
FilesResource.ListRequest listRequest = service.FileList();
FileList fileList = listRequest.Execute();
The exception occurs on that last line.
I do not understand why my service account cannot act on behalf of the designated user, which is part of the domain for which the application's service account should have delegated authority. What is it that I've misunderstood here?
I have found the answer myself, and it was configuration, not code. The link I shared with the steps for delegation of authority does not mention an option available when creating the service account: a checkbox saying that the account will be eligible for domain-wide delegation (DwD).
This link describes the service account creation and delegation more accurately: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I did not know about DwD when I created the service account, and so I had not selected that option. It is possible to go back and edit a service account to select it. Once I did this, I was able to retrieve a correct client ID for use in the "Manage API Client Access" part of the admin console. Using the GetServiceC() method then works as intended, and I am able to retrieve files for users in the same Apps domain.
This is the checkbox that needs to be ticked for a service account to be eligible for domain-wide delegation of authority:
This is the extra information available once you've done that (with a throwaway service account alongside that did not have the box ticked, for comparison):
You may tick the checkbox Enable G Suite Domain-wide Delegation, when you create the service account on the admin panel.
Regards
Most everything looks ok but:
A. Use ServiceC code, not sure if the object typing matters but your line:
var credential = new ServiceAccountCredential...
should be
ServiceAccountCredential credential = new ServiceAccountCredential...
B. Check that the P12 file in ServiceC is the real P12 file you actually uploaded to your environment where you're running this.
C. update your question with the exact runable code you're using to create and invoke your service:filelist:execute code. This way there's more clarity and less assumptions.

Requested client not authorized

I am trying to get google users from my domain using google service account.
But it throws error
Error:"access_denied", Description:"Requested client not authorized.", Uri:""
My code
X509Certificate2 certificate = new X509Certificate2(key_path,
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("publickey.gserviceaccount.com")
{ Scopes = scopes,
User = "admin#domain.com"
}.FromCertificate(certificate));
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "appname",
});
service.Users.List().Domain = "domain.com";
Users results = service.Users.List().Execute();
Thanks in advance
The service account email address needs to have access the domain. Take the email and add it as a user just enough access that it can read should be good.
Also did you change this for posting?
"publickey.gserviceaccount.com"
A service account email looks more like this:
539621478854-imkdv94bgujcom228h3ea33kmkoefhil#developer.gserviceaccount.com
You need to give your service-account/API project access to your domain first.
Steps detailed in the docs here:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account
You need to specify the correct scopes you need in step 6 of those instructions, which would be https://www.googleapis.com/auth/admin.directory.user.readonly to access the list of users.
In addition for the Directory API to work you need to enable API access in the domain settings: https://developers.google.com/admin-sdk/directory/v1/guides/prerequisites#set_up_api
I was finally able to get this working. Here is the code I have
var grpReq = service.Groups.List();
grpReq.Domain = "mydomain.com";
Groups groups = grpReq.Execute();
IList<Group> gps = groups.GroupsValue;
var memReq=service.Members.List(groups.GroupsValue[0].Id);
Members members = memReq.Execute();
I am still not sure why creating a var object and then Execute() got this to work but the earlier code didn't work.
I still have the problem of the consent screen showing up for all users. I have the following code. I think the way I get the logged in user's email is incorrect. Any ideas?
string mymail = googleauth.GetUsersEmail(ExchangeCodeWithAccessAndRefreshToken().Access_Token);
string path = "d:\\c6b82065f26fbb0-privatekey.p12";
X509Certificate2 certificate = new X509Certificate2(
path,
"notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("876131792-v824u6drpss#developer.gserviceaccount.com")
{
User = mymail,
Scopes = new[] { PlusService.Scope.UserinfoEmail, PlusService.Scope.UserinfoProfile, PlusService.Scope.PlusMe }
}.FromCertificate(certificate));
PlusService plus = new PlusService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "myapp"
});
Person profile = plus.People.Get("me").Execute();
string email = profile.Emails[0].Value;

Categories