Google Spreadsheet API - Accessing a spreadsheet created with service account - google-sheets

I created a service provider in my laravel app which connects with the google spreadsheet API, and I am able to create new spreadsheets, edit existing ones (with the right sharing permissions of course) and so on.
The problem is that I supplied my service account's json credentials key, hence the spreadsheets that are being created are owned by this cryptic mail address, so after the creation - I get an ID and when I'm trying to access the spreadsheet it gives me this 'request access' page, and when i'm trying to request access to my own email (I defined myself as an 'owner' of the project as well), I get these delivery status notifications via mail -
Your message wasn't delivered to
xxxxxx#yyyyyy-1x3x71x4x27x4.iam.gserviceaccount.com because the domain
admanager-1x3x71x4x27x4.iam.gserviceaccount.com couldn't be found.
Check for typos or unnecessary spaces and try again.
Obviously it's trying to send an email from me (xxxx#gmail.com) to my service account email, to request an access to this specific spreadsheet, and of course this address doesn't really exist. I feel like I'm doing something wrong from the very first place..
GoogleServiceProvider.php -
class GoogleServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->singleton('google_spreadsheet_client', function ($app) {
// we will instantiate the Google Spread Sheet Client once in this function
$client = new Google_Client();
$client->setApplicationName('xxxxx');
$client->setScopes(Google_Service_Sheets::SPREADSHEETS);
$client->setAuthConfig('xxxxxx-1xyxyxyxyxyxy-221ca2fc3123.json');
$google_service_sheets = new Google_Service_Sheets($client);
return $google_service_sheets;
});
}
SpreadsheetController.php
class SpreadsheetsController extends Controller {
public function create() {
$service = resolve('google_spreadsheet_client');
$spreadsheet = new \Google_Service_Sheets_Spreadsheet([
'properties' => [
'title' => 'test'
]
]);
$spreadsheet = $service->spreadsheets->create($spreadsheet, [
'fields' => 'spreadsheetId'
]);
printf("Spreadsheet ID: %s\n", $spreadsheet->spreadsheetId);
}
}
And as I said - I do get a response with a string that looks like an actual spreadsheet ID, but can't access it with my own email for some reason..

Related

Azure AD Access token of Client Application does not contain Roles Claims

I have 2 Azure AD applications say Client-App and Server-App in Azure AD App registrations.
Server AD Application:
Registered a new App in Azure AD.
Set up App Roles with name "Search.Address" which is custom role.
Client AD Application:
Registered a new App in Azure AD.
API Permissions: Added the role "Search.Address" created in server-app registration is exposed as an Application Permissions in client app.
Granted Admin access successfully.
I have client Function App created with .NET stack and enabled system managed identity which is associated with Client-App. Client function app runs code to get an access token using ManagedIdentityCredential.
Token is successfully created but role "Search.Address" is missing.
I tried Client-App exposing as an API. But in no vain.
Does Managed identity have any permission to talk to server? How I can assign that using approleassignment ?
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.Empty;
try
{
var credential = new ManagedIdentityCredential();
var accessToken = await credential.GetTokenAsync(new TokenRequestContext(scopes: new string[] { "SERVERAPP_ClientID/.default" }) { });
responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. Your Token: {accessToken.Token}";
}
catch (Exception ex)
{
log.LogError(ex.Message+ex.InnerException.Message);
}
return new OkObjectResult(responseMessage);
}
}
Reference:
https://anoopt.medium.com/few-ways-of-obtaining-access-token-in-azure-application-to-application-authentication-40a9473a2dde
You need to assign the application permission/app role to the Managed Identity service principal.
You can't do this through the Portal at the moment, you'll need PowerShell to do that:
Connect-AzureAD
New-AzureADServiceAppRoleAssignment -ObjectId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -Id 32028ccd-3212-4f39-3212-beabd6787d81 -PrincipalId 1606ffaf-7293-4c5b-b971-41ae9122bcfb -ResourceId c3ccaf5a-47d6-4f11-9925-45ec0d833dec
Note that this is for the older AzureAD module. There is also a similar cmdlet for the newer Microsoft.Graph module.
For the AzureAD cmdlet, the ids you need are:
ObjectId and PrincipalId: Managed Identity Service Principal object ID
Id: id of the app role/app permission
ResourceId: object ID of the API Service Principal
Running this command is the same thing as the admin consent for application permissions.
Article I wrote on this: https://joonasw.net/view/calling-your-apis-with-aad-msi-using-app-permissions

