AWS Amplify combine authorization rules - amazon-web-services

I have a multi tenant application in AWS Amplify, I'm using the Custom-attribute-based multi-tenancy described here
All models have a composite key with "company" being the unique tenant ID, and the cognito user pool has a custom attribute custom:company which links the user to the tenant data.
Example type below:
type Customer #model
#key(fields: ["company", "id"])
#auth(rules: [
{ allow: owner, ownerField: "company", identityClaim: "custom:company"},
{ allow: groups, groups: ["Member"], operations: [read] },
{ allow: groups, groups: ["Admin"]},
])
{
company: ID!
id: ID!
...
}
I want to add user groups to cognito to manage the operations that different users can perform - e.g. Admin users can perform all operations, but Member users can only perform read
The problem is the first owner auth rule will match for anyone with the matching custom:company attribute, regardless of their Group.
Is there a way to combine owner and group #auth rules - i.e. both owner and groups needs to pass to have access to an item?
For example - users of the Member group is allowed but only when their custom:company attribute matches the company of the model
Another example - anyone with a matching custom:company attribute have access to an item but Members can only read

Related

Creating different User Types in AWS Amplify

I am planning to use AWS Amplify as a backend for a mobile application. The App consists of two User Types (UserTypeA,UserTypeB). They have some common data points and some unique one's too.
UserTypeA(id, email, firstName, lastName, profilePicture, someUniquePropertyForUserTypeA)
UserTypeB(id, email, firstName, lastName, profilePicture, someUniquePropertyForUserTypeB)
What would be a scalable approach to achieve this? I am also using AWS Amplify authentication so I can save the common data as CustomAttributes offered by Cognito, but then how would I save the uniqueProperties for the two user types. Will this approach scale?
This is a social app and is heavily reliant on other users' profile data as well (which will be queried most of the time).
Check out the patterns that are recommended by AppSync (The graphQL service that is behind Amplify when adding graphQL API). It is described in detail here: https://docs.aws.amazon.com/appsync/latest/devguide/security-authorization-use-cases.html
The main idea is to have multiple user pools defined in Cognito and then you can use the different groups in the resolvers. For example:
// This checks if the user is part of the Admin group and makes the call
#foreach($group in $context.identity.claims.get("cognito:groups"))
#if($group == "Admin")
#set($inCognitoGroup = true)
#end
#end
#if($inCognitoGroup)
{
"version" : "2017-02-28",
"operation" : "UpdateItem",
"key" : {
"id" : $util.dynamodb.toDynamoDBJson($ctx.args.id)
},
"attributeValues" : {
"owner" : $util.dynamodb.toDynamoDBJson($context.identity.username)
#foreach( $entry in $context.arguments.entrySet() )
,"${entry.key}" : $util.dynamodb.toDynamoDBJson($entry.value)
#end
}
}
#else
$utils.unauthorized()
#end
or using the #directives on the graphQL schema, such as:
type Query {
posts:[Post!]!
#aws_auth(cognito_groups: ["Bloggers", "Readers"])
}
type Mutation {
addPost(id:ID!, title:String!):Post!
#aws_auth(cognito_groups: ["Bloggers"])
}
...
This is a Database design problem. To solve this, you can try creating a relation that has the common attributes in it, that is, User with attributes, (ID, email, firstName, lastName, profilePicture, someUniquePropertyForUserTypeA).
After that, create sub-classed based relations, that is UserTypeA, and UserTypeB.
These relations will have a unique ID, and have a foreign key relation with the parent (User). How? The first major relation would be 'User'. The 2 sub classed relations would be 'UserTypeA', and 'UserTypeB'.
The 'User' has an attribute 'ID'.
So the two sub classes have an attribute, 'User_ID', which is a foregin relation to 'User'.'ID'.
Now just autogen another ID column for UserTypeA and UserTypeB.
This way, you have a central table which has a unique ID for all users, and then you have a unique ID in each of the sub class relations, which together with User_ID forms a composite key.

Cognito Group Permission Dynamically from Database

