ACL works in playground? - hyperledger-composer

Could you please let us know if ACL works in playground ??
I want to create a rules, where owner of the asset can only modify the rules. I tried in playground by,which is not working
I created file as an asset and supplier as the owner of the asset. Then, created asset called file1 attached the supplier1 as the owner. When i am performing the submit transactions, Supplier2 can also modify the transactions. IS my rule are not valid ?? Do i need to some more ruless ??
/**
* New model file
*/
namespace org.acme.model
enum TransactionState {
o CREATED
o VERIFIED
}
asset File identified by fileId {
o String fileId
o String data
--> Supplier owner
o TransactionState state
}
participant Supplier identified by supplierId {
o String supplierId
o String emailId
o String details
}
transaction DataValidate {
--> File asset
o TransactionState state
--> Supplier supplier
}
/**
* Data Validation by Supplier
* #param {org.acme.model.DataValidate} dataValidate - the DataValidate transaction
* #transaction
*/
function DataValidate(dataValidate) {
dataValidate.asset.state = dataValidate.state;
return getAssetRegistry('org.acme.model.File')
.then(function (assetRegistry) {
return assetRegistry.update(dataValidate.asset);
});
}
rule Rule1 {
description: "can perform ALL operations , IF the participant is owner of the asset"
participant(m): "org.acme.model.Supplier"
operation: ALL
resource(v): "org.acme.model.File"
condition: (v.owner.getIdentifier() == m.getIdentifier())
action: ALLOW
}
rule Member {
description: "Allow the member read access"
participant: "org.acme.model.Supplier"
operation: READ
resource: "org.acme.model.*"
action: ALLOW
}
My criteria, data validation should be done only by the owner of the file, not others. how to handle it

