IAM permission denied while trying to detect intent on dialogflow CX - node.js

I created the service account and i provided to my env following this guide
https://cloud.google.com/dialogflow/cx/docs/quick/setup#windows
I tried to run my code using firebase serve, but i got the following error:
Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/botDialogflowCX/locations/us-central1/agents/chat' denied
I'm sure that the service account is correct. I already tried to create a dialogflow admin account, client and project owner account.
Here is my code
const functions = require("firebase-functions");
const { SessionsClient } = require("#google-cloud/dialogflow-cx");
const crededentials = require("../../.env/botdialogflowcx-5e936a89c163.json");
exports.teste = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true });
const client = new SessionsClient({
apiEndpoint: "us-central1-dialogflow.googleapis.com",
});
const sessionId = Math.random().toString(36).substring(7);
const sessionPath = client.projectLocationAgentSessionPath(
"botDialogflowCX",
"us-central1",
"chat",
sessionId);
console.info(sessionPath);
const requestDialogflow = {
session: sessionPath,
queryInput: {
text: {
text: "Oi",
},
languageCode: "pt-br",
},
};
client.detectIntent(requestDialogflow).then((snapshot) => {
const webhookResponse = {
fulfillment_response: {
messages: [{
text: {
text: ["testandoooo", snapshot],
},
},
],
},
};
response.send(webhookResponse);
}).catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
I really don't know what is going on.
Running the command
gcloud projects get-iam-policy botdialogflowcx --flatten="bindings[].members" --format="table(bindings.role)" --filter="bindings.members:teste-889#botdialogflowcx.iam.gserviceaccount.com"
The output was roles/dialogflow.admin.
I add the email to the service account in the dialogflow CX - agent - share.
email in the dialogflow CX - agent - share
email in the account service
But still having the same error, that the IAM does not have permission.

The IAM Permission denied error usually occurs because the service account you are using has not been granted sufficient permission to perform the requested action on the GCP Project connected to the Dialogflow Agent, you have used the incorrect credentials in your request, or you have queried the incorrect agent.
Looking at the following code and error encountered, it seems that the Project Name and Agent Name were used instead of the Project ID and Agent ID value respectively.
const sessionPath = client.projectLocationAgentSessionPath(
"botDialogflowCX", // update to Project ID
"us-central1",
"Chat", // update to Agent ID
sessionId);
Please note that Project ID and Agent ID are different from the Project Name and Agent Name, you can refer to the following documentation on how to collect IDs.

I got it. I just had to change the
client.projectLocationAgentSessionPath(
"botDialogflowCX",
"us-central1",
"chat",
sessionId);
to
const sessionPath = client.projectLocationAgentSessionPath(
"botdialogflowcx",
"us-central1",
"e55b9ef5-d1f2-4e5c-9e95-974501233d50",
sessionId);
and it worked.

if you're running this code in cloud functions i don't believe you need to provide the credentials. If you're running this locally you would set your credentials like this :
$env:GOOGLE_APPLICATION_CREDENTIALS="KEY_PATH"
That way you wouldn't need to provide the credentials in your code.

Related

Google Cloud function accessing a secret from Secrets Manger

I'm having issues getting a cloud function to access a secret from the secrets manager. Basically I want to have my front-end access secrets by sending a request to the backend and then the backend getting the secret from secrets manager.
My cloud function endpoint looks like this:
Endpoint.get("/get-key", authMiddleware, async (req: any, res: Response) => {
try {
const auth = new GoogleAuth({
scopes: ['https://www.googleapis.com/auth/cloud-platform']
});
const project = req.params.projectId;
const secret = req.params.secret;
const name = `projects/${project}/secrets/${secret}/versions/latest`;
const credentials = await auth.getCredentials();
const ver = new SecretManagerServiceClient({credentials,projectId:project});
const request = {
name,
};
const response = await ver.accessSecretVersion(request);
const payload = response.payload.data.toString();
console.log(`Payload: ${payload}`);
return res.status(200).send({ payload });
} catch (error:any) {
console.log("get key error: ", error);
return res.status(500).send(error.message);
}
});
When I try to access that endpoint I get this error:
PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
I tried explicitly setting the projectId there and it still gives me that error. Not sure what else I can change. perhaps the "latest" is not a valid endpoint for the secrets manager.
PERMISSION_DENIED: Permission denied: Consumer 'project:undefined' has been suspended
The above error occurs when you exceed usage quota as discussed in this github thread or due to ToS violations. You may need to submit the appeal. You can contact Google support for the same.
Also make sure you have given secretAccessor role to your service account.

PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' Node js

