Update "shared" state of a file/folder with google drive API - node.js

I can't resolve how to change "shared" state to false with the google drive API.
Here is what i do:
I fetch all my folders & files witch are public with [this
one][1]
(I use q:"'me' in owners and visibility != 'limited'" filter)
I take file/folder ID and put it inside [this other one][2]
Inside the response object I got this line i want to change : "shared": true
I don't where I can set it to false, is someone getting any idea?
Have a nice day
Edit: I use NodeJs (netlify function), here is my code to get my files & folders :
const { google } = require('googleapis')
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const REDIRECT_URI = 'https://developers.google.com/oauthplayground'
const REFRESH_TOKEN = process.env.REFRESH_TOKEN
exports.handler = async () => {
const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
oauth2Client.setCredentials({ refresh_token: REFRESH_TOKEN })
const drive = google.drive({
version: 'v3',
auth: oauth2Client,
})
try {
const result = await drive.files.list({
q:"'me' in owners and visibility != 'limited'"
})
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ ...result, Body: result.toString('utf-8') })
}
} catch (e) {
console.log(e.message)
return { statusCode: 500, body: e.message }
}
}
To change visibility ("shared": true -> "shared": false),
I tried #Tanaike answer with :
const fetch = require('node-fetch')
const API_ENDPOINT_A = 'DELETE https://www.googleapis.com/drive/v3/files/'
const API_ENDPOINT_B = '/permissions/anyoneWithLink'
exports.handler = async (event) => {
try {
const itemId = '1-7ESYk_zKJ5Sdfg_z-XiuoXxrKKpHwSa' // event.queryStringParameters.itemId
const response = await fetch(API_ENDPOINT_A + itemId + API_ENDPOINT_B)
const data = await response.json()
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({ data })
}
} catch (error) {
console.log(error)
return {
statusCode: 500,
body: JSON.stringify({ error: 'Failed fetching data' })
}
}
}
But i don't know how I can pass my private info (api key...), I used OAuth 2 for fetch, should I use it too for edit visibility ?
[1]: https://developers.google.com/drive/api/v2/reference/files/list
[2]: https://developers.google.com/drive/api/v2/reference/files/patch

If I understand correctly, you are unable to see the place where you can change the file to "shared": true. If so, in the same link you provided from the official documentation you can find it in the "Request body".

From I fetch all my folders & files witch are public with this one (I use q:"'me' in owners and visibility != 'limited'" filter), when you want to change the permission of publicly shared to the restricted, you can achieve this using Drive API as follows.
Request:
Permissions: delete is used.
DELETE https://www.googleapis.com/drive/v3/files/###fileId###/permissions/anyoneWithLink
Sample curl:
curl --request DELETE \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions/anyoneWithLink'
If the file is publicly shared and not shared with other specific users, when this request is run, "shared": true is changed to "shared": false.
Note:
If you want to remove the permission of the specific user, you can achieve this as follows.
Retrieve the permission ID using Permissions: list.
curl \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions'
Using the retrieved permission ID, you can delete the permission as follows.
curl --request DELETE \
-H 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
'https://www.googleapis.com/drive/v3/files/###fileId###/permissions/###permissionId###'
References:
Permissions: delete
Permissions: list
Added:
From your following replying,
I actualy use nodejs for drive API, I tried the http solution, but idk how can i pass my clientId and secretId (error 500), You can find my code on my question, i edited it
When you want to achieve to delete permissions with googleapis for Node.js, you can use the following script. In this case, please include the scope of https://www.googleapis.com/auth/drive.
Sample script:
const fileId = "###"; // Please set your file ID.
const drive = google.drive({ version: "v3", auth }); // Please use your client here.
drive.permissions.delete(
{
fileId: fileId,
permissionId: "anyoneWithLink",
},
(err, res) => {
if (err) {
console.error(err);
return;
}
console.log(res.data); // In this case, no values are returned.
}
);
In this sample script, the permission of the publicly shared file is deleted.

I got it working with the following code :
const result = await drive.permissions.delete({
fileId:"YOUR FILE ID",
permissionId:"anyoneWithLink"
})
Here is the full update code :
const { google } = require('googleapis')
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
const REDIRECT_URI = 'https://developers.google.com/oauthplayground'
const REFRESH_TOKEN = process.env.REFRESH_TOKEN
exports.handler = async () => {
const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI)
oauth2Client.setCredentials({ refresh_token: REFRESH_TOKEN })
const drive = google.drive({
version: 'v3',
auth: oauth2Client,
})
try {
const result = await drive.permissions.delete({
fileId:"YOUR FILE ID",
permissionId:"anyoneWithLink"
})
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ ...result, Body: result.toString('utf-8') })
}
} catch (e) {
console.log(e.message)
return { statusCode: 500, body: e.message }
}
}

Related

How to verify ID token from AAD, and then call user info