I created a cognito pool
Created users
Created 2 Groups WITHOUT any IAM roles
Assigned users to 2 different groups.
I store policies for a group in database and cache them .
In the lambda authorizer that has been configured , the deny policy works with principalId set to a random string.
For allowing access , i set the principal Id to the cognito User name. I get the policy from the database with permissions allowed for all api gateway end points. ( For testing )
But even after this i get the "User is not authorized" message.
Is my understanding wrong ? What am i doing wrong.
This is my policy for allowing access with the userId being the cognito user name.
authResponse = {}
authResponse['principalId'] = userId
authResponse['policyDocument'] = {
'Version': '2012-10-17',
'Statement': [
{
'Sid': 'FirstStatement',
'Action': 'execute-api:Invoke',
'Effect': 'Allow',
'Resource': 'arn:aws:execute-api:us-east-1:*:ppg7tavcld/test/GET/test-api-1/users/*'
}
]
}
return authResponse
Sorry . this was a mistake from me.
It was solved due to mixing up the stage position in the Resource

How would I autofill a field in an aws appsync schema?

I'd like to autofill a "userId" field when an object of this type is created.
type FriendRequest #model
#key(fields: ["userId", "receiver"])
{
userId: ID!
receiver: ID!
}
How would this be done? Would I need an #function directive on the userId field? What would that function look like?
Found the answer. Find the velocity resolvers in amplify\backend\api<your api name>\build\resolvers and move the mutations you want to edit into amplify\backend\api<your api name>\resolvers.
To autofill a field I used this line, which is based off the line of code used to autofill the "createdAt" and "updatedAt" fields for appsync api objects:
$util.qr($context.args.input.put("your field name here", $util.defaultIfNull($ctx.args.input.your field name here, [your value here])))
In my case I used $context.identity.sub to get the user's id, which I learned from this doc.

AppSync - creating nested mutation with array and objects?

Does AppSync support nested single mutation?
I want to call a single mutation which will insert records into two tables, eg: User and Roles tables in DynamoDB.
Something like this for example:
createUser(
input: {
Name: "John"
Email: "user#domain.com"
LinesRoles: [
{ Name: "Role 1" }
{ Name: "Role 2" }
]
}) {
Id
Name
LinesRoles {
Id
Name
}
}
Do I need to create two resolvers in AppSync for User and Roles to insert the records in both tables?
I can think of three ways to achieve this:
Use a BatchPutItem to save records into two tables at once. However, you won’t be able to use any ConditionExpression
Use a pipeline resolver with two AppSync functions where one function makes a PutItem to the Roles table and the other to the User table. However, you need to be ok with potentially inconsistent scenarios where the record has been inserted in one table but not in the other.
Use a Lambda resolver that does the write to 2 tables inside a DynamoDB transaction.

Update Hyperledger ACL from transactions

I have User and Buyer participants on the network. Generally, the buyers cannot READ the user's data but I want to make GrantAccess and RevokeAccess transactions so the Users to have the option to grant and revoke the READ access from Buyers
I haven't been able to find anything on how to do this, would appreciate any help.
You would run a 'tx_GrantAccess' transaction that firstly, updates a particular BUYER's record (eg. id buyer123 - a participant modeled with a field called access, which is set to true by this transaction).
I can use a condition match (as a boolean) on the target BUYER records (resources) and if the BUYER, say buyer123 (ie that's accessing the business network) has access=true then he can READ the USER records.
Transaction rule (needed by User to access the transaction classes)
rule rule_1 {
description: "grant access to User, for the 2 x Transactions themselves"
participant: "org.acme.example.User"
operation: CREATE
resource: "org.acme.example.tx_*"
action: ALLOW
}
User Access rule:
rule rule_2 {
description: "if granted access, allow READ of User by buyer"
participant(m): "org.acme.example.Buyer"
operation: READ
resource(v): "org.acme.example.User"
condition: (m.access)
action: ALLOW
}
where Buyer has a field (eg.
participant Buyer identified by id {
o String id
o Boolean access default=false
}
and your transaction tx_GrantAccess has a function that will set access to true on a particular Buyer's record and tx_RevokeAccess will set it to false etc.