.NET IdentityServer4 OpenIdConnect with Discord

I'm trying to cut my teeth with IdentityServer and have been following the guides on readthedocs closely. I'm at the point of adding external identity providers and have added all the ones I want to support to the IdentityServer project.
I specifically want to include "guilds" from Discord then do role based authorization in my web app based on the roles a user has on a specific Guild. Discord lists the various Scopes that are allowed:
So I've included the AspNet.Security.OAuth.Discord package and added an IdentityResource for guilds:
public static class AuthConfig
{
public static IEnumerable<IdentityResource> IdentityResources =>
new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Address(),
new IdentityResources.Email(),
new IdentityResources.Profile(),
new IdentityResource()
{
Name = "guilds",
DisplayName = "Discord Guilds",
Description = "All of the Discord Guilds the user belongs to",
Required = true,
Emphasize = true,
UserClaims = new[] { "name" } // <<< Not sure how I find the claims on the discord api doco
}
};
.
.
.
}
This then allows me to add scopes to my discord options in the startup of my IdentityServer project:
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddControllersWithViews();
services.AddAuthentication()
.AddDiscord("Discord", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "<my client id>";
options.ClientSecret = "<my client secret>";
options.Scope.Add("guilds");
})
When I login the uri has the guild scope added and I get the warning on the acknowlegement dialog:
But when I view the content of my claims I don't see anything.
If I add a standard oidc one of email that does display though.
If I follow through to the definition of IdentityResources.Email then I see these claims defined on the ScopeToClaimsMapping property in IdentityServer4.Constants
but I'm not sure how to determine what these claims should be for the Discord guilds scope...and is this even the issue anyway.
Can anyone point me in the right direction?
Claims and Scopes are different but related things.
An scope is a claim, it talks about the scope of your access.
When you request the "guild" scope, it means your token will be able to access the information under that scope. But that doesn't necessarily mean that information is going to be presented in a claim on the token or user_info response.
Instead, what you need to do to get the "guilds" information is to consume their API, using the token.
Discord Developer Portal - Guilds
Get Current User Guilds
GET /users/#me/guilds
Returns a list of partial guild objects the current user is a member of.
Requires the guilds OAuth2 scope.

Exchange Webservice using Oauth throws error when subscribing a resource

