Custom connector looker studio advanced services auth not working - google-bigquery

I cannot connect my GCP Service Account to Looker Studio Advanced Services to connect Big Query with my custom connector
I tried to follow this tutorial and i've created a OAuth2 code to retrieve token as follows:
const getOAuthAccessToken = () => {
const oauthParams = {
serviceName: 'BigQuery',
serviceAccount,
scopes: ['https://www.googleapis.com/auth/bigquery.readonly'],
};
const oauthService = createService(
oauthParams.serviceName,
oauthParams.serviceAccount,
oauthParams.scopes
);
if (!oauthService.hasAccess()) {
Logger.log('BQ ERROR IS ' + oauthService.getLastError());
throw oauthService.getLastError();
}
return oauthService.getAccessToken();
};
const createService = (name, serviceAccount, scopes, userToImpersonate) => {
return OAuth2.createService(name)
.setSubject(userToImpersonate)
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
.setPrivateKey(serviceAccount.private_key)
.setIssuer(serviceAccount.client_email)
.setPropertyStore(PropertiesService.getScriptProperties())
.setCache(CacheService.getUserCache())
.setLock(LockService.getUserLock())
.setScope(scopes);
};
Also, when i'm trying to add the connector to the Looker Studio, nothing happens and looking at Network tab with Inspect element i saw that Looker Studio makes a REST call to Google services and it responds with this:
Looker Studio response when i'm trying to add my connector

Related

API Authorization Tool

I'm working on an application with RESTful API endpoints that needs proper authorization security using an RBAC system. So far, I've looked into Keycloak. It looks promising at first but doesn't support granular authorization control of an endpoint, which is a hard requirement. For example, if I have the endpoint /object/<object:id>, a list of object IDs [1,2,3,4] and a test user, there's no way to restrict the test user to only have access to object IDs [1,2] but not [3,4] for the same endpoint. It seems the user will have access to all the IDs or none. Perhaps this can be accomplished by customizing or extending the base Keycloak server but there isn't enough documentation on the Keycloak website on how to do so.
I've done a search for other RBAC permissions systems but haven't been able to find much. Are there any authorization systems out there that can accomplish this?
but doesn't support granular authorization control of an endpoint
Check out Auth0's Fine Grained Authorization solution: https://docs.fga.dev/. (Disclaimer: I am employed by Auth0).
In your specific case you would need to create an authorization model like
type object
relations
define reader as self
And then add the following tuples in the FGA store using the Write API:
(user:test, relation:reader, object:1)
(user:test, relation:reader, object:2)
Then, in your API, you would do something like this:
const { Auth0FgaApi } = require('#auth0/fga')
const express = require('express')
const app = express()
const fgaClient = new Auth0FgaApi({
storeId: process.env.FGA_STORE_ID, // Fill this in!
clientId: process.env.FGA_CLIENT_ID, // Fill this in!
clientSecret: process.env.FGA_CLIENT_SECRET // Fill this in!
});
app.get('/objects/:id', async (req, res) => {
try {
const { allowed } = await fgaClient.check({
tuple_key: {
user: req.query.user,
relation: 'reader',
object: "object:" + req.params.id
}
});
if (!allowed) {
res.status(403).send("Unauthorized!")
} else {
res.status(200).send("Authorized!")
}
} catch (error) {
res.status(500).send(error)
}
});
const port = 3000
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

Is there a way to call the google books api from firebase auth to get a logged in users bookshelves in "My Library"?

I have the firebase auth set up and can log in, get the consent screen to access google books account, get the token, and log out.
Google will be deprecating the gapi.auth2 module and using Google Identity Services so I'm trying to find a solution that doesn't involve using the gapi.auth2 module.
The following website has an example of how to use the Google Identity Services:
https://developers.google.com/identity/oauth2/web/guides/migration-to-gis#gis-only
In this example they use the Google Identity Services library to get the access_token to then pass it along in the The XMLHttpRequest object to request data from a logged in users account. Seeing as I can already get the access_token I'm trying to make the request without using the Google Identity Service or Google API Client Library for JavaScript and just use a XMLHttpRequest.
The below code returns a response of the index.html page and not the response from the google books API.
function getData(access_token){
if(access_token !== undefined){
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Typical action to be performed when the document is ready:
console.log(xhttp.responseText);
}
};
xhttp.open("GET", "books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id)");
xhttp.setRequestHeader('Authorization', 'Bearer ' + access_token);
xhttp.send();
}
}
function Login(){
useEffect(()=>{
signInWithGoogle();
});
provider.addScope("https://www.googleapis.com/auth/books");
const signInWithGoogle = () =>{
signInWithPopup(auth, provider).then((result)=>{
// This gives you a Google Access Token. You can use it to access the Google API.
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
if(result.user){
getData(token);
}
}).catch((error)=>{
if(error.code === 'auth/popup-closed-by-user'){
}
});
}
return (
<p>Logging in...</p>
)
}
export default Login;
I know that "books/v1/mylibrary/bookshelves/4/volumes?fields=totalItems, items(id)" works because I've used it in a working version that doesn't use firebase as it's just a plain html version that uses the Google Services Identity library with the Google API Client Library for JavaScript. The network request look the same and have the same access_token with my adapted version and the plain html version that works.
Is there a way to call the google books API from firebase auth to get a logged in users bookshelves in "My Library"?

