Using this ACL for models, I want to prevent creating new models that have owner (userId) for those access-tokens that lead to other users, rather than the actual owner. I don't want userId 4 post a new model, and set that model's owners to 5, another userId. But this doesn't work right now, since there is no model on database to check, so what should I do? This worked for deletion last time I checked. But I need for posting...
I think this will do it.
server/boot/01-add-user.js
module.exports = (server) => {
// Before each create request, assign the userId property to the userId of the accessToken
const addUser = async (ctx) => {
function assignUserId(o) {
o.userId = ctx.req.accessToken.userId;
}
// You can post arrays of objects to loopback
if (Array.isArray(ctx.req.body)){
ctx.req.body.forEach(assignUserId);
} else {
assignUserId(ctx.req.body);
}
};
server.models.MY_MODEL.beforeRemote('create', addUser);
}
For your ACLs try this
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$owner",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW",
"property": "create"
}
],
Related
ERROR MESSAGE AT BOTTOM OF POST
I've setup a serverless architecture where my Cloudformation stack deploys a S3 bucket, customer and order table with content within once my text file with the content in is uploaded to the S3 bucket. The content displays fine once uploaded to my S3 bucket within DynamoDB 'Items' but I am receiving no SNS to my email inputted.
The error message is linked below in relation to for record in event['Records']:
Here is the code for my lambda function.
# TotalNotifier Lambda function
#
from __future__ import print_function
import json, boto3
# Connect to SNS
sns = boto3.client('sns')
alertTopic = 'HighOrderPurchaseAlertSNSTopic'
snsTopicArn = [t['TopicArn'] for t in sns.list_topics()['Topics'] if t['TopicArn'].lower().endswith(':' + alertTopic.lower())][0]
print(snsTopicArn)
# Connect to DynamoDB
dynamodb = boto3.resource('dynamodb')
orderTotalTableName = 'OrderTotal'
orderTotalTable = dynamodb.Table(orderTotalTableName);
# This handler is executed every time the Lambda function is triggered
def lambda_handler(event, context):
# Show the incoming event in the debug log
print("Event received by Lambda function: " + json.dumps(event, indent=2))
# For each order added, calculate the new order Total
for record in event['Records']:
customerId = record['dynamodb']['NewImage']['CustomerId']['S']
orderAmount = int(record['dynamodb']['NewImage']['OrderAmount']['N'])
# Update the customer's total in the OrderTotal DynamoDB table
response = orderTotalTable.update_item(
Key={
'CustomerId': customerId
},
UpdateExpression="add accountOrder :val",
ExpressionAttributeValues={
':val': orderAmount
},
ReturnValues="UPDATED_NEW"
)
# Retrieve the latest order purchase
latestAccountBalance = response['Attributes']['accountOrder']
print("Latest account balance: " + format(latestAccountOrder))
# If order > 10, send a message to SNS
if latestAccountBalance >= 10:
# Construct message to be sent
message = '{"customerID": "' + customerId + '", ' + '"accountOrder": "' + str(latestAccountOrder) + '"}'
print(message)
# Send message to SNS
sns.publish(
TopicArn=snsTopicArn,
Message=message,
Subject='Warning! High Account Order',
MessageStructure='raw'
)
# Finished!
return 'Successfully processed {} records.'.format(len(event['Records']))
Content within DynamoDB
CloudFormation Stack code:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description" : "Implementing a Serverless Architecture with AWS Managed Services",
"Resources": {
"InputS3BucketForCustOrdersFiles": {
"Type": "AWS::S3::Bucket"
},
"CustomerDynamoDBTable": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"TableName": "Customer",
"AttributeDefinitions": [
{
"AttributeName": "CustomerId",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "CustomerId",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": "5",
"WriteCapacityUnits": "5"
}
}
},
"OrdersDynamoDBTable": {
"Type": "AWS::DynamoDB::Table",
"DependsOn": "CustomerDynamoDBTable",
"Properties": {
"TableName": "Orders",
"AttributeDefinitions": [
{
"AttributeName": "CustomerId",
"AttributeType": "S"
},
{
"AttributeName": "OrderId",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "OrderId",
"KeyType": "HASH"
},
{
"AttributeName": "CustomerId",
"KeyType": "RANGE"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": "5",
"WriteCapacityUnits": "5"
}
}
},
"OrdersIAMRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "OrdersIAMRole",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
"arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
],
"Policies": [ {
"PolicyName": "CWLogsPolicy",
"PolicyDocument": {
"Version" : "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]}
}
]
}
},
"TotalNotifierRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "TotalNotifierRole",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AmazonSNSFullAccess",
"arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
],
"Policies": [
{
"PolicyName": "CWLogsPolicy",
"PolicyDocument": {
"Version" : "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]}
},
{
"PolicyName": "CWLogPolicy",
"PolicyDocument": {
"Version" : "2012-10-17",
"Statement": [
{
"Action": [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:DescribeStream",
"dynamodb:ListStreams"
],
"Resource": "*",
"Effect": "Allow"
}
]}
}
]
}
}
},
"Outputs": {
"InputS3BucketForCustOrdersFiles": {
"Value": {
"Ref": "InputS3BucketForCustOrdersFiles"
},
"Description": "Name of the S3 bucket in which orders from customers should be uploaded"
},
"CustomerDynamoDBTable": {
"Value": {
"Ref": "CustomerDynamoDBTable"
},
"Description": "Customer table in DynamoDB"
},
"OrdersDynamoDBTable": {
"Value": {
"Ref": "OrdersDynamoDBTable"
},
"Description": "Orders in DynamoDB."
},
}
}
dyanmo test
Stream is closed DynamoDB
It appears that you are running this function via the Test button in the AWS Lambda console. When doing so, you can specify the contents of the event that will be sent to the function.
The log files are showing that your Test passed this event to the function:
{
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
This is a 'fake' event and it clearly does not contain anything about a DynamoDB stream. Instead, you should create a test event by choosing the DynamoDB example template and configuring appropriate values in the test.
Or, trigger the event by updating information in DynamoDB, with a stream configured as a trigger to the AWS Lambda function.
thanks in advance!
I've been stuck on this issue for ages and can't find the solution...
Basically I want to implement the same access policy on my elasticsearch service but when I try to re-create this in cloudformation I receive a circular dependency error.. I know whats causing the error the Fn::GetAtt's which reference the elastic search DomainArn.
So my question is how do I go about implementing this statement without having to reference my elk domain arn?
Template contains errors.: Circular dependency between resources: [XXXXXX]
"XXXXXX": {
"Type": "AWS::Elasticsearch::Domain",
"Properties": {
"AccessPolicies": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": {
"Fn::GetAtt": ["myuser", "Arn"]
}
},
"Action": "es:*",
"Resource": {
"Fn::GetAtt": ["XXXXXX", "DomainArn"]
}
},
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "es:*",
"Resource": {
"Fn::GetAtt": ["XXXXXX", "DomainArn"]
},
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"xx.xx.xx.xx",
"xx.xx.xx.xx"
]
}
}
}
]
},
"DomainName": "XXXXXX",
"EBSOptions": {
"EBSEnabled": "True",
"VolumeSize": 10,
"VolumeType": "gp2"
},
"ElasticsearchClusterConfig": {
"InstanceCount": 1,
"InstanceType": "t2.small.elasticsearch"
},
"ElasticsearchVersion": "5.1",
"SnapshotOptions": {
"AutomatedSnapshotStartHour": 0
},
"Tags": {
"Key": "name",
"Value": "XXXXXX"
}
}
},
Rather than use Fn::GetAtt to retrieve the domain ARN, use Fn:Sub to construct the ARN using the rules here (scroll down to "Use the following syntax to specify domain resources for Amazon ES").
{ "Fn::Sub":"arn:aws:es:${AWS::Region}:${AWS::AccountId}:domain/XXXXXX" }
I am trying to tap into the password reset process on Loopback, so I can send an e-mail with the instructions to the user. I created a custom model called 'user' that extends 'User'. Then I added "User.on('resetPasswordRequest'...", but it never gets executed. Please check the console.log on the user.js
model-config.json
{
...
"user": {
"dataSource": "mysqlDs",
"public": true,
"options": {
"emailVerificationRequired": false
}
},
...
}
user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"principalType": "ROLE",
"principalId": "$everyone",
"accessType": "READ",
"permission": "ALLOW"
}
],
"methods": []
}
user.js
module.exports = function(User) {
console.log('It prints this log here.');
User.on('resetPasswordRequest', function(info) {
console.log('But it does not print this log here ever.');
var url = 'http://www.example.com/reset-password';
var html = 'Click here';
loopback.Email.send({
to: info.email,
from: 'mail#example.com',
subject: 'My Subject',
html: html
}, function() {
console.log('> sending password reset email to:', info.email);
if (err) return console.log('> error sending password reset email');
});
});
};
Ok, so #IvanSschwarz was right. Thank you so much! My initial code was correct, but it was missing a small change on the model-config.json: I needed to hide the User model for some reason. So I also took the opportunity and changed my model name from "user" to "member", to follow the loopback good practices guide lines.
...
"User": {
"dataSource": "mysqlDs",
"public": false
},
"member": {
"dataSource": "mysqlDs",
"public": true,
"options": {
"emailVerificationRequired": true
}
},
....
I believe that the other suggestions could potentially work as well, but I wanted to extend the User model through JSON and also leverage the built-in remote method Reset Password from the Users model. So thank you guys!
The solution is explained here:
http://loopback.io/doc/en/lb3/Extending-built-in-models.html#setting-up-a-custom-model
So you will need:
User.setup = function() {
var User = this;
// since setup is called for every extended model
// the extended model will also have the event listener
User.on('resetPasswordRequest', function() {
///Do your stuff here
});
}
Don't you simply miss to define a remote method in order to call your code?
This is what I would do:
user.js
module.exports = function(User) {
console.log('It prints this log here.');
User.resetPasswordRequest = function (info, cb) {
console.log('But it does not print this log here ever.');
var url = 'http://www.example.com/reset-password';
var html = 'Click here';
loopback.Email.send({
to: info.email,
from: 'mail#example.com',
subject: 'My Subject',
html: html
}, function() {
console.log('> sending password reset email to:', info.email);
if (err) {
cb(err, false);
return console.log('> error sending password reset email');
}
cb(err, true):
});
});
User.remoteMethod('resetPasswordRequest', {
description: 'Send a mail to an User to permit him to reset his password',
accepts: {arg: 'info', type: 'object', required: true},
returns: {arg: 'mailSend', type: 'boolean'},
http: {verb: 'post'}
});
};
Then, using the Explorer or calling the API via REST while posting a your info json like:
{
accessToken: xxx,
email: xxx
}
And don't forget to update your ACLs in user.json so you can call the method from remote:
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "resetPasswordRequest"
}
],
"methods": []
}
I have generated remote method using
slc loopback:remote-method
I tried to access this method from explorer and it give status 400 with message "phoneNumber is a required arg"
When I tried with postman it gives status 500 with message Internal sever error. I tried sending post data as form, x-www-form-urlencoded encoded, and row, it gives same result.
Server side error:
Unhandled error for request POST /otp/getOTP: TypeError: Cannot read property 'modelName' of null
at convertToBasicRemotingType (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:390:16)
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:544:20
at Array.filter (native)
at Function.SharedMethod.toResult (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:534:21)
at callback (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:249:31)
at Function.Onetimepassword.getOTP (/Users/manish/Documents/workspace-node/carbuk-services/common/models/one-time-password.js:14:4)
at SharedMethod.invoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/shared-method.js:263:25)
at HttpContext.invoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/http-context.js:387:12)
at phaseInvoke (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/strong-remoting/lib/remote-objects.js:644:9)
at runHandler (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/loopback-phase/lib/phase.js:135:5)
at iterate (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:146:13)
at Object.async.eachSeries (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:162:9)
at runHandlers (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/loopback-phase/lib/phase.js:144:13)
at iterate (/Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:146:13)
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:157:25
at /Users/manish/Documents/workspace-node/carbuk-services/node_modules/loopback/node_modules/async/lib/async.js:154:25
model json look like this
{
"name": "OneTimePassword",
"plural": "otp",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"phoneNumber": {
"type": "number",
"required": true
},
"otpNumber": {
"type": "number",
"required": true
},
"resendCounter": {
"type": "number",
"default": "0"
},
"createdDate": {
"type": "date"
} }, "validations": [], "relations": {}, "acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "deleteById"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "getOTP"
} ], "methods": {
"getOTP": {
"accepts": [
{
"arg": "phoneNumber",
"type": "number",
"required": true,
"description": "phone number",
"http": {
"source": "form"
}
}
],
"returns": [
{
"arg": "oneTimePassword",
"type": null,
"root": true,
"description": "otp"
}
],
"description": "generate otp and send sms",
"http": [
{
"path": "/getOTP",
"verb": "post"
}
]
} } }
model js:
module.exports = function(Onetimepassword) {
/**
* generate otp and send sms
* #param {number} phoneNumber phone number
* #param {Function(Error, )} callback
*/
Onetimepassword.getOTP = function(phoneNumber, callback) {
var oneTimePassword = {};
oneTimePassword.phoneNumber = phoneNumber;
// TODO
// logic will come here
//
callback(null, oneTimePassword);
};
};
Am I missing something?
You have a typo.
in line oneTimePassword.phoneNumber = phoneNumber; you need to set to optNumber
I found the solution.
In model.json, Remote method's return type is null. I changed this to object type "OneTimePassword"
But still I am not able to hit the remote method from explorer. There is a bug issue#440.
Using postman, request work fine
Having issues getting authentication working using static roles access control within models. Things seem to be working when using ACL's with principal types $authenticated and $everyone. So access controls are in place and functioning as expected when logged in and logged out. As ACL's are moved over to static roles authentication fails and a 401 is returned. Loopback built in models for for roles, role-mapping, and user are being used. I've tried using ROLE and USER as principalTypes.
Creating User, Role, and principal with RoleMapping:
User.create({
username: 'admin',
email: 'admin#admin.com',
password: 'password',
active: true
},
function (err, user) {
Role.create({
name: 'Admin'
},
function (err, role) {
if (err) throw err;
console.log('Created role:', role);
//make user an admin
role.principals.create({
principalType: RoleMapping.USER,
//principalType: RoleMapping.ROLE,
principalId: user.id,
active: true
},
function (err, principal) {
if (err) throw err;
console.log('Principal:', principal);
});
});
});
Customer Model:
"name": "Customer",
"base": "PersistedModel",
"strict": false,
"idInjection": false,
"options": {
"validateUpsert": true
},
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string"
},
"active": {
"type": "boolean"
}
},
"validations": [],
"relations": {
"products": {
.........
},
"users": {
.........
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "DENY"
},
{
WORKS AS EXPECTED
"accessType": "*",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
RETURNS 401 AFTER LOGGING IN AS USER ASSIGNED TO ROLE
"accessType": "*",
"principalType": "ROLE",
"principalId": "Admin",
"permission": "ALLOW"
}
],
"methods": []
User record created:
"_id" : ObjectId("55b7c34d6033a33758038c3b"),
"username" : "admin",
"password" : ....,
"email" : "admin#admin.com",
"active" : true
Role record:
"_id" : ObjectId("55b7c34d6033a33758038c3e"),
"name" : "Admin",
"created" : ISODate("2015-07-28T18:00:45.336Z"),
"modified" : ISODate("2015-07-28T18:00:45.336Z")
RoleMapping Record:
"_id" : ObjectId("55b7c34d6033a33758038c41"),
"principalType" : "USER",
"principalId" : "55b7c34d6033a33758038c3b",
"roleId" : ObjectId("55b7c34d6033a33758038c3e"),
"active" : true
Thanks before hand for any help!
When you define the user in principalId, try try instead:
principalId: user[0].id,
Please note at:
RoleMapping.principalType: USER (mean user, not ROLE)
RoleMapping.principalId: USER_ID (because define RoleMapping.principalType is USER)
acls: [
{ "principalType": "ROLE",
"principalId": "admin",
}
]
I have tested, it's working
Role
—————————
_id: ObjectId(5c70409a98103f1af6ee2b55)
name: “admin”,
description: “Only Admin can write”,
Role Mapping
——————————
_id: ObjectId(5c7040f998103f1af6ee2b57)
principalType: USER
principalId: 5c72b9ef79dcf14443c1aa3b
roleId: ObjectId(5c70409a98103f1af6ee2b55)
User
——————————
_id: ObjectId(5c72b9ef79dcf14443c1aa3b)
firstName: “”,
lastName: “”,
email:””,
Active:””,
emailVerified:
ACL
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "admin",
"permission": "ALLOW"
}