I am trying to log in from Azure Active Directory, and do get a token in return. How do I now verify said token and log in?
I am also trying to get access_token and get userinfo, but get an error when adding token to response_type.
My client code is:
export function AzureLogin(){
useEffect(async () => {
const { authorization_endpoint } = await fetchJSON(
"https://login.microsoftonline.com/consumers/v2.0/.well-known/openid-configuration"
);
const parameters = {
client_id: "my client id",
response_type: "id_token (id_token%20token when trying to get access_token)",
scope: "openid",
nonce: "123",
redirect_uri: window.location.origin + "/login/azure/callback"
}
window.location.href =
authorization_endpoint + "?" + new URLSearchParams(parameters)
}, [])
}
export function AzureCallback(){
useEffect(async () => {
const { id_token } = Object.fromEntries(
new URLSearchParams(window.location.hash.substring(1))
);
await fetch("/api/login/azure", {
method: "POST",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({ id_token }),
})
})
}
And my server code is:
router.post("/azure", async (req, res) => {
const { id_token } = req.body;
res.cookie("id_token", id_token, {signed: true, maxAge: 2 * 60 * 60 * 1000});
const {userinfo_endpoint} = await fetchJSON(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"
);
const userinfo = await fetchJSON(userinfo_endpoint, {
headers: {
Authorization: `Bearer ${id_token}`,
},
})
console.log(userinfo)
const name = userinfo.name;
const id = userinfo.sub;
const email = userinfo.email;
const picture = userinfo.picture;
db stuff
res.sendStatus(200);
})
To answer your question in your comment.
The purpose of the ID-token is to tell the client details about how the user authenticated. It is typically only used to create the local "session" in the client and then thrown away.
The ID-token typically have a very short lifetime (like 5 minutes in some setups) and it is not supposed to be sent to external services/APIs.
The client is not supposed to look inside the access-token and it doesn't even need to validate it. It is the job of the API that later receives the token to validate the signature of the access-token against the public signing key in AzureAD.

Call cloud function through another in the same project without allUsers permission

