Google API authentification Server-Side - c#

I'm trying to use the Gmail API reading the emails, but I'm running into the problem that I want to do a server-side authentification but with all the examples from google Doc., he always shows me window asking me to add my credentials (Gmail & password).
public static async void CreateService()
{
GoogleCredential credential;
using (var stream = new FileStream(#"key.json", FileMode.Open,
FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream)
.CreateScoped(GmailService.Scope.GmailLabels,
GmailService.Scope.GmailModify, GmailService.Scope.GmailMetadata,
GmailService.Scope.GmailReadonly);
}
var service = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Gmail",
});
Console.WriteLine(ListMessages(service, "me", ""));
}
Then I got this code from the documentation of the google api of how to read the messages from a user.
public static List<Message> ListMessages(GmailService service, String userId, String query)
{
List<Message> result = new List<Message>();
UsersResource.MessagesResource.ListRequest request = service.Users.Messages.List(userId);
request.Q = query;
do
{
try
{
ListMessagesResponse response = request.Execute();
result.AddRange(response.Messages);
request.PageToken = response.NextPageToken;
}
catch (Exception e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
} while (!String.IsNullOrEmpty(request.PageToken));
return result;
}
But when I run it I get this error: An error occurred:
Google.Apis.Requests.RequestError
Bad Request [400]
Errors [
Message[Bad Request] Location[ - ] Reason[failedPrecondition] Domain[global]
]

Answer:
If you want to use a user as the authentication account, then no. This is not possible and you will always get a login window pop-up.
Other Methods:
You can however create and use a service account to impersonate your user and bypass the need for authenticating on run. They require a little extra set up but you can create them in the Google Developer Admin Console.
Code Example:
After creating your service account and giving it the roles and permissions it needs (see links below), you only need to make small edits to your code to use it instead of your regular account. This is an example in Python, but you can find other examples on the managing keys page:
import os
from google.oauth2 import service_account
import googleapiclient.discovery
def create_key(service_account_email):
"""Creates a key for a service account."""
credentials = service_account.Credentials.from_service_account_file(
filename=os.environ['GOOGLE_APPLICATION_CREDENTIALS'],
scopes=['YOUR SCOPES HERE'])
# Rememer here that your credentials will need to be downloaded
# for the service account, YOU CAN NOT USE YOUR ACCOUNT'S CREDENTIALS!
service = googleapiclient.discovery.build(
'service', 'version', credentials=credentials)
key = service.projects().serviceAccounts().keys().create(
name='projects/-/serviceAccounts/' + service_account_email, body={}
).execute()
print('Created key: ' + key['name'])
References:
Google Developer Admin Console
Google Cloud - Service Accounts
Understanding Service Accounts
Creating and Managing Service Accounts
Creating and Managing Service Account Keys
Granting Roles to Service Accounts

Related

Sending mail with EWS and OAuth 403 error

A while ago I wrote in C# a simple utility method that was called by an application to send emails automatically.
The application used to authenticate in EWS with Basic authentication (username + password) and everything worked fine.
Starting from September 2022 Microsoft started disabling this now deprecated authentication method, so I decided to update this utility method with an OAuth system autentication.
The method is the following:
public static void SendMail(string to, string cc, string bcc, string replyTo, string from, string subject, bool isHtmlFormat, string body, string[] attachments)
{
var cca = ConfidentialClientApplicationBuilder.Create("my-app-id")//app id
.WithClientSecret("my-client-secret") //Client secret
.WithTenantId("my-tenant-id") //Id tenant
.Build();
var authResult = cca.AcquireTokenForClient(new string[] { "https://outlook.office365.com/.default" }).ExecuteAsync().Result;
string[] recipients = to.Replace(" ", "").Split(';');
string[] repliesTo = string.IsNullOrWhiteSpace(replyTo) ? Array.Empty<string>() : replyTo.Replace(" ", "").Split(';');
string[] ccs = string.IsNullOrWhiteSpace(cc) ? Array.Empty<string>() : cc.Replace(" ", "").Split(';');
string[] bccs = string.IsNullOrWhiteSpace(bcc) ? Array.Empty<string>() : bcc.Replace(" ", "").Split(';');
ExchangeService service = new ExchangeService
{
Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx"),
Credentials = new OAuthCredentials(authResult.AccessToken),
ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, from)
};
service.HttpHeaders.Add("X-AnchorMailbox", from); //Include x-anchormailbox header
EmailMessage emailMessage = new EmailMessage(service)
{
From = new EmailAddress(from),
Subject = subject,
Body = new MessageBody(isHtmlFormat ? BodyType.HTML : BodyType.Text, body)
};
emailMessage.ToRecipients.AddRange(recipients);
emailMessage.ReplyTo.AddRange(repliesTo);
emailMessage.CcRecipients.AddRange(ccs);
emailMessage.BccRecipients.AddRange(bccs);
foreach (string attachment in attachments ?? Array.Empty<string>())
{
emailMessage.Attachments.AddFileAttachment(attachment);
}
emailMessage.Send();
}
The function is called in a very straightforward way:
MailHelper.SendMail("MyEmail#myCompany.com", null, null, null, "NoReply#myCompany.com", "Test subject", false, "This is a test body", null);
The problem is that as soon as the Method emailMessage.Send(); is called, a Microsoft.Exchange.WebServices.Data.ServiceRequestException reporting a 403 forbidden error is thrown.
I already registered the app in the Azure Active Directory interface, set a secret and set the following permissions:
The accounts in my tenant are set to allow Exchange services:
I already double checked IDs and account names to be sure it was not a trivial mistake, but I'm not an expert when it comes to EWS, so I'm surely missing something, unfortunately I don't know where.
Thanks id advance.
The error 403 Forbidden usually occurs if you don't have required permissions or missed granting admin consent to the added API permissions.
Please note that, the code you mentioned is using "Client credentials flow" that works with only Application permissions but you added all Delegated permissions.
In that case, you will get 403 Forbidden error even you granted consent to Delegated permissions.
I tried to reproduce the same in my environment via Postman and got the below results:
I created one Azure AD application and added API permissions same as you like below:
Now, I generated an access token using "Client credentials flow" via Postman like below:
POST https://login.microsoftonline.com/tenantID/oauth2/v2.0/token
client_id:appID
grant_type:client_credentials
client_secret:secret
scope:https://graph.microsoft.com/.default
Response:
When I used the above token to send mail with below query, I got 403 Forbidden error like this:
POST https://graph.microsoft.com/v1.0/users/userID/sendmail
Response:
To resolve the error, you need to add Application permissions and grant admin consent to them like below:
Now I generated the access token again and used it in running below query to send sample mail:
In your case, add Application permissions by granting admin consent to them and run the code again.

