Is there a way to sync Google Admin directory users list with the people API directory contacts list? - google-admin-sdk

We've been using the google people api to list internal contacts in a custom staff directory on our company's intranet. We've been using the people api so that we can retrieve profile pictures as well as standard profile fields, as the pictures are blocked when using the google Admin SDK. However we've noticed recently that certain contacts are missing from the people API (e.g. new starters).
I've tried using the Admin SDK users.list method to return the users, and can see that the new users definitely exist in the directory, but using the people.listDirectoryPeople method the same users aren't being returned. Is there a way to sync up the directory people contacts list and the admin directory users list? Or is there something I'm missing in my request that's causing me to retrieve old data?
This is a code snippet to show the parameters I'm using with the API (further down there's more code which gets the other pages of results):
authenticate().then((res) => {
let token = res as JWT;
const service = google.people({ version: 'v1', auth: token });
let params: people_v1.Params$Resource$People$Listdirectorypeople = {
readMask: 'addresses,photos,emailAddresses,locations,names,organizations,phoneNumbers,relations',
pageSize: 500,
sources: ['DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE'],
};
service.people.listDirectoryPeople(params, async (err, res) => {

Related

AWS Making a User Store based on Userpools

I am creating an Android and iOS app and a lot of network features that I'm implementing utilize AWS Amplify. I have an authentication system that sets up and verifies user info through AWS Cognito and AWS User Pools. However, I'm also trying to store more attributes for each user beyond just usernames, emails, phone numbers and passwords. I also want to store things such as scores, number of days logged in, etc. Some AWS services such as S3 appear to allow me to create a database with these attributes, but it appears that everyone can access them, and I need an extremely secure system where the only people who can access these user attributes are the user themselves and me as the admin.
What is the best way to implement this feature within AWS while integrating it with an Android/iOS app? Thank you
I would recommend adding either a Rest API or GraphQL API to your Amplify backend. This will create a secure API that will use a JWT token generated by Cognito for authentication. Your data will be stored in DynamoDB tables that will be generated via the #model directive in your GraphQL schema.
Create a GraphQL API
Navigate into the root of a JavaScript, iOS, or Android project and run:
amplify add api
Select the following options:
Select GraphQL
When asked if you have a schema, say No
Select one of the default samples; you can change this later
Choose to edit the schema and it will open the new schema.graphql in your editor
A simple model for tracking user scores and days since last log in might look like:
type UserData #model {
id: ID!
cognitoUserId: String!
score: Float!
lastLoggedInAt: AWSDate!
}
More details on building an Amplify GraphQL API here https://docs.amplify.aws/cli/graphql-transformer/overview/
Create a REST API
Follow the wizard to create a new app. After finishing the wizard run:
amplify add api
Select the following options:
Please select from one of the below-mentioned services: REST
Provide a friendly name for your resource to be used as a label for this - category in the project: itemsApi
Provide a path (e.g., /book/{isbn}): /items
This will be the configuration for /items path in API Gateway:
/
|_ /items Main resource. Eg: /items
ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
OPTIONS Allow pre-flight requests in CORS by browser
|_ /{proxy+} Proxy resource. Eg: /items/, /items/id, items/object/{id}
ANY Includes methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
OPTIONS Allow pre-flight requests in CORS by browser
More on creating an Amplify REST API here

How can I post content to an app users page, linking to their website, from an app running on my website?

I am creating an app for a client that allows their clients to schedule posts and post to facebook from the app.
Clients log into my-domain and the app requests permissions to post to their pages. I am fine with getting a list of pages from facebook and requesting the pages_read_engagement and pages_manage_posts permissions.
My app needs to allow users to select an image (hosted on my-domain) and post that with a link that points at their-domain, along with the post content. Here is an example of an api call I am making using the Javascript SDK (from my app at my-domain):
FB.api('/' + page_id + '/feed', 'POST', {
message: post.content,
link: user.website,
picture: image.url,
access_token: page_access_token
}, () => {})
I get back an error:
(#100) Only owners of the URL have the ability to specify the picture, name, thumbnail or description params.
I have had a read through the domain verification docs but I don't understand what I need to do next. Do all of my users have to verify their own domain via DNS verification? Is there a way for me to allow users to post to their own pages, linking to their websites, from my app, without them having to do anything technical? Am I missing something?

3-legged OAuth and one-time-code flow using google-auth-library-ruby with google-api-ruby-client

Quick Overview: I have a ruby app that runs nightly and does something with a user's google calendar. The user has already given access via a separate react app. I'm having trouble getting the ruby app to access the user's calendar with the authorization code from the react app.
Details: I have a React front-end that can sign in a user using gapi and subsequently sign the user into Firebase. Here is how I configure the gapi obj:
this.auth2 = await loadAuth2WithProps({
apiKey: config.apiKey, // from firebase
clientId: config.clientId, // from gcp
// ....
access_type: "offline", // so we get get authorization code
})
Here is sign in:
doSignInWithGoogle = async () => {
const googleUser = await this.auth2.signIn();
const token = googleUser.getAuthResponse().id_token;
const credential = app.auth.GoogleAuthProvider.credential(token);
return this.auth.signInWithCredential(credential);
};
The user's next step is to grant the app offline access to their calendar:
doConnectGoogleCalendar = async () => {
const params = {scope:scopes};
const result = await this.auth2.grantOfflineAccess(params);
console.log(result.code); // logs: "4/ygFsjdK....."
};
At this point the front end has the authorization code that can be passed to a server-side application to be exchanged for access and refresh tokens. I haven't been able to find a good way to use a user supplied auth-code to make calls to available scopes. This is how I've configured the oauth client:
auth_client = Google::APIClient::ClientSecrets.load(
File.join(Rails.root,'config','client_secrets.json') // downloaded from GCP
).to_authorization
^ I'm using the same GCP Credentials on the backend that I'm using for the frontend. It is a "OAuth 2.0 Client ID" type of credential. I'm unsure if this is good practice or not. Also, do I need to define the same config that I do on the frontend (like access_type and scope)?.
Next I do what the docs say to get the access and refresh tokens(click Ruby):
auth_client.code = authorization_code_from_frontend
auth_client.fetch_access_token!
---------
Signet::AuthorizationError (Authorization failed. Server message:)
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
Is there something I'm missing in setting up a separate backend application that can handle offline access to a user granted scope? There is so much different information on these libraries but I haven't been able to distill it down to something that works.
UPDATE
I found this page describing the "one-time-code flow" which I haven't found anywhere else is all of the docs I've gone through. It does answer one of my minor questions above: Yes, you can use the same client secrets as the web application for the backend. (see the full example at the bottom where they do just that). I'll explore it more and see if my bigger problem can be resolved. Also going to update the title to include one-time-code flow.
After a good amount of digging through code samples and source code, I have a clean working solution. Once I found the page in my "update" it led me to finding out that ClientSecrets way I was doing things had been deprecated in favor of the google-auth-library-ruby project. I'm glad I found it because it seems to be a more complete solution as it handles all of the token management for you. Here is code to setup everything:
def authorizer
client_secrets_path = File.join(Rails.root,'config','client_secrets.json')
client_id = Google::Auth::ClientId.from_file(client_secrets_path)
scope = [Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY]
redis = Redis.new(url: Rails.application.secrets.redis_url)
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: redis)
Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, "postmessage")
end
and then this is how I use the authorization code:
def exchange_for_token(user_id,auth_code)
credentials_opts = {user_id:user_id,code:auth_code}
credentials = authorizer.get_and_store_credentials_from_code(credentials_opts)
end
after calling that method the library will store the exchanged tokens in Redis (you can configure where to store) for later use like this:
def run_job(user_id)
credentials = authorizer.get_credentials(user_id)
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = credentials
calendar_list = service.list_calendar_lists.items
# ... do more things ...
end
There is so much info out there that it is difficult to isolate what applies to each condition. Hopefully this helps anyone else that gets stuck with the "one-time-code flow" so they don't spend days banging their head on their desk.

Django REST framework - prevent data access for user view?

In my api, I have a /users endpoint which currently shows (eg address) details of all users currently registered. This needs to be accessed by the (Ember) application (eg to view a user shipping address) but for obvious reasons I can't allow anyone to be able to view the data (whether that be via the browsable api or as plain JSON if we restrict a view to just use the JSONRenderer). I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
Am I missing something?
UPDATE
Hi, I wanted to come back on this.
For authentication on the Ember side I'm using Ember Simple Auth and token based authentication in Django. All is working fine - I can log into the Ember app, and have access to the token.
What I need to be able to do is to access the user; for this I followed the code sample here https://github.com/simplabs/ember-simple-auth/blob/master/guides/managing-current-user.md
I have tested the token based authentication in Postman, using the token for my logged in user - and can access the /users endpoint. (This is returning all users - what I want is for only the user for whom I have the token to be returned but that's for later!).
The question is how to do I pass the (token) header in any Ember requests, eg
this.store.findAll('user') .... etc
This is clearly not happening currently, and I'm not sure how to fix this.
UPDATE
Fixed it. Turns out that the authorize function in my application adapter was not setting the headers, so have changed the code to set the headers explicitly:
authorize(xhr) {
let { access_token } = this.get('session.data.authenticated');
if (isPresent(access_token)) {
xhr.setRequestHeader('Authorization', `Token ${access_token}`);
}
},
headers: computed('session.data.authenticated.token', function () {
const headers = {};
if (this.session.isAuthenticated) {
headers['Authorization'] = `Token ${this.session.data.authenticated.token}`
}
return headers;
})
Ember is framework for creating SPAs. These run in the browser. So for Ember to get the data, you have to send the data to the browser.
The browser is completely under the control of the user. The browser is software that works for them, not for the owner of the website.
Any data you send to the browser, the user can access. Full stop.
If you want to limit which bits of the data the user can read from the API, then you need to write the logic to apply those limits server-side and not depend on the client-side Ember code to filter out the bits you don't want the user to see.
I don't think I can use authentication and permissions, since the application needs to log a user in from the front end app (I am using token based authentication) in the first instance. If I use authentication on the user view in Django for instance, I am unable to login from Ember.
This doesn't really make sense.
Generally, this should happen:
The user enters some credentials into the Ember app
The ember app sends them to an authentication endpoint on the server
The server returns a token
The ember app stores the token
The ember app sends the token when it makes the request for data from the API
The server uses the token to determine which data to send back from the API

Google Directory API users list gives 404 Not found

I'm using the javascript client library to try to get a list of users in a domain, but I'm getting a 404 not found in the response.
gapi.client.load('admin', 'directory_v1', function() {
var request = gapi.client.directory.users.list({ domain: "mydomain.com"});
request.execute(function(resp) {
console.log(resp);
});
});
I have a load function before this that gets the userinfo and that works fine, it's just the directory api I can't get working. I added the admin.directory.user scope, and I have the proper client id and api key. I enabled the Admin SDK in the Services tab of my project. The account that the project was created in has all administrator privileges except super admin. Is there some extra step you have to take to use admin sdk apis? What am I missing?
Any help would be appreciated.
You need to grant Oauth access for ClientId in admin console.
Security -> Advanced settings -> Manage Oauth client access
And then add your client ID and the scope needed.