Requesting multiple permissions using dialoglow - dialogflow-es

I've been developing actions using the Dialogflow API. I get how to ask for a single permission (like SIGN_IN, PERMISSION, etc.) at a time, but I wanted to know how to ask multiple permissions at the same time? Does one try them sequentially (i.e. ask for one and once it's accepted or rejected, try the next), or is there a way through which multiple permissions can be asked concurrently (maybe like askPermissions('SIGN_IN', 'PERMISSION'))?
The use case is that the action requires multiple permissions before it can be successfully executed.
Thanks!

Permissions is an array, so it can take more than 1 string. Like this:
const options = {
context: 'Whatever text you add here will explain to the user why you're asking to use their location.',
permissions: ['DEVICE_PRECISE_LOCATION','NAME'],
};
conv.ask(new Permission(options));

Related

Is it ok to store user's secrets in PropertiesService.getUserProperties()?

I am developing a Google Workspace Addon (standalone script) which will make REST API calls to external service and for that purpose it needs to provide an API key.
I request the API key input from a user and then store it in PropertiesService in the following way:
function onSheets(e) {
const userProperties = PropertiesService.getUserProperties();
const saved_api_key = userProperties.getProperty('api_key');
const api_key: string = saved_api_key ? saved_api_key : "";
const builder = CardService.newCardBuilder();
const apiKeyInput = CardService.newTextInput().setTitle('API Key')
.setFieldName('api_key')
.setHint('Enter your API Key')
.setValue(api_key);
const saveApiKey = CardService.newAction().setFunctionName('saveApiKeyFn');
const button = CardService.newTextButton().setText('Save').setOnClickAction(saveApiKey);
const optionsSection = CardService.newCardSection()
.addWidget(apiKeyInput)
.addWidget(button)
builder.addSection(optionsSection);
return builder.build();
}
function saveApiKeyFn(e) {
const api_key = e.formInput.api_key;
const userProperties = PropertiesService.getUserProperties();
userProperties.setProperty('api_key', api_key);
return CardService.newActionResponseBuilder()
.setNotification(CardService.newNotification()
.setText("API Key saved"))
.build();
}
Since userProperties are scoped to a current user it seems fine. But I have serveral problems with this solution:
Is this really safe? I mean it is stored in plain text so maybe there are ways to retrive it by other mailcious user?
The idea that by mistake I would use getScriptProperties() and thus leak one user's API key to all other users gives me nightmares. It is highly sensitive API key. It would cost a user tons of money if abused.
I read that some user's suggest https://cloud.google.com/secret-manager but I am not sure it's fit for this particular scenario. It would require one more external API call. It is not free. And lastly from what I underestand I would be sort of an owner of all of these secrets since I will be the owner of the Google Cloud project in which this API runs.
All I want is for the users to be able to store their keys safely, so that no one else including me can never access them.
What would you suggest? Thanks!
Is this really safe? I mean it is stored in plain text so maybe there are ways to retrive it by other mailcious user?
Security is relative. There's no such thing as absolute secrecy. Here are some attack scenarios:
Google employees or support may have unrestricted access
If a particular user installed a trigger, that trigger runs as that user and other users, if they can trigger the script and have edit access to the script, will be able to access the keys. A common scenario would be a installed edit trigger in a sheet. User B can access user A, if he can make a edit as well as edit the script. As mentioned in the comments by doubleunary, this is less of a problem in a published add on, as the source code is not accessible or editable.
Encrypting keys is a possibility. But, where would you store the decrypting key? You could ask every user to have a custom password for decrypting the key. But how many times are you going to make a API call? Would they have to enter the key every time? At what point does convenience overtake the need for secrecy?
The idea that by mistake I would use getScriptProperties() and thus leak one user's API key to all other users gives me nightmares. It is highly sensitive API key. It would cost a user tons of money if abused.
That is a possibility, but one that's easily avoidable by careful code review by yourself and your peers.
Those are the scenarios I could think of.
Related:
Securely Storing API Secrets used in Google Apps Script - Published Library

Is there an anonymous ID in Actions on Google with Dialogflow?

Is there an anonymous ID in Actions on Google with Dialogflow that I can access using DialogFlow in Node.js?
I don't need to know the Google account of who is using the Action, but I do want to have a unique identifier so that the Action can know when they come back.
Google no longer provides one for you. You will have to generate one when a new user interacts with your webhook and store the generated id in their user storage object.
To identify a new user your just check if they already have an id in their user storage object. For generating the id you can use an library like uuid. https://www.npmjs.com/package/uuid
Uuidv4 is probably the one that you need if you just need a unique id for simple identifications
The original idea from Google was to leverage a field called userStorage, but this feature seems to be borked ATM.
userStorage Documentation:
https://developers.google.com/actions/assistant/save-data
Reddit thread regarding issues:
https://www.reddit.com/r/GoogleAssistantDev/comments/d88z7e/userstorage_saga_continued/
Unless something has changed (I haven't checked on userStorage since I've been busy writing a fix around it) you may be out of luck without Account Linking. Feel free to try userStorage and keep me honest as they may have remedied the situation internally.
Alternatively, if all you need is an identifier or session for a single conversation you can leverage the conversationId which will be unique until the conversation ends.
I've found a possible option...
(When working in DialogFlow in Node.js, most code is in a handler and the parameter is usually called conv. The following assumes that it is inside such a handler.)
On every single call, check for an 'existing' id in the session data and the user storage:
var id = conv.data.MyId || conv.user.storage.MyId || '';
if(!id) {
id = /* make a new Id for this user... a GUID or some other unique id */
conv.user.storage.MyId = id;
}
Once I get the Id from storage or make a new one, it is critical to reassign it to conv.data, since conv.user.storage seems to be reliably provided only on the first call!
// IMPORTANT
conv.data.MyId = id;
/* use the Id as needed */
My code looks up the Id in a firebase database to get details from their last visit.
This seems to be working, but may not be reliable.

