Non-admin read-only access to Google Admin SDK - google-apps

In a post on the Google Developers blog from September 23, 2014, it says:
Read access to all domain users
Historically, only admins have been able to access the data in the Admin SDK.
Beginning today, any user (not just admins) will now be able to call the
Directory API to read the profile of any user on the domain (of course, we
will respect ACLing settings and profile sharing settings).
However, despite checking every Google Apps Admin setting I can find, my calls calls to the Directory API fail for non-admin users. Condensed code:
params = {
client_id: XXXXXX,
scope: 'https://www.googleapis.com/auth/admin.directory.user.readonly',
response_type: 'token id_token',
immediate: true
};
gapi.auth.authorize(params, gHandleAuthResult);
var request = gapi.client.request({
'path': '/admin/directory/v1/users',
'params': {
'customer': 'my_customer',
}
});
request.then(function (response) {
var users = response.result.users;
if (!!users && users.length > 0) {
users.forEach(function (user) {
newMember.id = user.id || '';
}
}
}
This is using the Google API Client Library for JavaScript. I've tried this on multiple Google Apps accounts, it always works for admin accounts, never for non-admins, for whom I get the response "Not Authorized to access this resource/api".
A previous post asked about this and received a response that you have to use a service account, but that was from June 2014, before the blog post. I have succeeded in making the call using a service account, but would rather not have to do so as it requires a server to act as a bridge.
The Google developer docs say that "Google engineers monitor and answer against the tag google-admin-sdk", so hoping for an answer from Google here.

Try:
var request = gapi.client.request({
'path': '/admin/directory/v1/users',
'viewType': 'domain_public'
'params': {
'customer': 'my_customer',
}
});
viewType=domain_public is needed to perform Directory operations as a non-admin as described in the reference documentation. There's also a bit more explanation in the user accounts docs.

Related

SharePoint keeps on asking credentials in pop up

We have created one SharePoint List custom form having Rest API and when users having Contribute access are trying to submit the form, they are getting pop up asking for credentials again and again. Although the functionality is working fine with Full Access and site collection admin users.
page is also becoming unresponsive after some time. Please assist
If you have client-side JavaScript executing REST calls, it will always run in the context of the current user, which means you cannot do anything in a REST call that the current user does not have permission to do themselves.
If you are using an on-premises installation of SharePoint Server that is using integrated NTLM security (meaning your Active Directory users are usually automatically logged into SharePoint without entering their credentials), then when your code attempts a client-side REST call that attempts to perform an action that the current user is not authorized for, the browser will automatically prompt them for AD credentials for a user account that Does have access.
If you are using an Online environment or one without integrated security, then instead of re-prompting the users for credentials, your code will just receive a 401 Unauthorized.
If your SharePoint farm is using integrated security with your local domain, there is no way to directly stop the user from being prompted for credentials when you try to access a resource they do not have access to. Instead, you will need to use the REST API to see if the current user has permission to perform the action, and display a more friendly error if they do not.
The following is an example, borrowed from a previous stack exchange post on checking a user's permissions:
function checkPermissions() {
var call = jQuery.ajax({
url: _spPageContextInfo.webAbsoluteUrl +
"/_api/Web/effectiveBasePermissions",
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=verbose"
}
});
call.done(function (data, textStatus, jqXHR) {
var manageListsPerms = new SP.BasePermissions();
manageListsPerms.initPropertiesFromJson(data.d.EffectiveBasePermissions);
var manageLists = manageListsPerms.has(SP.PermissionKind.manageLists);
var message = jQuery("#message");
message.text("Manage Lists: " + manageLists);
});
}

Using generated page access_token in multiple regions

I am trying to subscribe my facebook chatbot to the user's page, using the manage_pages permission.
I already have the permission approved to my app, but I am having trouble, because the token is generated in the browser(in Brazil), but I send it to aws lambda in US west 2, and I get the following error
The \'manage_pages\' permission must be granted before impersonating a user\'s page.
In my test, I run the subscribe process in my machine, and reuse this token in AWS Lambda(US West 2). After researching and making some tests, I figured out that it just dont work in USA, in Brazil it works.
Using the same token generated in the process, I run the following test.
var request = require("request")
request.post("https://graph.facebook.com/v2.9/me/messages", {
qs: {
access_token: "GENERATED TOKEN"
},
json: {
"recipient": {
"id": "USER_ID"
},
"message": {
"text": "hello!"
}
}
},function(e){
if(e){
return console.log("ERROR",e)
}
console.log("FINISH",arguments)
})
When I run with my internet, it works fine, but If I use a VPN in Miami, the error occurs.
I believe it has something to do with the data replication that facebook does, but how can I make this token be accessible by the USA region?
EDIT
I found this error when starting the facebook login process with the VPN online:
I didnt find any restrictions in my app, where can I check this?
The problem was occurring because my app had coutry restriction to only Brazil, I added United States to restrictions and it worked.
The restriction can be changed in Settings -> Advanced, App Restrictions

Azure App Service Facebook

I've migrated from Azure Mobile Service to an App Service but I'm having difficulty working out how best to implement extended Facebook auth.
In my old implementation I inherited from FacebookLoginProvider and fetched the token from the claims. I then added the CustomFacebookLoginProvider to my login providers. I then use the token to fetch more information about the user (their date of birth, friends and gender). With this information I created a user object and saved it to my DB.
Does anyone have any suggestions on how best to recreate this in App Service as I can't find any documentation.
As far as how to set up Facebook authentication, you can find documentation here (and it sounds like you've already figured out this much):
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-facebook-authentication/
Now that Facebook authentication is set up, you can refer to the following which shows how to obtain user information:
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-dotnet-backend-how-to-use-server-sdk/#user-info
// Get the credentials for the logged-in user.
var credentials =
await this.User
.GetAppServiceIdentityAsync<FacebookCredentials>(this.Request);
if (credentials.Provider == "Facebook")
{
// Create a query string with the Facebook access token.
var fbRequestUrl = "https://graph.facebook.com/me?access_token="
+ credentials.AccessToken;
// Create an HttpClient request.
using (var client = new System.Net.Http.HttpClient())
{
// Request the current user info from Facebook.
using (var resp = await client.GetAsync(fbRequestUrl))
{
resp.EnsureSuccessStatusCode();
// Do something here with the Facebook user information.
var fbInfo = await resp.Content.ReadAsStringAsync();
}
}
}
Note that you must add a using statement for System.Security.Principal to make the GetAppServiceIdentityAsync extension method work.
For more information on which Facebook user properties you can query, see the Facebook documentation here: https://developers.facebook.com/docs/graph-api/reference/user. Note that you may need to specify which user properties you want as an additional fields query string parameter on your call to the Facebook graph.
The only change I had to make when switching from Mobile Service to Mobile App was to change the end of the callback URL in the developer portal to use /.auth/login/facebook/callback instead of /signin-facebook and it worked exactly the same way as before.
Note that this is for a Windows app with a .NET backend; you didn't specify what you're using so your mileage may vary.
I've been using the following approach to obtain the Facebook Access Token in the iOS app.
App Services includes the Facebook Access Token in the request header, refer to https://azure.microsoft.com/en-in/documentation/articles/app-service-api-authentication/.
To get to the access token, create a Custom API in the Azure Portal, e.g. facebookUserInfo, with the following code:
module.exports = {
"get": function (request, response, next) {
response.send(200, { facebookAccessToken: request.headers['x-ms-token-facebook-access-token'] });
}};
In the iOS app, use the following code to query the custom API:
let client = self.table!.client
if client.currentUser != nil {
client.invokeAPI("facebookUserInfo", body: nil, HTTPMethod: "GET", parameters: nil, headers: nil, completion: { (result, response, error) -> Void in
if let resultDict = result {
if let facebookAccessToken = resultDict["facebookAccessToken"]! {
print(facebookAccessToken)
}
}
}
}
By Using the Easy Auth feature of Azure App Services, I dont need to worry about authentication.
I have a blogpost on this. I have explained on how we can use the FB GraphApi's to query FB data. Here is the link: https://blogs.msdn.microsoft.com/kaushal/2017/06/08/using-easy-auth-to-query-facebook-information-via-graph-api/
I have the sample code deployed on Github. Here is the link: https://github.com/kaushalp/Facebook-GraphApi-with-EasyAuth

Google Sign-In with Passportjs not getting authenticated

I'm using Sails with Passport for authentication. I'm using passport-google-oauth(OAuth2Strategy) and passport-facebook for enabling Google Sign-in.
I'm not too well-versed with Passport, so pardon me if this is a rookie question. I've set up login via Facebook and it works just fine. With Google, I do receive an authorization code after allowing access to the app, but the I'm eventually not authenticated. I'm guessing the same code should work for both Facebook and Google since the strategies are both based on oauth2.
I'm not even sure what code to share, since I'm using the auto-generated code from sails-generate-auth, but do let me know if there's anything else I can share.
Any ideas on why this might be happening? The app is locally hosted but that's unlikely to be the problem since I am getting to the authorization stage anyway.
I faced the same problem and it was located here in in api/services/passport.js:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
The Google service was not returning a profile.username property.
Because of it, the user is not saved in the database and cannot be authenticated. Then the passport callback receives an empty user, so the function that handles errors is fired and the user is redirected to the login page.
This change allows to use the displayName property as the username:
// If the profile object contains a list of emails, grab the first one and
// add it to the user.
if (profile.hasOwnProperty('emails')) {
user.email = profile.emails[0].value;
}
// If the profile object contains a username, add it to the user.
if (profile.hasOwnProperty('username')) {
user.username = profile.username;
}
/** Content not generated BEGIN */
// If the username property was empty and the profile object
// contains a property "displayName", add it to the user.
if (!user.username && profile.hasOwnProperty('displayName')) {
console.log(profile); // <= Use it to check the content given by Google about the user
user.username = profile.displayName;
}
/** Content not generated END */
// If neither an email or a username was available in the profile, we don't
// have a way of identifying the user in the future. Throw an error and let
// whoever's next in the line take care of it.
if (!user.username && !user.email) {
return next(new Error('Neither a username nor email was available'));
}
You could also use the profile.id property because profile.displayName is not necessarily unique (ie: two Google accounts can have an identical displayName). But it is also true accross different services: a Twitter account could also have the same username than a Facebook account. If both register on your application, you will have a bug. This is a problem from the code generated by sails-generate-auth and you should adapt it with the behavior that you want.
I will propose a PR if this solution works for you too.
Alright, so this ultimately turned out to be a known issue with the API.
TL;DR: Enable the Google+ API and the Contacts API as mentioned here. (The Contacts API isn't required, as #AlexisN-o pointed out in the comments. My setup worked as desired with Contacts API disabled. This obviously depends on what scope you're using.)
I believe it's not a nice way of failing since this was an API error that was prevented from bubbling up. Anyway, I dug into passport.authenticate to figure out what was going wrong. This eventually calls the authenticate method defined in the package corresponding to the strategy (oauth2 in this case). In here (passport-google-oauth/lib/passport-google-oauth/oauth2.js) I found that the accessToken was indeed being fetched from Google, so things should be working. This indicated that there was a problem with the requests being made to the token urls. So I ventured a little further into passport-oauth2/lib/strategy.js and finally managed to log this error:
{ [InternalOAuthError: failed to fetch user profile]
name: 'InternalOAuthError',
message: 'failed to fetch user profile',
oauthError:
{ statusCode: 403,
data: '{
"error": {
"errors": [{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}],
"code": 403,
"message": "Access Not Configured. The API (Google+ API) is not enabled for your project. Please use the Google Developers Console to update your configuration."
}
}'
} }
This was the end of the hunt for me and the first result for the error search led to the correct answer. Weird fix though.

Google OAuth API to get user's email address?

I am playing with Google's OAuth 2.0 Playground using my own personal Google account, but I cannot seem to recover my Gmail address using the playground.
The scope I am using is:
email profile https://www.googleapis.com/auth/plus.login
But when I call the API:
https://www.googleapis.com/oauth2/v2/userinfo
I get various information about the user such as family name, first name, gender, picture, etc. but it does not return the user's email.
How do I retrieve the user's email address? Do I have the wrong scope or am I calling the wrong API? I feel like this should be very simple but I have literally been trying to figure this out for hours and I cannot find an API and scope combination that consistently provides the user's email address.
Update: December 2018
On December 20th, Google announced that the Google+ API would be turned down in March 2019, with intermittent failure starting at the end of January 2019. As part of the the plus.people.get endpoint is deprecated and scheduled to be terminated.
The userinfo endpoint is de-deprecated (see clarification) and should provide the info assuming
You request the https://developers.google.com/identity/sign-in/web/devconsole-project scope and
You request the email field.
Clarification: 24 Jan 2019
Google documented that the userinfo (v2) endpoint was deprecated, but later changed it to "deprecated, but kept available for backwards compatibility".
Current documentation discusses getting profile and email information through the currently supported openid method. This includes using the "userinfo" endpoint specified in their discovery document, as required by OpenID Connect.
At the moment, that URL is https://openidconnect.googleapis.com/v1/userinfo, but this has changed in the past and the discovery document at https://accounts.google.com/.well-known/openid-configuration is the authoritative source for the URL to use.
So, to be clear:
The old userinfo URL is maintained for backwards compatibility
The new userinfo URL is available at the discovery document
Regardless, the plus version of anything (described below) is deprecated and scheduled to be removed.
Original Answer
There are a lot of issues here in what you're doing and how you're trying to do it.
For starters, the https://www.googleapis.com/oauth2/v2/userinfo endpoint is deprecated, and scheduled to be removed in September 2014. It has begun working inconsistently - so don't use it.
As #abraham noted, you'll use the people.get endpoint at https://www.googleapis.com/plus/v1/people/me. This should give you the emails field containing an array of addresses. In your case, there will likely be only one that has a type of "account".
As of 2017: use the email scope. See Authorizing API requests.
This email scope is equivalent to and replaces the
https://www.googleapis.com/auth/userinfo.email scope.
For signing in with Google using OAuth 2.0, there's no need to make a separate request to get user's email.
When Google calls the callback URL, it provides a code in the query string that you could use to exchange for access token and ID token. The ID token is a JWT that contains identity information about the user, which includes the email address.
See more information here: https://developers.google.com/identity/protocols/oauth2/openid-connect
You'll want to add the https://www.googleapis.com/auth/userinfo.email scope or replace https://www.googleapis.com/oauth2/v2/userinfo with it. If you're using the HTML example they provide, you can list multiple scopes separated by a space.
<span
class="g-signin"
data-callback="signInCallback"
data-clientid="{{ plus_id }}"
data-cookiepolicy="single_host_origin"
data-requestvisibleactions="http://schemas.google.com/AddActivity"
data-scope="https://www.googleapis.com/auth/plus.login
https://www.googleapis.com/auth/userinfo.email">
</span>
To retrieve the email address, you need to include the scope: "https://www.googleapis.com/auth/userinfo.email" as mentioned in this document. If this scope is included while you generate the refresh token, you should be able to get the email address of the authenticating user by making the following request:
you can call this with your own access token then will give the response
https://www.googleapis.com/oauth2/v3/userinfo?access_token="YOUR_ACCESS_TOKEN"
response will look like this
{
"sub": "1057abc98136861333615xz",
"name": "My Name",
"given_name": "My",
"family_name": "Name",
"picture": "https://lh3.googleusercontent.com/a-/AOh14qiJarwP9rRw7IzxO40anYi4pTTAU_xseuRPFeeYFg",
"email": "MyName#gmail.com",
"email_verified": true,
"locale": "en"
}
or simply you can just write a function
import requests
def get_user_email(access_token):
r = requests.get(
'https://www.googleapis.com/oauth2/v3/userinfo',
params={'access_token': access_token})
return r.json()
I came here looking why my server did not get email in response to /oauth2/v2/userinfo api call. It was only once that I saw this & it has been working well in past.
The answer gave good lead. While fixing this, there were several other resources that helped. Still I am not sure whether expecting always email in the response is ok. so - put error handling in code in case emails are not returned.
Google api documentation about migrating to google+ signin.
https://www.googleapis.com/auth/userinfo.email scope
People resource documentation
Add google+ api to the project using google developer console. The complimentary (quota) of calls is quite high (20m for google+ signin api per day).
Add error handling & logging in server code in case api returns no emails. In my case, I was looking only type='account' email.
This is actually a bit of a challenge as Google does not provide an email by default. You must specifically request it from Google Plus.
const scope = [
'https://www.googleapis.com/auth/plus.me', // request access here
'https://www.googleapis.com/auth/userinfo.email',
];
auth.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: scope,
});
const plus = google.plus({ version: 'v1', auth });
const me = await plus.people.get({ userId: 'me' });
const userEmail = me.data.emails[0].value;
There is a full version in this blog post I wrote: https://medium.com/#jackscott/how-to-use-google-auth-api-with-node-js-888304f7e3a0
by using google nodejs sdk:
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
googleClientIdPublic,
googleClientSecret,
googleRedirectUriPublic
);
//scope you need: https://www.googleapis.com/auth/userinfo.email
oauth2Client.setCredentials(tokens);
const googleAuth = google.oauth2({
version: "v2",
auth: oauth2Client,
});
const googleUserInfo = await googleAuth.userinfo.get();
const email = googleUserInfo.data.email;
more info
I have been following Prisoner's answer right above, and it helped me... until I received the email from Google Developers about how Google+ API will be shutdown on March 7, 2019.
I scrounged around and found this solution to get the email using an id_token that is returned when you authorize an app with the email scope on your developer console.
From Google Sign-in for Websites:
To validate an ID token in PHP, use the Google API Client Library for
PHP. Install the library (for example, using Composer):
composer require google/apiclient
Then, call the verifyIdToken() function. For example:
require_once 'vendor/autoload.php';
// Get $id_token via HTTPS POST.
$client = new Google_Client(['client_id' => $CLIENT_ID]); // Specify the CLIENT_ID of the app that accesses the backend
$payload = $client->verifyIdToken($id_token);
if ($payload) {
$userid = $payload['sub'];
// If request specified a G Suite domain:
//$domain = $payload['hd'];
} else {
// Invalid ID token
}
This will return an array that contains the user information, that also contains the email of the user who logged in. Hope this helps anyone else.
Please see my answer here to the identical issue:
how to get email after using google OAuth2 in C#?
In your scopes variable. Use the value "email" not the
full https address. Scope keywords in the web link are separated by spaces. I solve your issue with scopes written as: profile email openid.
https://developers.google.com/gmail/api/v1/reference/users/getProfile
For gmails api, add this to nodejs code:
function getUsersEmail (auth) {
const gmail = google.gmail({version: 'v1', auth})
gmail.users.getProfile({
userId: 'me'
}, (err, {data}) => {
if (err) return console.log('The API returned an error: ' + err)
console.log(data.emailAddress)
})
}
Gmails api: https://developers.google.com/gmail/api/guides/
Change the authorizationRequest with given scope: scope=openid%20email%20profile and use userinfoapi. This link worked for me
I suggest the following minimal code, which include '*/userinfo.email' and '#google-cloud/local-auth' package:
const path = require('path');
const { google } = require('googleapis');
const { authenticate } = require('#google-cloud/local-auth');
const scope = [
'https://www.googleapis.com/auth/userinfo.email'
];
async function runSample() {
const auth = await authenticate({
keyfilePath: path.join(__dirname, 'oauth2.keys.json'),
scopes: scope
});
google.options({ auth });
const dat = await google.oauth2('v2').userinfo.get()
console.log(dat.data.email);
}
if (module === require.main) {
runSample().catch(console.error);
}
module.exports = runSample;