Authenticate to SharePoint Online C#

I'm trying to connect to SharePoint online in a console App and print the title of the site.
Its giving me the error : "The sign-in name or password does not match one in the Microsoft account system."
I have checked and made sure the username and password are 100% right.
I dont know what else to check
Heres my code:
private static void SPCredentialsConnect()
{
const string SiteUrl = "https://tenant.sharepoint.com/sites/mysite";
const string pwd = "appPassword";
const string username = "username#tenant.onmicrosoft.com";
SecureString securestring = new SecureString();
pwd.ToCharArray().ToList().ForEach(s => securestring.AppendChar(s));
ClientContext context = new ClientContext(SiteUrl);
context.Credentials = new SharePointOnlineCredentials(username, securestring);
try
{
var web = context.Web;
context.Load(web);
context.ExecuteQuery();
Console.WriteLine($"web title: {web.Title}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Have your issue fixed? “The sign-in name or password does not match one in the Microsoft account system” Error will occur sometimes and fixed after a while with nothing changed.
AppOnly Authentication for sharepointonline can't be registed in Azure Active Directory.
It should be register in
https://contoso.sharepoint.com/_layouts/15/appregnew.aspx
And grant permission in
https://contoso-admin.sharepoint.com/_layouts/15/appinv.aspx
You can refer to following document
https://learn.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azureacs
Consider using the PnP.Framework (a NuGet package), and use the AuthenticationManager object for SPO sites. This method bypasses MFA (which is mandatory in our organization, FWIW). You can find a lot more information and examples here, including steps on getting the client id and client secret for a site. Here is what we use to log into SPO sites:
using (ClientContext context =
new AuthenticationManager().GetACSAppOnlyContext(SiteUrl, clientID, clientSecret))
{
...
}
Also, once you connect, you should adjust the Context.Load to grab the title if you want to use that value right away. Here's what I used in my code:
context.Load(web, p => p.Id, p => p.Title);
context.ExecuteQuery();
Console.WriteLine($"Logged into source {web.Title} ({web.Id})");
Good luck!
Steve in Spain

403 error when accessing users in a project

I am trying to get the list of users who have access to a project using this Forge API. I have given the scope as account:read. I tried it suing Postman and also from ASP.NET Core backend which I am developing.
In both cases I get the same error:
{
"status": 403,
"type": "",
"id": "80757c600ab0de6c",
"title": "Forbidden",
"detail": "The 3 legged access token does not have access"
}
Is there anything that I need to change in BIM 360 so that I can resolve this error?
This is my backend code (BASE URL):
[HttpPost]
[Route("api/forge/bim360/projectusers")]
public async Task<dynamic> GetProjectUsersAsync([FromQuery] string projectId, [FromQuery] string userId)
{
dynamic access_token = await CheckToken();
RestClient client = new RestClient(BASE_URL);
RestRequest request = new RestRequest("/bim360/admin/v1/projects/{project_id}/users", RestSharp.Method.GET);
request.AddParameter("project_id", projectId, ParameterType.UrlSegment);
request.AddHeader("Authorization", "Bearer " + access_token);
request.AddHeader("User-Id", userId);
try
{
IRestResponse issueTypesResponse = await client.ExecuteGetTaskAsync(request);
dynamic users = JObject.Parse(issueTypesResponse.Content);
return Ok("Found Users");
}
catch (Exception ex)
{
//TODO Add real logger
StreamWriter st = new StreamWriter(#"Logg/logg.txt", true);
st.Write(ex.Message);
st.Close();
return StatusCode(500);
}
}
Any help appreciated.
Just tried your code here and it works just fine.
The error message states that the user on 3LO doesn't have permission to access the project users.
Please note that this endpoint works with 3LO or 2LO (with or without User-id).
If you change to a user with permission or 2LO you should be able to access the project users.
When using 3LO, permission is granted by the user that acquired the token (User-id header is ignored).
Worked on it for some time and figured it out. It has to do with the way the Forge App is setup. The access token that we retrieve can't access user details.

Google .NET API fails due to error 403 (forbidden)

My code snippet below is supposed to return the list of beacons. When having Google API Console generate an API Key I have whitelisted my public IP address and associated with the api key. When the code calls ExecuteAsync() method, I keep receiving an exception with error code 403 (forbidden). What may have I done wrong and how to mitigate the issue?
public async void TestApiKey()
{
var apikey = "739479874ABCDEFGH123456"; //it's not the real key I'm using
var beaconServices = new ProximitybeaconService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "My Project",
ApiKey = apikey
});
var result = await beaconServices.Beacons.List().ExecuteAsync();
// Display the results.
if (result.Beacons != null)
{
foreach (var api in result.Beacons)
{
Console.WriteLine(api.BeaconName + " - " + api.Status);
}
}
}
You are using a Public API key. Public API keys only work with public data.
beacons.list Authenticate using an OAuth access token from a
signed-in user with viewer, Is owner or Can edit permissions.
Requires the following OAuth scope:
•https://www.googleapis.com/auth/userlocation.beacon.registry
The method you are trying to access is accessing private user data. You need to be authentication before you can use it. Switch to Oauth2 authentication. Setting it to public probably wont work because you cant to my knowledge supply a scope to a public api key.

OrgUnit Not Found using Google Directory API

Procedure
I'm going to:1. Get a OrgUnit from the Google Directory API 2. Read the OrgUnit and collect the required Data 3. Try to delete the OrgUnit I just collected.
This somehow results in a 404 [Not Found] Error Please keep in mind that the DirectoryService Class I am using, is working properly. I modified the code in this example to make it easy to read, for example: Exception handling is not included etc.
The API
using Google.Apis.Admin.Directory.directory_v1
1. Get a OrgUnit from the Google Directory API
DirectoryService directoryService = ServiceInitializers.InitializeDirectoryService();
OrgUnit oUnit = directoryService.Orgunits.List(Settings.customerId).Execute().OrganizationUnits.FirstOrDefault();
2.Read the OrgUnit and collect the required Data
string orgUnitPath = oUnit.OrgUnitPath;
3.Try to delete the OrgUnit I just collected
var orgUnitDeleteResult = directoryService.Orgunits.Delete(Settings.customerId, orgUnitPath).Execute();
The Exception
GoogleApiException was unhandledAn unhandled exception of type 'Google.GoogleApiException' occurred in Google.Apis.dll
Additional information: Google.Apis.Requests.RequestError
Org unit not found [404]
My reputation isn't high enough to add a comment to get clarification before posting an answer, so I'll have to make some assumptions here.
First assumption is that you're using a service account to access the API.
Second assumption is that you've got a certificate from your Google administrative control panel and that's all in order.
I had a similar issue when I was updating user accounts through the API, and what fixed it for me was having a directory administrator account act as a delegate for the service account.
Here's the code I use to initialize my Google Directory Service.
private static DirectoryService initializeGoogleDirectoryService()
{
try
{
String serviceAccountEmail = "your_service_account_email#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"your_certificate_name.p12", "your_secret", X509KeyStorageFlags.Exportable);
// For the service account to work, a user with admin privs must be assigned as the delegate.
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
// Change the scope here to the one you need to modify org units.
Scopes = new[] { DirectoryService.Scope.AdminDirectoryUser },
User = "administrator_account#your_google_apps_domain.com"
}.FromCertificate(certificate));
// Create the service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Your_Application_Name"
});
return service;
}
catch (Exception ex)
{
// Exception handling code below.
return null;
}
finally
{
}
}

Categories