How to pass to an intent if Permission request is denied

I have an Action that integrates with Dialogflow which as part of the conversation requests access to the user's location.
This is fulfilled via a webhook:
app.intent('actions_intent_PERMISSION', async (conv, params, permissionGranted) => {
if (!permissionGranted) {
app.intent('actions_intent_PERMISSION - no', (conv, params) => {
conv.ask('sad face, you said no to my permission request!');
});
// conv.ask(`Ok, no worries. I'll have to figure out how to get your postcode. follow-up intent I suppose`);
} else {
conv.data.postcode = conv.device.location.zipCode;
conv.ask(`Ok great - please give me a minute, I have to get data from a few different places.`);
//use postcode to make some other API calls
}
});
Everything is fine when the user gives permission but when they don't give permission I would like to pass off to an intent that asks for their location manually ('what is your postcode/zipcode?').
As per the screenshot I tried creating a followup intent to actions_intent_PERMISSION called actions_intent_PERMISSION - no but this causes the app to crash.
What is the best way to pass the conversation to another intent is the value of permissionGranted is false?
Your question doesn't quite make sense the way you've asked it. Intents represent what the user has said and not what you do with it - that is what your webhook does in an Intent Handler. There is nothing (technically) stopping you from replying to the user asking for their zip code by just replying differently.
If you want users to be able to trigger some Intents if they have given permissions, and other Intents if they have not, you can set a different Context for each. Then you would set some Intents to only be triggered if the "permitted" context was set, and others only if the "notpermitted" context was set.
However, you have a non-technical problem to consider. If they aren't giving you permission to get their location, why would they tell you their location? It also is likely that the reviewers would reject it, saying that you should be getting location information through the provided API.

setCanDeleteDocuments() Lotus Notes ACL

I am trying to delete document,where conditions is...
Where the user is in Group and having the deletion rights & every users in group is able to delete the document.
But problem is when, If the name I specify is also listed explicitly in the ACL and does not have deletion rights.Then it does not check the group rights which is fair enough.
For that i am trying to give deletion rights to those users who are in group by code given below.
var acl:NotesACL=database.getACL();
var entry:NotesACLEntry=acl.getFirstEntry();
if(entry!=null)
{
var user:NotesACLEntry=acl.getEntry(#UserName());
if(user.isCanDeleteDocuments()==false)
{
user.setCanDeleteDocuments(true);
acl.save();
}
}
Where it shows error like,
Exception occurred calling method NotesACL.save() null.
Even explicitly added user is having user type=person & Access= Manager in ACL.
is there any other way to do this?
Any help would be appreciated.
Thanks in advance.
Using database as a starting point means you're getting the database as the user. Unless the user already has Manager access to the database, this will fail because the user doesn't have access to update the ACL.
You can use sessionAsSigner, but bear in mind you cannot use the getCurrentDatabase() method. Instead you must use the getDatabase(server,filePath) method in order to get the database with the signer authority. Obviously the signer also needs rights to modify the ACL.

Backbone.js: Admin Interface

I am developing a CRUD based database which needs to have some ACL (Access Control Lists) based on users. We want to make it so that there's an Admin access panel as well so we can quickly delete content if needed or otherwise moderate it. I am trying to keep it flexible so that it could be a separate interface for convenience but also not diverge from the general user interface because otherwise it will be harder to maintain.
What would be a good way to handle creating a user interface in the front end, assuming the backend will deal with the ACLs? Is it necessary to create a separate BB.js interface to serve or is it fine to pass a bit of extra code to all users that will be ignored?
Recommendations or warnings would be appreciated too!
I interpreted your question as you want to serve up different interfaces for different access levels of users. Therefore I would:
Put a check for the users access before they hit each route to make sure they aren't accessing a page they shouldn't see
They can still try to circumvent this (since they can change their user model), your backend would still catch any unauthorized requests.
You can also conditionally show and hide page elements based on user access levels.
Heres an example code for my ACL.coffee
acl = {}
acl['admin'] = [
'page1',
'page2',
'page3',
'page4',
'page5'
]
acl['user'] = [
'page1',
'page2',
'page3'
]
hasAccess = (route) ->
# Get User Model
user = window.App.user
# Get Associated ACL
permissions = acl[user.get('role')]
# Check each URL for Access Privileges
# Returns false if route not in array
permissions.some (r) -> ~route.indexOf r
{hasAccess}

Resources