I am using OAuth2.0 to connect to Exchange webservices. Everything else seems to work ok for me . However when i try to subscribe one of the room resource by using grouping info and providing the anchor mailbox as one of the primary mail box it throws an error.
"Request failed because EWS could not contact the appropriate CAS server for this request."
So for example i am trying to subscribe nitroom1 and one the primary mailbox associated with the group is nitroom2 which i am using as X-AnchorMailbox then i got the above error.
public static ExchangeService GetExchangeService(string exchangeURL, string userName, string password, string resourceEmail, string primaryMailbox, string clientID, string tenantID, string clientSecret, string certName)
{
ExchangeService service;
service = new ExchangeService(setTZtoUTC);
service.Url = new Uri(exchangeURL);
if (!string.IsNullOrWhiteSpace(clientID) && !string.IsNullOrWhiteSpace(tenantID))
{
string oAuthToken = multiExchangeManager.getOAuthTokenFromCache(clientID, tenantID, clientSecret, certName);
service.Credentials = new OAuthCredentials(oAuthToken);
}
else
{
service.Credentials = new WebCredentials(userName, password);
}
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, resourceEmail);
service.HttpHeaders.Add("X-AnchorMailbox", primaryMailbox);
service.HttpHeaders.Add("X-PreferServerAffinity", "true");
return service;
}
However if i connect ews using impersonate account then do same thing it works fine.
Also, if i use resourceMailbox same as primary mailbox then it works ok as well.so in my example it will look like this.
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "nitroom1");
service.HttpHeaders.Add("X-AnchorMailbox", "nitroom1");
This is how i am trying to use subscription.
exchangeService.SubscribeToStreamingNotifications(
new FolderId[] { WellKnownFolderName.Calendar, WellKnownFolderName.DeletedItems },
EventType.Created, EventType.Deleted, EventType.Modified, EventType.Moved, EventType.Copied);
Does anyone have any idea why its happening or what i am doing wrong here?
one more thing to add, i tried EWSEditor tool which provides subscription info and both above mentioned resources sharing same grouping info.
I think i found a solution for this issue, i just need to set
X-BackEndOverRideCookie with any service used for subscribing child mailbox.
For more info read this article
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-maintain-affinity-between-group-of-subscriptions-and-mailbox-server

MSAL and Azure AD: What scopes should I pass when I just want to get the user ID?