To answer your main question - Yes the ACL file does work in the Online Playground, I have it working for one of my applications. If you are not referring to the online playground I'm not sure if the rest of my answer helps.
If you are using the online Playground presume that you've gone to the top right where it says 'admin' and created new identities and issued those to participants? If not you can do so by:
Clicking 'admin' in the top right of the playground
'+ Issue New ID'
Supply a User ID (whatever you like) and participant (will be one you created earlier) and then press 'Create New'
Select Option 2: '+ Add to my Wallet' (This will allow you to then use the identity and you will 'be' that participant
The reason I ask is that even with your ACL file working, if you remain as the 'admin' identity you can still view/do everything you want.
Another thing, could you try '===' instead of '==' in your Rule1? The two rules make sense, and from looking at it, all users can view, but an error would be raised if anyone except the owner tries to validate that asset because it requires UPDATE permissions which are not granted.
Hope this helps.

Related

AWS Amplify authorization based on db field value or relationship

I am creating a webapp with Amplify ( GraphQL api ) and Quasar Framework.
Using Amazon Cognito for authentication.
Lets say the db has these entities:
A User who has his own profile where he can manage his own data, and even make it public if he turns the 'public' boolean field to true.
An Organization who have todos etc.
A User can become an Employee of one ( or maybe more ) organization(s) and should be able to manage for example the todos that belong to the organization where he became an employee.
I am stuck at figuring out how to add authorization rules to make this happen.
Owner authorization should be suitable for the user profile, but even there its not clear how to setup a rule that makes the profile public if the user sets the 'public' boolean field setting to true in his profile.
For example:
type Todo #model #searchable #auth(rules: [{allow: owner, operations: [read, create, update, delete]}]) {
id: ID!
Title: String!
Description: String
}
This way if a user logs in he can manage and list his own todos, but how can I allow him to view and manage todos that belong to an organization where he is an employee ( employee would be a join table which connects the user and the organization )?
I have undertaken some research on this issue, and although it is far from complete, I would like to share it.
Despite amplify docs say, that there is possibility to combine multiple autorization types, they don't specify explicitly, that you can't combine them in one request (my be, it's evident for them, but not for novice like me).
When you configure your AppSync GraphQL API with amplify update api, you choose default authorization type. All subsequent requests from your front by await API.graphql() will use this default unless you explicitly specify different type like this await API.graphql(Object.assign(graphqlOperation(listTodos),{authMode: auth_mode})), where auth_mode can take one of next values: "API_KEY", "AWS_IAM", "AMAZON_COGNITO_USER_POOLS" and "OPENID_CONNECT".
There are two "true" public authorization types in Amplify - "API_KEY" and "AWS_IAM". To activate any of them (or both), you should do something like that:
type Todo #model
#auth(rules: [
{ allow: public },
{ allow: public, provider: iam},
])
{
id: ID!
name: String!
description: String
owner: String
editors: [String]
entity: String
}
{ allow: public } stands for "API_KEY", { allow: public, provider: iam} - for "AWS_IAM".
For "API_KEY"to work propery you should configure API KEY (for ex - with amplify update api). For "AWS_IAM" you should configure Cognito and enable unauthenticated identities in Cognito Identity Pool.
After that, any request without prior user sign-in of the form await API.graphql(Object.assign(graphqlOperation(listTodos),{authMode: auth_mode})) with auth_mode=API_KEY or auth_mode=AWS_IAM will succeed (also updateTodo, createTodo and deleteTodo).
In either case you can't implement your workflow with public field, enabled by user, "out of the box", because permission evaluation get accomplished prior to any database info gets available. For ex, IAM authorization uses unauthenticated role policy, generated for you by amplify update api. You can see it in your AWS console in IAM service.
To partially implement your private/public workflow I can suggest to use so called dynamic group authorization(We assume "AMAZON_COGNITO_USER_POOLS" auth mode). In that case you implement "public" group in your Cognito User Pool and make any user member of this group (you can automate this by using post signUp hooks).
Your #auth could be something like that
type Todo #model
#auth(rules: [
{ allow: owner },
{ allow: groups, groupsField: "groupsCanAccess", operations: [read] },
])
{
id: ID!
name: String!
description: String
owner: String
editors: [String]
entity: String
groupsCanAccess: [String]
}
When your user decides that it's time to go public, she request updateTodo with groupsCanAccess set to public. As you can see, this is partial solution because to read todos your "public" user should be registered.
To partially implement your employee-organization workflow I could suggest next approach (We again assume "AMAZON_COGNITO_USER_POOLS"):
type User #model
#auth( rules: [
{ allow: owner },
{ allow: groups, groupsField: "groupsCanAccess", operations: [read] },
{allow: groups, groups: ["Admin"] }
]) {
id: ID!
owner: String! #auth(rules[{allow: groups, groups: ["Admin"] }])
groupsCanAccess:[String]
#auth(rules[{allow: owner},{allow: groups, groups: ["Admin"] }])
todo: [Todo] #connection(keyName: "byUser", fields: ["owner"])
type Todo #model
(queries: null)
#auth ({allow: owner, operations[delete]})
#key [(name: "byUser", fields: ["owner", "description"])
{
id: ID!
name: String!
description: String
owner: String! #auth(rules[{allow: owner}])
}
Here everybody can create, update and read Todo, but read operation is possible only through User (queries: null), so nobody can get particular id to update particular Todo except owner. There is though some little possibility that someone can guess this id and it's drawback of this approach. It's impossible to create Todo without owner (exclamation sign on field), and nobody can create Todo but owner (nobody can alter owner field but owner). Note that operations[delete] is important, because without that nobody can query Todo through User ( {allow: owner} equivalent to {allow: owner, operations[create,update,read,delete]}).
Owner can do everything with User, but she can't create or delete User because she can't alter owner field protected by perfield #auth directive
Admins can create User and set owner to particular user.
Admins and owner can alter groupsCanAccess field. Every element in this array corresponds to some organization. When Admins or owner add some organization, every member of this group gains access to this User and to all of her Todos through this User. Drawback - owner can forbid access granted by Admins. Because Todos aren't protected from update - every group member can alter Todos of particular User.
Operations of adding Cognito User Pool user to group performed by admins and are out of scope of this document. Drawback - only 300 Groups per Cognito Pool.
And last - you of course have option to manually adjust resolvers, automatically generated by Amplify. You may for ex use "AWS_IAM" and organize owner check by analyzing $context.identity.cognitoIdentityId inside your rezolver mapping template. As you know - there is one-to-one correspondence between this Cognito Identity Pool paramener and user from User Pool. It's especially convinient when you need to store public and owner's Todos (there is some unique per Identity Pool cognitoIdentityId corresponding to unauthenticated user).
Have exact the same problem
My hopefully temporarily solution is : Adding a lamdaFunction : myResolver and adding this function to the graqphl-schema. in that function a can do all the filtering thru the databaseClient Class
the schema
type Query {
myqueryresolver(params: String): String #function (name: "myqueryresolver-${env}")
}
Now You define a lamda (In my case I use a generic form ....) in which you perform all the non standard queries.
You call this function :
const dataObj = await API.graphql(
{
query: myqueryresolver,
variables: {"params": JSON.stringify(params)}
}
);
All the public access goes thru this function call.
Additional hint
You can run it locally with "amplify mock" and you can see all the output from your console.log locally - no need to deploy during dev.

Create indirect relationships and return the sum of two relationships

I have a program to share services between people. Basically I am facing a problem trying to get the conversations between users. Here is the idea:
My program allows users to offer services to other users, but also to acquire services from other users. Each user can register an unlimited number of services.
When a user finds a service that he/she wants to get, he creates a deal and starts a conversation with the counterpart.
The structure of my tables is the following (I am only including the relationship columns):
Users table
id // This is the user identifier
Services table
id // This is the service identifier
user_id // This is the identifier of the user who created the service
Deals table
id // This is the deal identifier
user_id // This is the identifier of the user acquiring the service
service_id // This is the identifier of the service being acquired in this deal
Conversations table
id // This is the identifier of the conversation
deal_id // This is the identifier of the deal that the conversation belongs to
Here is my problem:
I want to be able to retrieve the conversations for each user, both as applicant and as a seller.
I created this relationship (in User.php) for conversations in which the user is acquiring the service:
public function conversationsApplicant(){
return $this->hasManyThrough( Conversations::class, Deal::class );
}
I would like to create also the conversationsSeller() function
public function conversationsSeller(){
return $this->????;
}
I am guessing I should add some kind of relationship between a Conversation and a Service in Service.php. It would be something like $this->services()->with( 'deals' )->with( 'conversations' );
The final goal would be to have a method that returns both relationships in one $user->conversations()->get().
public function conversations(){
return $this->conversationsApplicant() + $this->conversationsSeller();
}
To retrieve the relationship I was thinking that maybe there is a workaround using a SQL query, but I am not sure if that will return the relationship as I need it. Here is the query that I need to perform:
SELECT
conversations.*
FROM
mydb.conversations, mydb.services, mydb.users, mydb.deals
WHERE
users.id = services.user_id AND
services.id = deals.service_id AND
deals.id = conversations.deal_id AND
users.id = $user->id;
Here is how you implement merged relationship simply
public function getCombinedChats($value)
{
// There two calls return collections
// as defined in relations.
$Applicant= $this->conversationsApplicant;
$Seller= $this->conversationsSeller;
// Merge collections and return single collection.
return $Applicant->merge($Seller);
}

How to write scripts to create new participants and assets in hyperledger composer?

I am trying to create new participants and assets in hyperledger composer by writing scripts. Please help me to do that. If it is not possible please let me understand the reason.Please provide the sample code snippet also.
Thank you
A quick background, Participants and Assets are the "resources" that you model using Composer Modeling Language. The other resources are Transactions and Events.
For your use case, where you want to create scripts to add these resources, you'll need to use transaction processor functions. In these functions, you'll utilize the registry and factory to create participants and assets. Also, remember that before creating a transaction processor function, you'll first need to create a Transaction resource.
A sample code below:
Say we have a participant User. And to create this participant, we use transaction CreateUser. So your model file will be:
namespace org.network.participants
participant User identified by userId {
o String userId
o String name
o String email
}
transaction CreateUser {
o User userDetails
}
And your script file will be:
/**
*
* #param {org.network.participants.CreateUser} transactionRequest
* #transaction
*/
async function createUser(transactionRequest) {
try {
let userDetails = transactionRequest.userDetails;
let userRegistry = await getParticipantRegistry('org.network.participants.User');
let factory = await getFactory();
let user = await factory.newResource('org.network.participants', 'User', userDetails.userId);
user.name = userDetails.name;
user.email = userDetails.email;
return userRegistry.add(user);
} catch(exception) {
throw new Error(exception);
}
}
You can refer to the official composer documentation for further reference.

How to execute logic on query?

With transactions, we can put our business logic in the transaction processors. Very simple access control logic can be put in the ACL file. But how can we use more complex logic to guard (or extend) queries?
I'm working on a case where I'd like to restrict read access to an asset by checking if another asset exists and has a certain property.
Example:
asset PersonalDetails identified by id {
o String id
o String dateOfBirth
o String firstName
o String lastName
--> Participant owner
}
asset AccessRequest identified by id {
o String id
o String property
o Boolean allowed
--> PersonalDetails personalDetails
}
When a participant requests PersonalDetails, an AccessRequest has to exist and to have allowed === true. The owner of the personal details is the one who can grant access. Ideally the AccessRequest has a field 'property' to allow more fine grained access control.
So my initial thought was:
transaction GetInfo identified by transactionId {
o String transactionId
--> AccessRequest accessRequest
}
/**
* Sample transaction
* #param {org.example.GetInfo} tx
* #transaction
*/
function getInfo(tx) {
if (!tx.accessRequest.allowed) {
throw 'Access denied.';
}
return Promise.resolve(tx.accessRequest.personalDetails[tx.accessRequest.property]);
}
But I don't think Composer supports returning values from a transaction (right?). So, in general how can we use logic in or before queries and more specifically, what would be the 'composer way' to solve my case?
I'm in the process of committing a new sample network that illustrates how to do this. You can follow the pull request here:
https://github.com/hyperledger/composer-sample-networks/pull/66/commits/6bb0d757a0248046da60b013cfddbd7d549686b6

How to manage new Breeze entities which are aggregate roots?

I have a domain model which has a Customer, which in turn has 1 Address (1:1) and 1 or more Phone numers (1:M).
Customer has user supplied PK (a string), while Address and Phone use identity column (server generated).
I am struggling in trying to understand how to manage Breeze entity creation for a "Add new Customer" screen.
The form on the screen allows user to enter Customer, Address, and Phone data.
I am using Durandal and Knockout so my "customeradd.js" viewmodel looks something like this:
// -- snip ---
var customer = ko.observable(),
hasChanges = ko.computed(function () {
return datacontext.hasChanges();
});
var vm = {
hasChanges: hasChanges,
customer: customer,
activate: activate
};
return vm;
function activate() {
customer(datacontext.createCustomer());
}
// -- snip ---
and my "/services/datacontext.js" :
// -- snip ---
breeze.NamingConvention.camelCase.setAsDefault();
var manager = new breeze.EntityManager(config.remoteServiceName);
var hasChanges = ko.observable(false);
manager.hasChangesChanged.subscribe(function (eventArgs) {
hasChanges(eventArgs.hasChanges);
});
function createVehicle() {
return manager.createEntity("Customer");
}
// -- snip ---
My questions are following:
Once I create a Customer, do I need to create Address and list of Phones and add them to Customer entity before making it a KO observable? Or is this done automatically by createEntity() method?
How do I create a Customer but without having to specify the Id? If I set the key to null or '', Breeze complains ("Error: Cannot attach an object to an EntityManager without first setting its key or setting its entityType 'AutoGeneratedKeyType' property to something other than 'None'"). However, if I generate the temp key (using either breeze.core.getUuid() or something else), then it shows up in my Id form field, and I really want the end user to specify it....Do I have to resort to extending the entity with extra field and then do the swapping and validation before saving (I don't like this idea at all)? Is there a better way?
In order to enable/disable buttons on my form I am tracking if there are changes in EntityManager. But every time entity is created, it is automatically in 'added' state so hasChanges is true. What I want is for changes to be picked up only if user edits the form (and therefore makes changes to underlaying entity). What is the best way to approach this?
BTW, I have seen this recommendation to register custom constructor for entity (I have already implemented it but I am still not clear how to let user supply their own id and to flag entity as modified only when user edits it...)
I realize this has been up for a while, but here are my thoughts (in case anyone comes looking).
If you use the entityManager to create your customerl and everything is specified correctly in the metadata, you can just create the customer and add phone numbers/addresses as needed. Breeze automatically makes an entity's properties observable (if specified correctly and if breeze knows that KO is being used)
If you can only do it the way that you say, then you are stuck. Ideally, you would have a user-entered ID which is NOT the key (though you could still force it to be unique) and a database-generated key, which Breeze will manage behind the scenes (assigning a negative key until it is saved to the data store, then updating the key and all related keys without any input from you).
if you use the 2nd approach for answer 2, then your buttons can easily be enabled and disabled using ko data-binding. When you create the entity, save its value to the viewmodel (custSource). Then you can add to the save button the data-bind="disable: custSource == Customer(), enable: custSource != Customer()". (You might need to play around with the syntax -- I haven't tested that part yet)
I don't think you need a custom constructor unless you are doing something different from what I understand.
PS. you should be aware that I believe Breeze wants Knockout defined as 'ko', while Durandal definitely expects it to be 'knockout', so you will probably need a 'map' property in your require.config
I think you could solve some of your problems by taking a slightly different approach to your entity creation. Here's your current approach:
Create a customer entity
User modifies that entity
Save the changes
Instead, try this:
User enters customer information
Create and save the customer entity
I realize that this doesn't really answer your questions, but I've found the second approach to be much easier to implement. Just have the user enter all the information you need to create a customer, and then supply those values to createEntity.
customeradd.js
// -- snip ---
var vm = {
customerId: ko.observable(),
address: ko.observable(""),
phoneNumbers: ko.observableArray([]),
submit: submit
};
return vm;
function submit() {
datacontext.createCustomer(
vm.customerId(),
vm.address(),
vm.phoneNumbers());
}
// -- snip ---
/services/datacontext.js
// -- snip ---
/**
* Creates a new customer.
* #param {int} id - The customer's id number.
* #param {string} address - The customer's address.
* #param {string[]} phoneNumbers - An array of the customer's phone numbers.
*/
function createCustomer(id, address, phoneNumbers) {
return manager.createEntity("Customer",
{
id: id,
address: address,
phoneNumber: phoneNumbers
});
}
// -- snip ---

Resources