How to execute logic on query? - hyperledger-composer

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

Related

Modifying an array property of a resource (participant) during a transaction

I want to modify a property of a participant which is an array of relationships during a transaction.
Let's assume I have a user that holds an array of keys like so:
participant User identified by userId {
o String userId
--> Key[] keys
}
asset Key identified by keyId {
o String keyId
}
transaction modifyUserKeys {
--> User user
}
Then in the transaction processor function I modify the array (as in adding and removing elements in it) and update the participant:
function modifyUserKeys(tx) {
let user = tx.user;
// create a new key via factory
var newKey = ...
user.keys.push(newKey);
return getParticipantRegistry('com.sample.User')
.then(function (participantRegistry) {
return participantRegistry.update(user);
});
}
In the documentation I saw a method called addArrayValue() which adds an element to an array. Now I'm unsure whether I'm meant to use this over conventional array manipulation like in my example.
What purpose does this addArrayValue() method have and am I able to e.g. remove elements from keys via keys.pop() or is it restricted to just the addition of new elements like the documentation suggests?
you can use conventional (push/pop) if you like (and as you've done on the array), but newKey would need to use newRelationship()
a useful example similar to what you're trying to achieve is here -> https://github.com/hyperledger/composer-sample-networks/blob/master/packages/fund-clearing-network/lib/clearing.js#L151 in Composer sample networks - addArrayValue() is also validating that it does not violate the model

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.

ACL works in playground?

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.

MutableAcl.createAcl() with custom UserDetailsService

I am currently trying to implement Spring's ACLs into my existing application.
Sadly i am stuck at a specific point which seems to be caused by my UserDetailsService.
The problem/error is the following when i call the createAcl() function of the
MutableAcl class like this:
public void addPermission(long objectId, Sid recipient, Permission permission, Class clazz) {
MutableAcl acl;
ObjectIdentity oid = new ObjectIdentityImpl(clazz.getCanonicalName(), objectId);
try {
acl = (MutableAcl) mutableAclService.readAclById(oid);
} catch (NotFoundException nfe) {
acl = mutableAclService.createAcl(oid);
}
acl.insertAce(acl.getEntries().size(), permission, recipient, true);
mutableAclService.updateAcl(acl);
}
Inside of this function a new ObjectIdentity is created, if this class instance does not yet have one. A new PrincipalSid is created from the current Authentication object for this purpose (saved inside the ACL_SID table). Like this:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
PrincipalSid sid = new PrincipalSid(auth);
this.createObjectIdentity(objectIdentity, sid);
The problem occurs when it tries to save the entry into the ACL_SID table which is defined as this:
CREATE TABLE IF NOT EXISTS ACL_SID (
id BIGSERIAL NOT NULL PRIMARY KEY,
principal BOOLEAN NOT NULL,
sid VARCHAR(100) NOT NULL,
CONSTRAINT UNIQUE_UK_1 UNIQUE(sid,principal)
);
As you can see the sid is a VARCHAR with 100 characters. My custom User class contains a few properties which are mostly converted to a String representation, which causes the PrincipalSid to be longer than 100 characters. Now the first obvious solution would be to just change the toString method to only return the most essential values.
Still this seems kinda "hacky" to me. Is there a better solution?
The sid column in ACL_SID table should only contain the username, not the entire user object with its properties. To create a correct instance of PrincipalSid, make sure at least one of following is true:
auth.getPrincipal() returns an instance of UserDetails interface.
auth.getPrincipal().toString() returns the username.

Partial Entity Updates in WebAPI PUT/POST

Say you have a repository method to update a Document:
public Document UpdateDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
In this case, the entity has two properties. When updating a Document, both of these properties are required in the JSON request, so a request to PUT /api/folder with a body of
{
"documentId" = "1",
"title" = "Updated Title"
}
would return an error because "content" was not provided. The reason I'm doing this is because, even for nullable properties and properties that the user doesn't update, it seems safer to force the client to specify these fields in the request to avoid overwriting unspecified fields with nulls serverside.
This has led me to the practice of always requiring every updatable property in PUT and POST requests, even if it means specifying null for those properties.
Is this cool, or is there a pattern/practice that I haven't learned about yet that might facilitate partial updates by sending only what is needed over the wire?
The best practice in API design is to use HTTP PATCH for partial updates.
In fact, use cases like yours are the very reason why IETF introduced it in the first place.
RFC 5789 defines it very precisely:
PATCH is used to apply partial modifications to a resource.
A new method is necessary to improve interoperability and prevent
errors. The PUT method is already defined to overwrite a resource
with a complete new body, and cannot be reused to do partial changes.
Otherwise, proxies and caches, and even clients and servers, may get
confused as to the result of the operation. POST is already used but
without broad interoperability (for one, there is no standard way to
discover patch format support).
Mark Nottingham has written a great article about the use of PATCH in API design - http://www.mnot.net/blog/2012/09/05/patch
In your case, that would be:
[AcceptVerbs("PATCH")]
public Document PatchDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
Is this cool, or is there a pattern/practice that I haven't learned
about yet that might facilitate partial updates by sending only what
is needed over the wire?
A good practice of doing a POST or PUT is to only include values that you need for that specific request. In doing the UpdateDocument you should ask yourself what "really should be done here"? If you have a hundred fields on that object do you need to update all of them or only part of them. What "action" are you really trying to do?
Let's have an illustration for those questions, say we have a User object that has the following fields:
public class User {
public int Id {get;set;}
public string Username {get;set;}
public string RealName {get;set;}
public string Password {get;set;}
public string Bio {get;set;}
}
You then have two use cases:
Update the profile of a User
Update the password of a User
When you do each of those you will not, or it's a good idea to, have one update method that will do both. Instead of having a generic UpdateUser method you should have the following methods:
UpdateProfile
UpdatePassword
Methods that accepts fields that they just need, nothing more, nothing less.
public User UpdateProfile(int id, string username, string realname, string bio) {
}
public User UpdatePassword(int id, string password) {
}
Now comes the question:
I have a use case that a "user action" allows for an update on
multiple fields where some of the fields can have "no input" from the
user but I don't want to update that field in my model.
Suppose a user updates his/her profile and provided values for Username, RealName but not for Bio. But you do not want to set Bio as null or empty if it has a value already. Then that becomes a part of your application's business logic and that should be handled explicitly.
public User UpdateProfile(int id, string username, string realname, string bio) {
var user = db.Users.Find(id);
// perhaps a validation here (e.g. if user is not null)
user.Username = username;
user.RealName = realname;
if (!string.IsNullOrEmptyWHiteSpace(bio)) {
user.Bio = bio;
}
}

Resources