google identity services equivalent for googleUser.getBasicProfile()

I have a react app where I'm trying to migrate from using gapi.auth2 module in the Google API Client Library for JavaScript to the Google Identity Services Library.
With gapi.auth2 module you could get the signed in users basic profile info with googleUser.getBasicProfile(). The following code is how you log a user in with the Google Identity Services Library.
Login.js
function Login(){
var tokenClient;
var access_token;
function getToken(){
tokenClient.requestAccessToken();
}
function initGis(){
tokenClient = window.google.accounts.oauth2.initTokenClient({
client_id: '********.apps.googleusercontent.com',
scope: 'https://www.googleapis.com/auth/books',
callback: (tokenResponse) => {
access_token = tokenResponse.access_token;
},//end of callback:
});
}
useEffect(()=>{
initGis();
getToken();
});
return (
<>
<p>Logging in...</p>
</>
)
}
export default Login;
How do you get the users basic profile info when using the Google Identity Services Library?
Let me keep this answer short.🙂 Once you get the access_token just invoke the following function:
const getUserProfileData = async (accessToken: string) => {
const headers = new Headers()
headers.append('Authorization', `Bearer ${accessToken}`)
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
headers
})
const data = await response.json();
return data;
}
PS: [Unfortunately] I am also working on migrating to Google Identity Services Library. 😰
After a discussion on Discord where a very helpful user explained that it can only be done server side. So the simple answer is that it can't be done client side using the Google Identity Services Library
I was faced with the same issue migrating my web app to Google Identity Services. I resolved it by using the Google Drive API About:get method, and requested user fields. This returns the user's displayName and emailAddress (plus some other data that's really not very useful). I use the drive.readonly scope, but I believe a less sensitive scope like drive.appdata or drive.file would work.
You can try this:
function getTokenInfos(token) {
var splitResponse = token.split(".");
var infos = JSON.parse(atob(splitResponse[1]));
return infos;
}

How to create a event in MS Teams Calendar using MS Graph API from a backend service

I am a newbie in using Microsoft Graph API, I really interested to implement the graph API possibilities in my .Net Core application. I decided to create a sample application, that create meetings in MS Teams app. I have already done the steps listed below.
I register a new app in Azure Active Directory.
Assign 'Calendars.Read' and 'Calendars.ReadWrite' (Permission type - Application) permissions.
I know there are two types of authentication for permission. Delegated and Application.
Permissions
Code
try
{
var config = this.LoadAppSettings();
GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);
var #event = new Event
{
Subject = "My event by ragesh",
Start = new DateTimeTimeZone
{
DateTime = "2020-06-11T07:44:21.358Z",
TimeZone = "UTC"
},
End = new DateTimeTimeZone
{
DateTime = "2020-06-18T07:44:21.358Z",
TimeZone = "UTC"
}
};
await graphClient.Me.Events
.Request()
.AddAsync(#event);
}
catch (Exception ex)
{
throw ex;
}
But when I execute my code to create events in graph API it shows an authentication error.
Error
When you use application permissions, you cannot use the /me API url segment, since there is no authenticated user. You must instead use /users/<user-id> in it's place.
You're using the .NET SDK, so that translates to you cannot use graphClient.Me, you must use graphClient.Users[userId].

Adal.js does not get tokens for external api endpoint resource