I'm using MSAL to get an ID Token which is then used to access an Web API app. I've got a couple of questions and I was wondering if someone could help me understand what's going on.
Let me start with the authentication process in the client side. In this case, I'm building a Windows Forms app that is using the following code in order to authenticate the current user (ie, in order to get an ID Token which will be used to validate the user when he tries to access a Web API app):
//constructor code
_clientApp = new PublicClientApplication(ClientId,
Authority, //which url here?
TokenCacheHelper.GetUserCache());
_scopes = new []{ "user.read" }; //what to put here?
//inside a helper method
try {
return await _clientApp.AcquireTokenSilentAsync(_scopes, _clientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex) {
try {
return await _clientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException ex) {
return null;
}
}
The first thing I'd like to clear is the value that should be used for the authority parameter. In this case, I'm using an URL on the form:
https://login.microsoftonline.com/{Tenant}/oauth2/v2.0/token
However, I'm under the impression that I could also get away with something like this:
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
It seems like one endpoint is specific to my Azure AD while the other looks like a general (catch all) URL...Where can I find more information about these endpoints and on what's the purpose of each...
Another thing that I couldn't quite grasp is the scope. I'm not interested in querying MS Graph (or any other Azure related service for that matter). In previous versions of the MSAL library, it was possible to reference one of the default scopes. However, it seems like that is no longer possible (at least, I tried and got an exception saying that I shouldn't pass the default scopes...).
Passing an empty collection (ex.: new List<string>()) or null will also result in an error. So, in this case, I've ended passing the user.read scope (which, if I'm not mistaken, is used by MS Graph API. This is clearly not necessary, but was the only way I've managed to get the authentication process working. Any clues on how to perform the call when you just need to get an ID Token? Should I be calling a different method?
Moving to the server side, I've got a Web API app whose access is limited to calls that pass an ID token in the authentication header (bearer). According to this sample, I should use something like this:
private void ConfigureAuth(IAppBuilder app) {
var authority = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions {
AccessTokenFormat = new JwtFormat(GetTokenValidationParameters(),
new OpenIdConnectCachingSecurityTokenProvider(authority)),
Provider = new OAuthBearerAuthenticationProvider {
OnValidateIdentity = ValidateIdentity
}
});
}
Now, this does work and it will return 401 for all requests which don't have a valid ID Token. There is one question though: is there a way to specify the claim from the Ticket's Identity that should be used for identifying the username (User.Identity.Name of the controller)? In this case, I've ended handling the OnValidateIdentity in order to do that with code that looks like this:
private Task ValidateIdentity(OAuthValidateIdentityContext arg) {
//username not getting correctly filled
//so, i'm handling this event in order to set it up
//from the preferred_username claim
if (!arg.HasError && arg.IsValidated) {
var identity = arg.Ticket.Identity;
var username = identity.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value ?? "";
if (!string.IsNullOrEmpty(username)) {
identity.AddClaim(new Claim(ClaimTypes.Name, username));
}
}
return Task.CompletedTask;
}
As you can see, I'm searching for the preferred_username claim from the ID Token (which was obtained by the client) and using its value to setup the Name claim. Is there any option that would let me do this automatically? Am I missing something in the configuration of the OAuthBearerAuthenticationMiddleware?
Regarding your First Query -
Where can I find more information about these endpoints and on what's the purpose of each...
Answer -
https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
The {tenant} can take one of four values:
common -
Users with both a personal Microsoft account and a work or school account from Azure Active Directory (Azure AD) can sign in to the application.
organizations -
Only users with work or school accounts from Azure AD can sign in to the application.
consumers -
Only users with a personal Microsoft account can sign in to the application.
8eaef023-2b34-4da1-9baa-8bc8c9d6a490 or contoso.onmicrosoft.com -
Only users with a work or school account from a specific Azure AD tenant can sign in to the application. Either the friendly domain name of the Azure AD tenant or the tenant's GUID identifier can be used.
Regarding your Second Query on Scope -
Answer - Refer to this document - OpenID Connect scopes
Regarding your Third Query on Claim -
Answer - Refer to this GIT Hub sample - active-directory-dotnet-webapp-roleclaims

Need to Authenticate the Users using Twitter, Google, Facebook Apis through oauth plugin?

First i briefly want to know how you can use oauth works. What we need to pass in this plugin and what this plugin will return. Do we have to customize the plugin for different php frameworks. I have seen that their is a different extension of oauth for different framework, why is that?
I need to authenticate the users using social networks in yii framework and I have integrated eouath extension of yii to use oauth and have made an action to use access the ads service of google user like this
public function actionGoogleAds() {
Yii::import('ext.eoauth.*');
$ui = new EOAuthUserIdentity(
array(
//Set the "scope" to the service you want to use
'scope'=>'https://sandbox.google.com/apis/ads/publisher/',
'provider'=>array(
'request'=>'https://www.google.com/accounts/OAuthGetRequestToken',
'authorize'=>'https://www.google.com/accounts/OAuthAuthorizeToken',
'access'=>'https://www.google.com/accounts/OAuthGetAccessToken',
)
)
);
if ($ui->authenticate()) {
$user=Yii::app()->user;
$user->login($ui);
$this->redirect($user->returnUrl);
}
else throw new CHttpException(401, $ui->error);
}
If I want to use other services like linkedin, facebook, twitter just to sign up the user should I just change the scope and parameters or also have to make some changes elsewhere. How do I store user information in my own database?
In simple case you may use the table "identities" with fields "*external_id*" and "provider". Every OAuth provider must give unique user identificator (uqiue only for that provider). To make it unique on your site you may use pair with provider predefined name (constant). And any other additional fields (if a provider gives it).
In the same table you should store identity data of internal authorization, with provider name 'custom' (for ex.). To store password and other data use a separate table, and PK from this table would be your "*external_id*". Universal scheme.
And PHP, something like this:
class UserIdentity extends CUserIdentity
{
protected $extUserID;
public function __construct($extUserID)
{
$this->extUserID = $extUserID;
}
...
public function authenticate()
{
...
//After search $this->extUserID as PK in users table (built-in authorization)
...
$identity = Identities::model()->findByAttributes(array(
'ext_id' => $this->extUserID,
'service' => 'forum',
));
if(!count($identity))
{
$identity = new Identities;
$identity->ext_id = $this->extUserID;
$identity->service = 'forum';
$identity->username = $userData['username'];
$identity->save();
}
$this->setState('id', $identity->id);
...
}
}

Resources