Related
I have the following dynamodb table and want to remove "oldInfo" from the attribute "accounts" using update_expression = f"REMOVE accounts.oldInfo" however that does not work. Does anyone have any ideas or suggestions for this? Note that the "accounts" attribute is a map and so is "oldInfo".
{
"accounts": {
"oldInfo": {
"oldStuff": []
}
"otherInfo": {
"otherStuff": []
}
}
}
This is the code that I am using to remove "oldInfo"
self.clean_up_dynamo_db.updateitem(table_name=self.table_name,
key={
"partitionKey": item["partitionKey"],
"sortKey": item["sortKey"]
},
update_expression=f"REMOVE {self.attribute}")
I didn't find a clear example in the DDB docs, so I replicated your situation in my AWS account.
I created a map attribute named accounts and gave it keys of oldInfo and otherInfo. I executed the following operation, which removed accounts.oldInfo as expected.
const params = {
TableName: "YOUR_TABLE_NAME",
Key: {
PK: "PARTITION_KEY",
SK: "SORT_KEY"
},
UpdateExpression: "REMOVE accounts.otherInfo"
}
await ddbClient.updateItem(params);
Although this snippet is in JavaScript, it confirms that you are going about the operation correctly according to the DDB API.
Perhaps there is something else going on in your code that is preventing the operation from succeeding? Are you certain the variables you are sending update_item are as you expect them?
When I authenticate using WebAuthn and my YubiKey, the response.userHandle property is always null. That is the user id and displayName that I registered the credential with does not get returned. Is this becuase of something I am doing wrong during the registration / authentication process:
async function register() {
const publicKeyCredentialCreationOptions = {
challenge: Uint8Array.from("this-is-a-test", (c) => c.charCodeAt(0)),
rp: {
name: "Webauthn Test",
id: "localhost",
},
user: {
id: Uint8Array.from("a1b2c3d4e5f6", (c) => c.charCodeAt(0)),
name: "just-a-test",
displayName: "MrUser",
},
pubKeyCredParams: [{ alg: -7, type: "public-key" }],
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
},
timeout: 60000,
attestation: "direct",
};
const credential = await navigator.credentials.create({
publicKey: publicKeyCredentialCreationOptions,
});
}
This is the code I use to authenticate:
async function authenticate() {
const publicKeyCredentialRequestOptions = {
challenge: Uint8Array.from("test", (c) => c.charCodeAt(0)),
allowCredentials: [
{
id: credentialId,
type: "public-key",
transports: ["usb", "ble", "nfc"],
},
],
timeout: 60000,
};
const assertion = await navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
});
console.log(assertion);
}
What I end up with is:
{
rawId: ArrayBuffer(64),
id: "U-nitqhlORmmdltp7TLO3i18KNoWsSebFyrtc3OIRvcktvwlz-dJZCA1_1gxXrNHzqReU7xGAHdfVP75N2aJSw",
response: {
authenticatorData: ArrayBuffer(37) {}
clientDataJSON: ArrayBuffer(101) {}
signature: ArrayBuffer(71) {}
userHandle: null
}
type: "public-key"
}
As you can see: userHandle is null. Can anyone tell me why?
The userHandle can be null depending on which type of WebAuthn credential the relying party requested to be created.
The default WebAuthn behavior will create a non-discoverable credential and the userHandle returned in the assertion will be null. No data is stored on the authenticator for this type of credential so there is nothing to return.
To create a WebAuthn client-side discoverable credential, a.k.a. resident key, you must set the requireResidentKey member to true. This will store credential data on the authenticator and will return the userHandle in the assertion. Refer to the AuthenticatorSelectionCriteria in the W3C WebAuthn spec for the details.
Here is an example:
authenticatorSelection: {
authenticatorAttachment: "cross-platform",
requireResidentKey: true
},
See Yubico's WebAuthn Dev Guide to learn more about resident keys and the userHandle.
I have tried to understand what you are dealing with. I played with https://u2f.bin.coffee/ to get a feeling for the data flow. As a result of authentication I have received a response like:
Got response:
{
"keyHandle": "F74UNCdNv1d43zw7hqxYgkjR3O6dcevopiSb3jrcB3rMFRUM486LbsVExJD0R3ESC5MCb3zeFGdxvS3ksZ7sCA",
"clientData": "eyJ0eXAiOiJuYXZpZ2F0b3IuaWQuZ2V0QXNzZXJ0aW9uIiwiY2hhbGxlbmdlIjoiTXpPTjhXRHpvSDlhZHU0bTk5YWF0ZyIsIm9yaWdpbiI6Imh0dHBzOi8vdTJmLmJpbi5jb2ZmZWUiLCJjcm9zc09yaWdpbiI6ZmFsc2UsImV4dHJhX2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ",
"signatureData": "AQAAAAUwRAIgEqi5POKKUraU97W3vbfn34DSWqXwiZwEi5g9QPPtS6MCIBbLYW1_b3aRjHQivSRZQUAfBobx6CZnQ0_VVvuu1LJJ"
}
Now I assume the keyHandle here is your authenticatorData, the clientData here is your clientDataJSON and that signatureData is your signature. Whatever this userHandle is you are missing, it does not seem to be required.
Look at this picture too:
If the userHandle were the handle, the authentication would not work with a null value. But it does if I understand your example correctly.
So I believe you are dealing with a field that is reserved for future purposes or other flows than that which you need at the moment.
I have created a set using dynamoDB document client . I am able to remove items in this set however when i remove to the last element in the set nothing returns until i make a new post. Then all the other data is displayed.
const params = {
TableName: 'beta-user-' + process.env.NODE_ENV,
Key: {
username: request.username
},
UpdateExpression: "DELETE #features :feature",
ExpressionAttributeNames: { "#features" : "features" },
ExpressionAttributeValues: { ":feature": dynamodb.createSet([request.feature]) },
ReturnValues: "NONE"
};
and im calling it like
const dynamoPromise = dynamodb.update(params).promise();
return await dynamoPromise.then(result => { // stuff })
The UpdateExpression i do not think is wrong
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html#Expressions.UpdateExpressions.DELETE
I belive the problem is with ExpressionAttributeValues if i remove
dynamodb.createSet I get many validation errors.
When i make a get request to app i get
{
"message": [
{
"username": "x",
"feature": [
"blah",
"test"
]
},
{
"username": "z",
"feature": [
"blah"
]
},
}
I make a delete request and remove the feature test from username x. This works and returns the same response minus the test feature. I make another delete request to remove blah. Blah is removed however when I make a get request I recieve:
{
"message": {}
}
The other data is returned when i make a new post to that specific user.
EDIT:
I think the issue might be due to dynamo not liking an empty set
The issue was with my return statement in my get request. I assumed that once features were deleted the record would be deleted. I was trying to return features on an object that had no features, therefore, it was erroring out and not returning anything.
I want to add a member to a distribution list. As apparently I can't do this using Microsoft Graph, I am trying to use Azure AD Graph API. I am using Node.js.
I am able to connect to Azure using the adal-node library. I get a token back, send requests, and get responses. (I can list groups, users, etc.).
I am following the Add Members documentation, but I am confused.
In the URL, is object_id the id of the Group to witch I want to add the member?
For myorganization, I am using the tennant_id.
Where do I specify the user data? Should I pass that in the POST? If so, whats' the format?
What is the $links in the URL?
Currently, I am doing this:
request.post(
"https://graph.windows.net/TENNANT_ID_HERE/groups/GROUP_ID_HERE/$links/members?api-version=1.6",
{
headers: {
Authorization: "Bearer " + TOKEN_HERE,
"Content-Type": "application/json"
},
form: { key: "value" } //should I put my user object here?
},
function(err, res, body) {
if (err) {
console.log("err: " + err);
} else {
console.log("res: " + JSON.stringify(res, null, 3));
}
}
);
I get the following error:
{
"odata.error": {
"code": "Request_BadRequest",
"message": {
"lang": "en",
"value": "A supported MIME type could not be found that matches the
content type of the response. None of the supported type(s) 'application/xml, text/xml,
application/json;odata=minimalmetadata;streaming=true, application/json;odata=minimalmetadata;
streaming=false, application/json;odata=minimalmetadata,
application/json;odata=fullmetadata;streaming=true,
application/json;odata=fullmetadata;streaming=false,
application/json;odata=fullmetadata,
application/json;odata=nometadata;streaming=true,
application/json;odata=nometadata;streaming=false,
application/json;odata=nometadata,
application/json;streaming=true,
application/json;streaming=false,
application/json;odata=verbose,
application/json'
matches the content type 'application/x-www-form-urlencoded'."
}
}
}
The short/most important answer is that neither Microsoft Graph or Azure AD Graph API supports distribution lists. From the documentation:
Important: You can only add members to security groups and mail-enabled security groups.
That said, this isn't technically why your call is failing here. Your code is reaching the point where it's failing due to the type of group you're working with. And while it won't help you with managing a distribution list, the following is what is actually happening.
The form: { key: "value" } option is telling request to send the payload as a URL Encoded Form (application/x-www-form-urlencoded). The API requires the payload be sent as JSON (application/json).
To send over JSON, you need to do two things:
Set the json option to true
Set the body value rather than the form value.
The proper code would look something like this:
request.post(
"https://graph.windows.net/{tenant-id}/groups/{group-id}/$links/members?api-version=1.6",
{
headers: {
Authorization: "Bearer " + TOKEN_HERE
},
json: true,
body: JSON.stringify({ url: "https://graph.windows.net/{tenant-id}/directoryObjects/{user-id}" })
},
function(err, res, body) {
if (err) {
console.log("err: " + err);
} else {
console.log("res: " + JSON.stringify(res, null, 3));
}
}
);
The $links parameter in the URI is telling the API that you're providing a link to another resource (in this case, a user record).
we could add member to a group with AD graph API.
post https://graph.windows.net/{tenantId}/groups/{groupobjectid}/$links/members?api-version=1.6
body
{
"url": "https://graph.windows.net/{tenantId}/directoryObjects/{userObjectId}"
}
Test it with Postman
Note: I'm using the 'classic' experience because the new interface doesn't have a way for a template to set up required fields for future signers.
The workflow:
have a template with a bunch of fields
using the api:
create an envelope/document from the template, and assign a new user to sign (this document is going to be an agreement for signing up for a service)
create new role
set roleName to fake signer on template (because I can't configure fields without at least one signer on the template)
add textTabs to try to perfill some of the fields.
retrieve the recipients
create a recipient view so that I get the URL to put in an iframe
This is kind of obnoxious, because I don't care about having a first signer that isn't the user signing up for the service. I would however, like to have the the document be copied to someone after signing, but docusign doesn't appear to support this (that I've found anyway).
Here is the node.js code for the creation of the envelope (where I think my API usage is going wrong):
function createEnvelopeDefinition(templateId, userData) {
var envDef = new docusign.EnvelopeDefinition();
envDef.setEmailSubject('Signup Agreement');
envDef.setTemplateId(templateId);
var tRole = new docusign.TemplateRole();
tRole.setRoleName('RoleOne');
tRole.setName(userData.fullName);
tRole.setEmail(userData.email);
tRole.setClientUserId('2');
tRole.setTabs(new docusign.Tabs());
tRole.getTabs().setTextTabs([]);
const fieldsToPreFill = [
'field1',
'field2',
'field3',
'field4'];
fieldsToPreFill.forEach(fieldName => {
let textTab = new docusign.Text();
let value = userData[fieldName];
if (value === null || value === undefined) { value = 'not null'; }
textTab.setTabLabel(fieldName);
textTab.setValue(value);
tRole.getTabs().getTextTabs().push(textTab);
});
tRole = removeNulls(tRole);
envDef.setTemplateRoles([tRole]);
// send the envelope by setting |status| to 'sent'.
// To save as a draft set to 'created'
// sent is required for getting view URLs
envDef.setStatus('sent');
return envDef;
}
In the template editor on docusign, the Data Field Tag Properties show the label of each of the corresponding fields as field1, field2, etc.
These fields are now filled out with the provided values when I throw the new envelope in an iframe.
just for reference here is the rest of the code that creates the api connection, and gets the view URL
import ENV from 'environment/backend';
const accountId = ENV.docusign.accountId;
var Promise = require('bluebird');
var docusign = require('docusign-esign');
export function newApiClient() {
let apiClient = new docusign.ApiClient();
apiClient.setBasePath(ENV.docusign.endpoint);
// create JSON formatted auth header
let creds = JSON.stringify({
Username: ENV.docusign.email,
Password: ENV.docusign.password,
IntegratorKey: ENV.docusign.integratorKey
});
apiClient.addDefaultHeader('X-DocuSign-Authentication', creds);
// assign api client to the Configuration object
// this probably doesn't need to be set every time...
docusign.Configuration.default.setDefaultApiClient(apiClient);
return apiClient;
}
const defaultApiClient = newApiClient();
const envelopesApi = new docusign.EnvelopesApi();
const createEnvelope = Promise.promisify(envelopesApi.createEnvelope, { context: envelopesApi });
const listRecipients = Promise.promisify(envelopesApi.listRecipients, { context: envelopesApi });
const createRecipientView = Promise.promisify(envelopesApi.createRecipientView, { context: envelopesApi });
export default defaultApiClient;
// promise resolves to the view URL, envelopeId for the user.
// returns a recipientView
export function setupDocumentForEmbeddedSigning(templateId, userData) {
let envDefinition = createEnvelopeDefinition(templateId, userData);
return createEnvelope(accountId, envDefinition, null)
.then(envelopeSummary => {
const envelopeId = envelopeSummary.envelopeId;
return createViewFromEnvelope(envelopeId);
});
}
export function createViewFromEnvelope(envelopeId) {
return getRecipients(envelopeId).then(recipients => {
// the last signer is the one we added in the
// createEnvelopeDefinition step
let signers = recipients.signers;
let lastSigner = signers[signers.length - 1];
return createView(envelopeId, lastSigner)
.then(recipientView => [recipientView.url, envelopeId]);
});
}
function getRecipients(envelopeId) {
return listRecipients(accountId, envelopeId);
}
function createView(envelopeId, signerData) {
var viewRequest = new docusign.RecipientViewRequest();
viewRequest.setReturnUrl(ENV.host);
viewRequest.setAuthenticationMethod('email');
// recipient information must match embedded recipient info
// from the createEnvelopeDefinition method
viewRequest.setEmail(signerData.email);
viewRequest.setUserName(signerData.name);
viewRequest.setRecipientId('2');
viewRequest.setClientUserId('2');
return createRecipientView(accountId, envelopeId, viewRequest);
}
// bug with the api wrapper
// https://github.com/docusign/docusign-node-client/issues/47
const removeNulls = function(obj) {
var isArray = obj instanceof Array;
for (var k in obj) {
if (obj[k] === null) isArray ? obj.splice(k, 1) : delete obj[k];
else if (typeof obj[k] == 'object') removeNulls(obj[k]);
if (isArray && obj.length == k) removeNulls(obj);
}
return obj;
};
So, I may not fully understand where you're stuck, but I'll take a crack at this anyway...
Let's say I create a Template using the DocuSign UI and define two Recipient roles:
Signer1 (which will be the person who is signing up for your service) -- Action = "Sign"
CarbonCopy1 (which will be the person who gets a copy of the completed/signed documents once Signer1 signs) -- Action = "Receive a Copy"
(Note: these roles can be named whatever you want to name them -- I named them "Signer1" and "CarbonCopy1" so it'd be clear who each role represents.)
Assuming the above scenario, your Template's Recipient Roles (in the DocuSign UI) will look like this:
Next, let's assume that you define some fields (tabs) in the Template's document(s) (i.e., using the DocuSign UI) that the Signer1 recipient will need to populate when they sign the document(s). For this example, let's assume that the label (name) of one of those Text tabs is field1. Notice that the field is assigned to the Signer1 recipient:
Now, if I want to create an Envelope via the API that uses this Template, and pre-fill fields for one or more of the recipients, the key to doing that is using the "Composite Templates" structure in the API request. (See the Composite Templates section of this page for details.) In the example described above, your compositeTemplates object in the API request would contain a single serverTemplate object (which specifies the templateId and sequence=1), and a single inlineTemplate object (which specifies sequence=2 and the recipient info, including values for any tabs (fields) that you want to pre-fill).
In the example described above, the JSON API request to create the Envelope would look like this (assuming we're just pre-filling a single field for Signer1 -- obviously you could pre-fill additional fields by simply including them in the tabs object of the request along with field1):
POST https://{{env}}.docusign.net/restapi//v2/accounts/{{accountId}}/envelopes
{
"emailSubject": "Test Pre-fill Tabs",
"emailBlurb": "This is a test.",
"compositeTemplates": [{
"serverTemplates": [{
"sequence": "1",
"templateId": "CD0E6D53-3447-4A9E-BBAF-0EB2C78E8310"
}],
"inlineTemplates":[{
"sequence": "2",
"recipients": {
"signers": [
{
"roleName": "Signer1",
"recipientId": "1",
"name": "John Doe",
"email": "johndoe#test.com",
"clientUserId": "1234",
"tabs": {
"textTabs": [
{
"tabLabel": "field1",
"value": "TEST-123"
}
]
}
},
{
"roleName": "CarbonCopy1",
"recipientId": "2",
"name": "Jane Doe",
"email": "janedoe#test.com"
}
]
}
}]
}],
"status": "sent"
}
Once I create the Envelope using the above request, I execute a "POST Recipient View" request to get the signing URL for the first recipient (https://{{env}}.docusign.net/restapi//v2/accounts/{{accountId}}/envelopes/{{envelopeId/views/recipient).
Then, when I subsequently use the URL that's returned in that response to launch the signing session for Signer1 (John Doe), I see that the field1 tab is indeed pre-filled with the value that I specified in the "Create Envelope" API request (TEST-123):
Furthermore, once John Doe (Signer1) finishes signing and submits the completed documents, Jane Doe (CarbonCopy1) will be sent a copy.
I'm not familiar with the DocuSign Node SDK, but imagine you can figure out the syntax to use composite templates as shown in the above example. Hope this helps!