I'm trying out adal.js with an Angular SPA (Single Page Application) web site that gets data from an external Web API site (different domain). Authentication against the SPA was easy with adal.js, but getting it to communicate with the API is not working at all when bearer tokens are required. I have used https://github.com/AzureAD/azure-activedirectory-library-for-js as template in addition to countless blogs.
The problem is that when I set up endpoints while initiating adal.js, adal.js seems to redirect all outgoing endpoint traffic to microsofts login service.
Observations:
Adal.js session storage contains two adal.access.token.key entries. One for the client ID of the SPA Azure AD application and one for the external api. Only the SPA token has a value.
If I do not inject $httpProvider into adal.js, then calls go out to the external API and I get a 401 in return.
If I manually add the SPA token to the http header ( authorization: bearer 'token value') I get a 401 in return.
My theory is that adal.js is unable to retrieve tokens for endpoints (probably because I configured something wrong in the SPA) and it stops traffic to the endpoint since it is unable to get a required token. The SPA token cannot be used against the API since it does not contain the required rights. Why is adal.js not getting tokens for endpoints and how can I fix it?
Additional information:
The client Azure AD application is configured to use delegated permissions against the API and oauth2AllowImplicitFlow = true in app manifest.
The API Azure AD application is configured for impersonation and oauth2AllowImplicitFlow = true (do not think that is required, but tried it). It is multi tenant.
The API is configured to allow all CORS origins and it works correctly when used by another web app using impersonation (hybrid MVC (Adal.net) + Angular).
Session storage:
key (for the SPA application): adal.access.token.keyxxxxx-b7ab-4d1c-8cc8-xxx value: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1u...
key (for API application): adal.access.token.keyxxxxx-bae6-4760-b434-xxx
value:
app.js (Angular and adal configuration file)
(function () {
'use strict';
var app = angular.module('app', [
// Angular modules
'ngRoute',
// Custom modules
// 3rd Party Modules
'AdalAngular'
]);
app.config(['$routeProvider', '$locationProvider',
function ($routeProvider, $locationProvider) {
$routeProvider
// route for the home page
.when('/home', {
templateUrl: 'App/Features/Test1/home.html',
controller: 'home'
})
// route for the about page
.when('/about', {
templateUrl: 'App/Features/Test2/about.html',
controller: 'about',
requireADLogin: true
})
.otherwise({
redirectTo: '/home'
})
//$locationProvider.html5Mode(true).hashPrefix('!');
}]);
app.config(['$httpProvider', 'adalAuthenticationServiceProvider',
function ($httpProvider, adalAuthenticationServiceProvider) {
// endpoint to resource mapping(optional)
var endpoints = {
"https://localhost/Api/": "xxx-bae6-4760-b434-xxx",
};
adalAuthenticationServiceProvider.init(
{
// Config to specify endpoints and similar for your app
clientId: "xxx-b7ab-4d1c-8cc8-xxx", // Required
//localLoginUrl: "/login", // optional
//redirectUri : "your site", optional
extraQueryParameter: 'domain_hint=mydomain.com',
endpoints: endpoints // If you need to send CORS api requests.
},
$httpProvider // pass http provider to inject request interceptor to attach tokens
);
}]);
})();
Angular code for calling endpoint:
$scope.getItems = function () {
$http.get("https://localhost/Api/Items")
.then(function (response) {
$scope.items = response.Items;
});
Ok, I've been bashing my head against the wall to figure this out. Trying to make my ADAL.js SPA app (sans angular) successfully make cross-domain XHR requests over to my precious CORS-enabled Web API.
This sample app, the one all the newbies like me are using, has this problem: it features an API and SPA all served from the same domain - and only requires a single AD Tenant app registration. This only confuses things when it comes time to pull things apart into separate pieces.
So, out of the box, the sample has this Startup.Auth.cs which works OK, as far as the sample goes...
public void ConfigureAuth(IAppBuilder app) {
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
});
}
but, you need to modify the above code, drop the Audience assignment, and go for an array of audiences.. That's right: ValidAudiences .. So, for every SPA client that is talking to your WebAPI, you'll want to put the ClientID of your SPA registration in this array...
It should look like this...
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new [] {
ConfigurationManager.AppSettings["ida:Audience"],//my swagger SPA needs this 1st one
"b2d89382-f4d9-42b6-978b-fabbc8890276",//SPA ClientID 1
"e5f9a1d8-0b4b-419c-b7d4-fc5df096d721" //SPA ClientID 2
},
RoleClaimType = "roles" //Req'd only if you're doing RBAC
//i.e. web api manifest has "appRoles"
}
});
}
EDIT
Ok, based on #JonathanRupp's feedback, I was able to reverse out the Web API solution I was using shown above, and was able to modify my client JavaScript as shown below to make everything work.
// Acquire Token for Backend
authContext.acquireToken("https://mycorp.net/WebApi.MyCorp.RsrcID_01", function (error, token) {
// Handle ADAL Error
if (error || !token) {
printErrorMessage('ADAL Error Occurred: ' + error);
return;
}
// Get TodoList Data
$.ajax({
type: "GET",
crossDomain: true,
headers: {
'Authorization': 'Bearer ' + token
},
url: "https://api.mycorp.net/odata/ToDoItems",
}).done(function (data) {
// For Each Todo Item Returned, do something
var output = data.value.reduce(function (rows, todoItem, index, todos) {
//omitted
}, '');
// Update the UI
//omitted
}).fail(function () {
//do something with error
}).always(function () {
//final UI cleanup
});
});
ADAL.js does get the access_token apart from id_token for calling Azure AD protected API running on different domain.
Initially, during login, it only takes id_token. This token has the access for accessing resource of the same domain.
But, on calling the API running in different domain, adal interceptor checks if the API URL is configured in as endpoint in adal.init().
It is only then that the access token is called for the requested resource. It also necessitates that the SPA is configured in the AAD to access API APP.
The key to achieve this is following:
1. Add endpoints in the adal.init()
var endpoints = {
// Map the location of a request to an API to a the identifier of the associated resource
//"Enter the root location of your API app here, e.g. https://contosotogo.azurewebsites.net/":
// "Enter the App ID URI of your API app here, e.g. https://contoso.onmicrosoft.com/TestAPI",
"https://api.powerbi.com": "https://analysis.windows.net/powerbi/api",
"https://localhost:44300/": "https://testpowerbirm.onmicrosoft.com/PowerBICustomServiceAPIApp"
};
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'common',
clientId: '2313d50b-7ce9-4c0e-a142-ce751a295175',
extraQueryParameter: 'nux=1',
endpoints: endpoints,
requireADLogin: true,
//cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
// Also, token acquisition for the To Go API will fail in IE when running on localhost, due to IE security restrictions.
},
$httpProvider
);
Give permission to the SPA application in Azure AD to access the API application:
You may refer this link for details : ADAL.js deep dive
You need to make your Web API aware of your Client application. It's not enough to add delegated permission to API from your Client.
To make the API client aware, go to Azure management portal, download API's manifest and add ClientID of your Client application to the list of "knownClientApplications".
To allow Implicit flow you need to set "oauth2AllowImplicitFlow" to true in the manifest as well.
Upload the manifest back to API application.
I'm not sure if our setup is exactly the same, but I think it it comparable.
I have a Angular SPA that uses and external Web API through Azure API Management (APIM). My code might not be best practice, but it works for me so far :)
The SPAs Azure AD app has a delegated permission to access the External APIs Azure AD app.
The SPA (is based upon the Adal TodoList SPA sample)
app.js
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: 'mysecrettenant.onmicrosoft.com',
clientId: '********-****-****-****-**********',//ClientId of the Azure AD app for my SPA app
extraQueryParameter: 'nux=1',
cacheLocation: 'localStorage', // enable this for IE, as sessionStorage does not work for localhost.
},
$httpProvider
);
Snippet from the todoListSvc.js
getWhoAmIBackend: function () {
return $http.get('/api/Employee/GetWhoAmIBackend');
},
Snippets from the EmployeeController
public string GetWhoAmIBackend()
{
try
{
AuthenticationResult result = GetAuthenticated();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var request = new HttpRequestMessage()
{
RequestUri = new Uri(string.Format("{0}", "https://api.mydomain.com/secretapi/api/Employees/GetWhoAmI")),
Method = HttpMethod.Get, //This is the URL to my APIM endpoint, but you should be able to use a direct link to your external API
};
request.Headers.Add("Ocp-Apim-Trace", "true"); //Not needed if you don't use APIM
request.Headers.Add("Ocp-Apim-Subscription-Key", "******mysecret subscriptionkey****"); //Not needed if you don't use APIM
var response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var res = response.Content.ReadAsStringAsync().Result;
return res;
}
return "No dice :(";
}
catch (Exception e)
{
if (e.InnerException != null)
throw e.InnerException;
throw e;
}
}
private static AuthenticationResult GetAuthenticated()
{
BootstrapContext bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext;
var token = bootstrapContext.Token;
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authContext =
new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/mysecrettenant.onmicrosoft.com");
//The Client here is the SPA in Azure AD. The first param is the ClientId and the second is a key created in the Azure Portal for the AD App
ClientCredential credential = new ClientCredential("clientid****-****", "secretkey ********-****");
//Get username from Claims
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
//Creating UserAssertion used for the "On-Behalf-Of" flow
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
//Getting the token to talk to the external API
var result = authContext.AcquireToken("https://mysecrettenant.onmicrosoft.com/backendAPI", credential, userAssertion);
return result;
}
Now, in my backend external API, my Startup.Auth.cs looks like this:
The external API
Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
SaveSigninToken = true
},
AuthenticationType = "OAuth2Bearer"
});
}
Please let me know if this helps or if I can be of further assistance.