I am trying to access google sites feed by using google service account. But I get "The remote server returned an error: (403) Forbidden."(Not authorized to access this feed) error.
The code written is as follows:
var certificate = new X509Certificate2(filePath, password, X509KeyStorageFlags.Exportable);
var serviceAccountCredentialInitializer =new ServiceAccountCredential.Initializer("abc#developer.gserviceaccount.com")
{Scopes = new[] {"https://sites.google.com/feeds/"}}.FromCertificate(certificate);
var credential = new ServiceAccountCredential(serviceAccountCredentialInitializer);
if(!credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Result)
throw new InvalidOperationException("Access token request failed.");
var requestFactory = new GDataRequestFactory(null);
requestFactory.CustomHeaders.Add("Authorization: Bearer " + credential.Token.AccessToken);
//SitesService class inherits MediaService
SitesService service = new SitesService(null)
{
RequestFactory = requestFactory
};
Stream feed = service.Query(new Uri("https://sites.google.com/feeds/content/domain-name/site-name?path=page-path"));
I have also granted access to required scopes to google service account from admin panel. But still same error.
Can someone please tell me, what is wrong in my code?
Related
Im trying to programatically create a WorkItem (issue) in AzureDevOps.
However I cannot get the authentication to work.
Im simply trying to migrate from the deprecated SOAP API where we weren't required to use PATs.
So I hope it is still possible to use authentication that does not require the user to put his PAT somewhere.
Our AD is synchronized with Azure.
This is what I have tried:
var appBuilder = PublicClientApplicationBuilder.Create("XXXXXXXXXXXXX");
appBuilder.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient");
appBuilder.WithAuthority(AadAuthorityAudience.AzureAdMyOrg);
appBuilder.WithTenantId("XXXXXXXXXXXXXX");
var application = appBuilder.Build();
var authBuilder = application.AcquireTokenByIntegratedWindowsAuth(new[] { "https://app.vssps.visualstudio.com/user_impersonation" });
AuthenticationResult token = authBuilder.ExecuteAsync().Result;
var projectUri = new Uri("https://dev.azure.com/XXXXXXX/");
var credentials = new VssCredentials(new WindowsCredential(true), new VssOAuthAccessTokenCredential(token.AccessToken));
var connection = new VssConnection(projectUri, credentials);
var client = connection.GetClient<WorkItemTrackingHttpClient>();
client.CreateWorkItemAsync(...)
Throws the following exception:
Microsoft.VisualStudio.Services.Common.VssUnauthorizedException: "VS30063: Sie sind nicht autorisiert, auf https://dev.azure.com zuzugreifen."
This is is the apps permission
The part i do not understand is, that the token is created succesfully and i can acquire the instance of WorkItemTrackingHttpClient, but the exception only occurs once i try to create the WorkItem.
I'm trying to read all Inbox email items from an Office 365 mailbox using ExchangeService.
For that, I:
Created an app in my AzureAD portal.
Given this app all permissions.
Issues this app an access secret to use in my code.
The code works to the point that I sucessfully get a token, but when trying to get the folder items I get an 403 error:
'The request failed. The remote server returned an error: (403)
Forbidden.'
I get this error from my dev and my prod environments so I'm pretty sure it's not a network or port issue.
Here's my code:
var cca = ConfidentialClientApplicationBuilder
.Create("myApplicationId")
.WithClientSecret("myClientSecret")
.WithTenantId("myTenantId")
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
// This is where I get the token
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "my#mail.box");
ewsClient.HttpHeaders.Add("X-AnchorMailbox", "my#mail.box");
// This is where I get the 403 error:
var items = ewsClient.FindItems(
new FolderId(WellKnownFolderName.Inbox, new Mailbox("my#mail.box")),
new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter[] {}
),
new ItemView(15)
);
403 if its coming back from Office365 sounds like they have either disabled EWS on the Mailbox your trying to access or they have limited the clients that are allowed to connect eg https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-control-access-to-ews-in-exchange . You could try testing EWS itself using a user account via the EWSeditor https://github.com/dseph/EwsEditor
Problem
I have been trying to figure out how to find a SharePoint path for a user when using OAuth2 Client Credential Flow (where an application has permission to read all users' SharePoint files using an Office 365 administrator's one-time acceptance)
I have my client application setup in Azure and am able to read files if I hard-code the SharePoint URL - so I know it is setup correctly.
But I need to "discover" the SharePoint URL so it will be change-tolerant and reusable across customers.
Related Articles:
Different OAuth2 Flows
Using OAuth2 Flow for Exchange
Code
var azureAdAuthority = "https://login.windows.net/{tenant-id}/oauth2/authorize".Replace("{tenant-id}", tenantId);
var discoveryUri = "https://api.office.com/discovery/v1.0/me/";
var discoveryResourceUri = "https://api.office.com/discovery/";
// discover contact endpoint
var cert = new X509Certificate2(certFilePath, certFilePassword, X509KeyStorageFlags.MachineKeySet);
var clientAssertion = new ClientAssertionCertificate(clientId, cert);
var userIdentifier = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
var userAssertion = new UserAssertion(userObjectId);
// create auth context
var authContext = new AuthenticationContext(azureAdAuthority, false);
// create O365 discovery client
var discovery = new DiscoveryClient(new Uri(discoveryUri),
() => authContext.AcquireTokenSilent(discoveryResourceUri, clientAssertion, userIdentifier).AccessToken);
// query discovery service for endpoint for 'calendar' endpoint
var dcr = await discovery.DiscoverCapabilityAsync("MyFiles");
This and many other variations throw exceptions from the AcquireTokenSilent function.
If I don't use a "userIdentifier" and call the AcquireToken function it succeeds, but the DiscoverCapabilityAsync function fails.
I'm trying to use the Google Drive and Spreadsheets APIs from a C# console app. I'd like to authorize both services using user credentials with a FileDataStore so that I don't have to reauth my app every single time it runs. Below is how I'm authorizing my Drive service object:
var userCredential = GoogleWebAuthorizationBroker.AuthorizeAsync
(
new ClientSecrets
{
ClientId = "[clientID]",
ClientSecret = "[clientSecret]"
},
new []
{
"https://www.googleapis.com/auth/drive",
"https://spreadsheets.google.com/feeds"
},
"[userName]",
CancellationToken.None,
new FileDataStore("MyApp.GoogleDrive.Auth.Store")
).Result;
var driveService = new DriveService
(
new BaseClientService.Initializer
{
HttpClientInitializer = userCredential,
ApplicationName = "MyApp",
}
);
For the Spreadsheets service, I'm authorizing as prescribed by this guide, but every time I run my app, I have to open a browser to the given auth URL and manually copy in the access token to get it to work.
Is there a way to auth once, obtain the user credentials as above, and use them with both services? Note, I'm authorizing with both the Drive and the Spreadsheets scope, so I don't think there's a problem with that.
I've tried to make it work like this, but I keep getting 400 Bad Request errors when I attempt to insert rows into my spreadsheet:
var auth = new OAuth2Parameters
{
ClientId = "[clientID]",
ClientSecret = "[clientSecret]",
RedirectUri = "[redirectUri]",
Scope = "https://www.googleapis.com/auth/drive https://spreadsheets.google.com/feeds" ,
AccessToken = userCredential.Token.AccessToken,
RefreshToken = userCredential.Token.RefreshToken,
TokenType = userCredential.Token.TokenType,
};
var requestFactory = new GOAuth2RequestFactory(null, "MyApp", auth);
var spreadsheetsService = new SpreadsheetsService("MyApp")
{
Credentials = new GDataCredentials(userCredential.Token.TokenType + " " + userCredential.Token.AccessToken),
RequestFactory = requestFactory,
};
Is there a way to auth once, obtain the user credentials as above, and use them with both services?
Yes. Provided you have included all scopes and have requested offline access, then you'll get a refresh token which you can store and reuse to get access tokens as needed. Obv you need to consider the security implications.
A 400 bad request doesn't sound like an OAuth issue. I think you have two questions/issues here and it might be worth starting a new thread. Include the http request/response for the 400 in your question.
I want to use the google analytics api in my MVC website, im authenticating using the api service account and oauth2 with have no issues on my localhost but as soon as I deploy to Azure i get a 502 error:
"502 - Web server received an invalid response while acting as a
gateway or proxy server. There is a problem with the page you are
looking for, and it cannot be displayed. When the Web server (while
acting as a gateway or proxy) contacted the upstream content server,
it received an invalid response from the content server."
heres my code:
const string ServiceAccountUser = "xxxxxxxxxx-cpla4j8focrebami0l87mbcto09j9j6k#developer.gserviceaccount.com";
AssertionFlowClient client = new AssertionFlowClient(
GoogleAuthenticationServer.Description,
new X509Certificate2(System.Web.Hosting.HostingEnvironment.MapPath("/Areas/Admin/xxxxxxxxxxxxxxxxxx-privatekey.p12"),
"notasecret", X509KeyStorageFlags.Exportable))
{
Scope = AnalyticsService.Scopes.AnalyticsReadonly.GetStringValue(),
ServiceAccountId = ServiceAccountUser //Bug, why does ServiceAccountUser have to be assigned to ServiceAccountId
//,ServiceAccountUser = ServiceAccountUser
};
OAuth2Authenticator<AssertionFlowClient> authenticator = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
I cant figure out whats causing it? Am im missing something within Azure?
Thanks for any help.
I also ran into the same issue but passing X509KeyStorageFlags.MachineKeySet into the constructor as well fixed the issue for me.
X509Certificate2 certificate = new X509Certificate2(file, "key", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
After hours of pain on this exact same problem, I found a work around by piecing together various sources of info.
The problem arises from trying to read the p12 file from the Azure web site, i.e. this line in my code fails
var key = new X509Certificate2(keyFile, keyPassword, X509KeyStorageFlags.Exportable);
No idea why, but it works if you split the file into a cer and key.xml file?
Firstly, extract these files, (I just used a console app)
// load pfx/p12 as "exportable"
var p12Cert = new X509Certificate2(#"c:\Temp\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
// export .cer from .pfx/.p12
File.WriteAllBytes(#"C:\Temp\MyCert.cer", p12Cert.Export(X509ContentType.Cert));
// export private key XML
string privateKeyXml = p12Cert.PrivateKey.ToXmlString(true);
File.WriteAllText(#"C:\Temp\PrivateKey.xml", privateKeyXml);
Then copy them to your website then load them in like so
//Store the authentication description
AuthorizationServerDescription desc = GoogleAuthenticationServer.Description;
//Create a certificate object to use when authenticating
var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(File.ReadAllText(keyFile));
var key = new X509Certificate2(certFile) {PrivateKey = rsaCryptoServiceProvider};
//Now, we will log in and authenticate, passing in the description
//and key from above, then setting the accountId and scope
var client = new AssertionFlowClient(desc, key)
{
//cliendId is your SERVICE ACCOUNT Email Address from Google APIs Console
//looks something like 12345-randomstring#developer.gserviceaccount.com
//~IMPORTANT~: this email address has to be added to your Google Analytics profile
// and given Read & Analyze permissions
ServiceAccountId = clientId,
Scope = "https://www.googleapis.com/auth/analytics.readonly"
};
//Finally, complete the authentication process
//NOTE: This is the first change from the update above
var auth = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
//First, create a new service object
//NOTE: this is the second change from the update
//above. Thanks to James for pointing this out
var gas = new AnalyticsService(new BaseClientService.Initializer { Authenticator = auth });
This now works for me and I hope it helps you.