3

I set up a service account with domain-wide delegation and I passed the client email, private key, scopes, and a user email the JWT method to impersonate a G-Suite user. I then get specific user info from the Admin API and use it to create an email signature and push it to the Gmail API. It works great if the user email I pass to the JWT method is a super admin but if I try to pass any other user I get an error response, "Not Authorized to access this resource/api". Any ideas on how I can get it to work with a regular user account within my domain?

Here is the code. (genSignature.js)

const { google } = require('googleapis');
const privatekey = require('../private-key.json');

const scopes = [
    'https://www.googleapis.com/auth/gmail.settings.basic',
    'https://www.googleapis.com/auth/gmail.settings.sharing', 
    'https://www.googleapis.com/auth/admin.directory.user', 
    'https://www.googleapis.com/auth/admin.directory.user.readonly'
];

const auth = async (user) => {
    try {
        const jwtClient = new google.auth.JWT(
            privatekey.client_email,
            null, 
            privatekey.private_key,
            scopes,
            user // User who will be impersonated using the JWT client.
        );

        await jwtClient.authorize();
        return jwtClient;

    } catch (err) {

        console.log(err.message);

    };
};

function genSig(e) {

    auth(e).then((jwtClient) => {

        // Authenticate with the gmail API.
        const gmail = google.gmail({
            version: 'v1', 
            auth: jwtClient
        }); 

        // Authenticate with the admin API.
        const dir = google.admin({
            version: 'directory_v1', 
            auth: jwtClient
        });


        // Get users contact and job data from the directory. This data will be used as variables in their email signature. 
        dir.users.get({ userKey: e }, (err, response) => {
            if (err) {
                console.log(err.message);
            } else {
                let phones = response.data.phones;
                let workPhone = '';

                if (phones) {
                    for (i = 0; i < phones.length; i++) {
                        if (phones[i].type == 'work') {
                            workPhone = phones[i].value;
                        };
                    };
                };

                function getUserData() {
                    let userData = {
                        name: response.data.name.fullName, 
                        email: response.data.primaryEmail, 
                        phone: workPhone, 
                        avatar: response.data.thumbnailPhotoUrl, 
                        department: response.data.organizations[0].department, 
                        title: response.data.organizations[0].title
                    }; 
                    return userData;
                };

                let requestBody = {
                    signature: 'Test' 
                };

                // Update the users email signature for their primary email.
                gmail.users.settings.sendAs.update({ userId: e, sendAsEmail: e, requestBody }, (err, response) => {
                    if (err) {
                        console.log(err.message);
                    } else {
                        console.log(response.data);
                    };
                });
            };
        });
    });
}

module.exports = genSig;

(signatures.js)

const express = require('express');
const router = express.Router();
const genSig = require('../../functions/genSignature');

// Get webhooks from Google.
router.post('/', (req, res) => {

    let email = req.body.email;
    let emailStr = email.toString();
    console.log(emailStr);
    genSig(emailStr);


    res.status(200).json({
        "msg": "data recieved..."
    });
});

module.exports = router;

(index.js)

const express = require('express');
const app = express();

app.use(express.json());
app.use('/email-signature', require('./routes/api/signatures'));

const PORT = process.env.PORT || 6000;

app.listen(PORT, () => console.log(`Server is running on port ${PORT}`));

Here are some screenshots.

API configuration on G-Suite

Service Account Setup

Successful request vs unsuccessful request

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449

2 Answers2

3

You need to impersonate an admin:

Only accounts with User Management privileges (like a Super Admin, or a User Management Admin) can access Users: get. You have to take into account that this is part of Admin SDK, which is to be used by admin accounts.

You can also check this is not possible if you try calling this via Try this API on the reference docs (you'll get the same message: Not Authorized to access this resource/api).

It doesn't matter that you're using a Service Account with domain-wide authority: when the service account is impersonating another user, it can only access the resources this user can access.

Solution:

In this case, the impersonated account should have user management privileges if you want to retrieve user data from Admin SDK.

But since these privileges are not necessary for calling the Gmail API method, you could impersonate an admin account when calling Users: get, and a regular one when calling users.settings.sendAs.update.

Reference:

Iamblichus
  • 18,540
  • 2
  • 11
  • 27
  • That makes a lot of sense, thank you for clarifying for me. So technically the service account email is considered an admin account correct? Maybe I can use that account to retrieve specific information from the Admin SDK about another user and then update their email signature. The idea is to have a cloud function that listens for events from the Admin SDK and updates email signatures to reflect those changes. – Raine Petersen Nov 03 '20 at 16:21
  • 1
    @RainePetersen No, the service account is technically not even part of the domain, and it cannot access this data on its own; it needs to impersonate an account that has access. You should impersonate an regular account with admin privileges if you want to use Admin SDK. – Iamblichus Nov 04 '20 at 09:53
  • 1
    @RainePetersen Also, please consider accepting this answer, if it solved your question. – Iamblichus Nov 04 '20 at 09:54
  • Creating a custom admin role with read access to users in Admin API works for users.list(). Also note that giving an admin role to a user might take a fiew minutes to apply. – Jerther Sep 16 '22 at 16:11
2

this is not a new post. However, I faced it and found a solution.

You can use a service account by assigning a role. See "Assign a role to a service account" in Assign specific admin role. There are details in updates blog post.

At first, you need to create a custom admin role at Google Workspace Admin Console. And you can assign service accounts to the custom admin role with email address.

It worked on Google Cloud Functions in my environment.

nayuta
  • 41
  • 4