I have 2 functions in the same google cloud functions project (myfunction1 and myfunction2.
exports.myfunction1 = async (req, res) => {
await axios({
method: 'post',
url: 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
data: myjson
}).then(response => {
console.log(JSON.stringify(response.data));
}).catch(err => {
console.error("catch error");
console.error(err);
})
}
It is works fine, but only if I configure invokers permission for allUsers. If I remove this permission, e receive 403 code error. Not sounds good keep this permisson activate, because the function is exposed. I tried solve with this link and this link, but, no sucess.
Edit1:
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const targetAudience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2'
const url = '??????????';
async function request() {
console.info('request ${url} with target audience ${targetAudience}');
const client = await auth.getIdTokenClient(targetAudience);
const res = await client.request({url});
console.info(res.data);
}
I'm trying using this code, but, who is const url?
You must perform service to service authentication. You can find a great tutorial in the Cloud Run page (ok you use Cloud Functions but the underlying infrastructure is the same and the doc is better).
You also have to be aware about the Functions identity and how to change them (or to grant the current service account the correct permission)
let audience = 'https://SERVER-PROJECT-ID.cloudfunctions.net/myfunction2';
let token_request_url = 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=' + audience;
var token_response = await axios.get(token_request_url, { headers: {'Metadata-Flavor': 'Google'} });
let token_auth = token_response.data;
axios({
method: 'post',
url: audience,
timeout: 15000,
headers: {
'Authorization': "Bearer " + token_auth
},
data: myJSON
}).catch(err => {
console.error(err);
});

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

How to change google profile picture via API?

I am looking for a way how to change user profile photo programmatically.
Generally, I need to be able to change avatars of domain GSuite users, but if there are API for changing my own avatar is also good.
Had tried already:
https://developers.google.com/admin-sdk/directory/v1/reference/users/photos/update - this API is working but only changes the small icon of a user in admin panel itself. The main profile picture remains the same.
https://developers.google.com/people/api/rest/v1/people/updateContactPhoto - this API throws me an error Resource name "people/10244712xxxxx1564465" is not a valid contact person resource. when I am trying to replace my own profile image. people/me also isn't working.
There are Contacts API, but I am trying to change my own (or impersonated one) profile image, so I believe this API isn't for my case.
If you can point me in some direction to search I'll be very glad. I just do not believe that there is no way to change avatar, it can't be for real.
Update:
Checked Contact API - also doesn't work, looks like it changes an avatar only on me in my contact list, nobody else doesn't see changes, and the main profile picture remains the same.
Code:
// admin directory way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const encodedPhoto = SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7');
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/admin.directory.user',
],
'gsync#***.com', // sub
);
const directory = google.admin({ version: 'directory_v1', auth: JWTClient });
directory.users.photos.update({}, ctx.params.email);
const res = await directory.users.photos.update({
userKey: ctx.params.email,
resource: {
photoData: encodedPhoto,
},
});
// Contacts API way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.google.com/m8/feeds/',
],
ctx.params.email, //sub
);
const res1 = await JWTClient.requestAsync({
headers: {
'GData-Version': 3.0,
},
params: {
alt: 'json',
q: 'arsenyp#***.com',
},
url: `https://www.google.com/m8/feeds/contacts/${ctx.params.email}/full`,
});
const contactIdFull = res1.data.feed.entry.filter((c) => c.gd$email[0].address === ctx.params.email)[0].id.$t;
const [, contactId] = /\/base\/([a-z0-9]+)$/.exec(contactIdFull);
// https://www.google.com/m8/feeds/contacts/{userEmail}/full/{contactId}
const res2 = await JWTClient.requestAsync({
headers: {
'GData-Version': 3.0,
},
params: {
alt: 'json',
},
url: `https://www.google.com/m8/feeds/contacts/${ctx.params.email}/full/${contactId}`,
});
const { href: image, gd$etag: etagJ } = res2.data.entry.link.filter((l) => l.rel === 'http://schemas.google.com/contacts/2008/rel#photo')[0];
const res3 = await axios({
method: 'GET',
url: image,
headers: {
Authorization: `Bearer "${(await JWTClient.getAccessTokenAsync()).token}"`,
},
responseType: 'arraybuffer',
});
const etag = JSON.parse(etagJ);
// PUT /m8/feeds/photos/media/default/contactId
// If-match: Etag
// Content-Type: image/*
const res4 = await axios({
method: 'PUT',
url: `https://www.google.com/m8/feeds/photos/media/default/${contactId}`,
headers: {
Authorization: `Bearer "${(await JWTClient.getAccessTokenAsync()).token}"`,
'Content-Type': 'image/png',
},
// responseType: 'arraybuffer',
data: Buffer.from('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7', 'base64'),
});
// People API way (won't work, throwing an error)
const userId = '1024471xxxxx251564465';
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/contacts',
'profile',
],
ctx.params.email, // sub
);
const people = google.people({ version: 'v1', auth: JWTClient });
const res = await people.people.updateContactPhoto({
resourceName: `people/${userId}`,
resource: {
photoBytes: SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7'),
personFields: 'photos',
},
sources: [
'READ_SOURCE_TYPE_PROFILE',
],
});
Ok, so as #Sudhakar mention there is no such API.
But I've found that admin directory API actually changes the profile in most places.
The trick in that the user needs manually once remove the avatar from https://aboutme.google.com/ Profile picture because this photo has top priority against admin directories one.
If the user removes the photo from aboutme.google.com then changes it via admin directory v1 API this photo is seen in the calendar, Gmail, contacts...
So check my first code sample:
// admin directory way
const { google } = require('googleapis');
const SafeBase64 = require('url-safe-base64');
const GOOGLE = require(process.env.GOOGLEAPIS_VAULT);
const encodedPhoto = SafeBase64.encode('R0lGODdhBAAEAIABAAMA/////ywAAAAABAAEAAACBkRiYJgHBQA7');
const targerUser = 'arsenyp#***.com';
const JWTClient = new google.auth.JWT(
GOOGLE.client_email,
null,
GOOGLE.private_key,
[
'https://www.googleapis.com/auth/admin.directory.user',
],
'gsync#***.com', // sub - this is service user with proper permissions, not a target user
);
const directory = google.admin({ version: 'directory_v1', auth: JWTClient });
directory.users.photos.update({}, targerUser);
const res = await directory.users.photos.update({
userKey: targerUser,
resource: {
photoData: encodedPhoto,
},
});

PUT doesn't work on POSTMAN but okay when called from front-end

I have the following lambda function that works when called from the front-end React app:
// Update a contact
module.exports.updateContact = async (event, _context) => {
const id = event.pathParameters.id;
const body = JSON.parse(event.body);
const paramName = body.paramName;
const paramValue = body.paramValue;
const params = {
Key: {
id: id
},
TableName: contactsTable,
ConditionExpression: 'attribute_exists(id)',
UpdateExpression: 'set ' + paramName + ' = :v',
ExpressionAttributeValues: {
':v': paramValue,
},
ReturnValues: 'ALL_NEW'
};
try {
const res = await db.update(params).promise();
}
catch (err){
console.log(err);
return err;
}
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify({
status: 'updated!',
[paramName]: paramValue,
}),
};
return response;
};
But it fails with status code 502 & 'InternalServerErrorException' message when called from Postman.
I tried to solve it with one of the suggestions found on stackoverflow with method override:
It doesn't work and I'm now getting status code 403 and 'MissingAuthenticationTokenException'.
I'm not sure what I'm doing wrong, seeking some guidance. Thank you.
It seems the API endpoint has an Authorizer configured on the API Gateway and the front end is sending the correct credentials, while the Postman configuration is not.
I would suggest to use the browser dev tools selecting the PUT request and copy it as cUrl:
then you can paste the copied cUrl command into the Postman request. It will prefill all the parameters: method, body and the headers.
hope this help

Resources