I have created a webhook for WhatsApp Chatbot using NodeJS following this online article: https://dev.to/newtonmunene_yg/creating-a-whatsapp-chatbot-using-node-js-dialogflow-and-twilio-31km
The webhook is linked to Twilio Sandbox for WhatsApp.
I have also provided the DialogFlow Admin API permission to service account on Google Cloud Platform.
When I send a new message from WhatsApp, it's received on Twilio and the webhook is triggered, but I am getting the following error on the console on my local machine.
"Error: 7 PERMISSION_DENIED: IAM permission 'dialogflow.sessions.detectIntent' on 'projects/xxxx-xxx-xxxx/agent' denied."
I am using Ngrok to tunnel the localhost build to the web and using that URL as the webhook URL in Twilio.
We have a client demo for this feature, so any quick help is appreciated. I am placing my dialog flow code and controller code below
dialogflow.ts
const dialogflow = require("dialogflow");
const credentials = require("../../credential-new.json");
const sessionClient = new dialogflow.SessionsClient({
credentials: credentials
});
const projectId: string = process.env.DIALOGFLOW_PROJECT_ID!;
export const runQuery = (query: string, number: string) => {
return new Promise(async (resolve, reject) => {
try {
// A unique identifier for the given session
//const sessionId = uuid.v4();
const sessionId = number;
// Create a new session
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: query,
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
const result = responses[0].queryResult;
resolve(result);
} catch (error) {
reject(error);
}
});
};
This issue got resolved by creating a new account on DialogFlow and providing new API key.
I think the problem is with the service account. Make sure you use the same email which is registered with Dialogflow and GCP and then create a service account and also make sure the service account email is present in the credential-new.json file is the same service account which has the Dialog Flow Admin Role and also check you have given the valid path to access credential-new.json file in the code.
You can safely do this by going to the settings menu on Dialogflow and then clicking on the project id, it will take you to the correct place.
Also, there may be a possibility that you forget to enable the Dialogflow API from the API section on GCP.

Not Authorized To Access This Resource/API (GCP)

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
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:
Admin privileges definitions
Pre-built administrator roles
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.

Google Directory API: Unable to access User/Group endpoints using Service Account (403)

I am trying to verify members of groups using the Google Directory API and cannot get past a 403 error every time I make the request.
I am using a service account, which I have enabled the "Enable G Suite Domain-wide Delegation" option for. I have also added the "https://www.googleapis.com/auth/admin.directory.user, https://www.googleapis.com/auth/admin.directory.group" Scopes using the Client ID within Suite under, "Manage API Client Access"
Code wise, I am using Node for this, and the google supplied googleapis package from NPM.
The external JSON file is the JSON credentials file downloaded when I created the service user.
Here's the code of me trying to get the request.
import { google } from 'googleapis';
async function getGroupUsers(){
const auth = await google.auth.getClient({
keyFile: './src/jwt.keys.json',
scopes: [
'https://www.googleapis.com/auth/admin.directory.group',
'https://www.googleapis.com/auth/admin.directory.group.member',
],
});
const admin = google.admin({
version: 'directory_v1',
auth,
});
const res = await admin.groups.get({
groupKey: 'redacted#domain.redacted',
});
console.log(res)
}
I can't see any obvious reason this isn't working, as I can't see how the user doesn't have permission to the resource?
Obviously missing something obvious here, as the google documentation for this is all over the shop sadly.
Help greatly appreciated!
Thanks
Gareth
Ok after much banging of head and googling I finally for there with this, final working code is as follows, not the inclusion of the client.subject value, which has to be an administrator for the domain in question.
async function validateToken(idToken) {
const keys = JSON.parse(GOOGLE_CREDS);
const client = auth.fromJSON(keys);
client.scopes = [
'https://www.googleapis.com/auth/admin.directory.user',
'https://www.googleapis.com/auth/admin.directory.group',
];
client.subject = 'admin#gsuite.domain';
const admin = google.admin({
version: 'directory_v1',
// auth,
auth: client,
});
const res = await admin.groups.list({
domain: 'redacted',
userKey: email,
});
const { groups } = res.data;
let role = '';
// Check for user role
if (containsGroup(USER_GROUP, groups)) {
role = USER_GROUP;
}
// Check for admin role
if (containsGroup(ADMIN_GROUP, groups)) {
role = ADMIN_GROUP;
}
// Not an admin or user so return unathenticated
if (role === '') {
return authResponse();
}
return successResponse({
'X-Hasura-User-Id': userid,
'X-Hasura-Email': email,
'X-Hasura-Role': role,
'X-Hasura-Groups': groups.map(group => group.id),
'Cache-Control': 'max-age=600',
});
}

Error: User is not authorized while creating GCP project using service account

I am trying to create GCP project programmatically using Google API. Here is the sample code:
const {JWT} = require('google-auth-library')
async function main (keyFile = {PATH_TO_CREDENTIAL_FILE}) {
const keys = require(keyFile)
const client = new JWT({
email: keys.client_email,
key: keys.private_key,
scopes: ['https://www.googleapis.com/auth/cloud-platform']
})
const url = 'https://cloudresourcemanager.googleapis.com/v1beta1/projects/'
const data = {
projectId: 'my-first-project',
name: 'My First Project',
parent: {
type: 'organization',
id: {ORGANIZATION_ID}
}
}
const res = await client.request({
url,
method: 'POST',
data: JSON.stringify(data)
})
console.log('project Info:')
console.log(res.data)
const tokenInfo = await client.getTokenInfo(client.credentials.access_token)
console.log('tokenInfo', tokenInfo)
}
const args = process.argv.slice(2)
main(...args).catch(console.error)
After running this code I am getting the following error:
UnhandledPromiseRejectionWarning: Error: User is not authorized.
Can anyone help me why I am getting this error? And how can I fix this?
P.S.
Google Resource Manager API is enabled.
Service account has the role=owner permission.
You are creating a project within an organization. Does the user have the Project Creator role in the organization?
When the organization is created, all users in your domain are automatically granted Project Creator and Billing Account Creator IAM roles at the organization level. This enables users in your domain to continue creating projects with no disruption.
The Organization Administrator will decide when they want to start actively using the organization. They can then change the default permissions and enforce more restrictive policies as needed
Also if you are authenticating using a service account (SA) then the SA needs to have the role

Resources