Introduction to Solidity: ACL and Events. [Part 2]


Back


Join over 115,115 Members
Angel Investors, Startups & Blockchain developers...
Yes, Get Access!
No Thanks!

This article is next in series to the previous one that talked about data contracts. The example contract in the previous discussion was inheriting from a contract called “ACLContract”, but we were not using any feature provided by the ACLContract as we were yet to see how ACL (Access Control List) and “eventing” could be used to deliver access control and access logging facility.

Access control is a mechanism to provide selective restrict access to a resource. There are various kinds of access control models in Information technology, most of them center around the user or “principal” that is attempting to access the resource. There are situations where the resource itself can define the access to it, but we will not go into that one. For our purpose, we will stick to two kinds of access controls – Role based and attribute based.

Introduction to Solidity: ACL and Events. [Part 2]

Inheritance:

Since access control is a feature that should be pluggable to some extent, we will leverage inheritance to provide this feature to your domain contracts. Inheritance in solidity uses the copy code mechanism to plant the “super-contract” code into “subcontract”- super-contract being the inheritable contract and sub-contract being the inheriting contract. Solidity also supports an Object Oriented Programming (OOPS) concept of polymorphism, where a contract instance can double up as any of its subclass instances.

 

Our ACL (Access Control List) contract is going to be inherited by a domain contract and will provide data access controls features. The domain contract will just have to surround its resource calls with the ACL contracts methods that it will inherit.

ACL contract:

The basic premise of the ACL contract is to provide methods to create/manage a list or lists of users and to provide methods that can check any specific user against that list. Our assumption is that each user of a contract is an “address” type – address is a special type of variable in solidity that represents your wallet or account address. It makes sense to use it as a user credential as a user only accesses a contract by his account address. We can also draw an assumption that the call from an address is authentic as it can only be made if the user has access to the private key of the account, which is the basic premise of authenticity on ethereum.  

 

The basic template of the contract is going to have two instance variables:

 

address public owner;     

address [] public users;

The owner address will be the creator of the contract. We will instantiate it during the constructor call. This is a fair assumption as the owner should be only deploying the contract unless we need to delegate the owner functions to another address, for which we will make provisions within the contract as we will see.

So if the contract is called ACLContract, the following code will instantiate the owner address and also make the owner as one of the users.

function ACLContract() {
    owner = msg.sender;
    users.push(owner);
}

 

Once we instantiate the contract, we will need some operation methods to administer the list of users. The msg is a special type of object in solidity that holds data about the caller or sender of a transaction. In this case, the deployer account will the caller of transaction that deploys the contract, so we will assign that owner address to the this caller’s address.

 

function addUser(address user){
       if (msg.sender != owner) throw;
       users.push(user);

}

   

function getIthUser(uint i) constant returns (address){
    return users[i];

}

   

function getUserCount() constant returns (uint){
       return users.length;

}

   

function deleteIthUser(uint i){
        if (msg.sender != owner) throw;
       delete users[i];

}

 

These methods will be used by clients to manage the user’s list. We see that only the owner will have the ability to administer the list with this check:

if (msg.sender != owner) throw;

So this code matches the caller address with the owner and fails the transactions with a throw in case there is a mismatch. Before we move further we need to look a bit at throwing exceptions as this is the main protection we will put in case our access control mechanism fails.

 

Exceptions:

Exceptions are the mechanism in solidity to indicate an error situation in a transaction. The result of an exception is that the current call is stopped, and all the effects of the transaction are reversed. If the exception occurs in a call stack, then the exception is returned up the stack to the first caller. Currently, it’s not possible to recover from the exception in solidity, as the partial state change that has occurred may not be safe to continue the transaction, so to maintain the atomicity of the transaction, all changes are reverted.

 

There are two kinds of exceptions, runtime exceptions, and user-provided exceptions.

Runtime exceptions occur automatically in case the code encounters the 12 situations listed in the solidity documentation (please refer to the documentation – too voluminous and redundant to paste here), those are mostly anomalies that might occur in the state of the statement/transaction being executed is successful.

 

User-provided exceptions are throwing manually in code. There are two ways to create them – using require with a condition what turns out to be false or using an explicit throw. We are using the conditional throw, and as we want this will revert the transaction effects and fail the call in case of violation of the access control that is put in place.

 

Coming back to our ACLContract, we have completed the creation of the list of users and the methods that we need to administer it. There are some caveats to what we did, it’s possible to create two lists – one for read-only users, and another one for reading and write users.

 

The fact is any data that is there in ethereum blockchain is public in nature, so its doesn’t make sense to create a read-only users, as anyone can see that data, but in case where the data access is encrypted in a way that allows it to be linked to user account then we can think of a read-only user. There are discussions on in the ethereum community to make ethereum blockchain privacy friendly, and there are multiple proposals on how that can be done.

 

If we can draw an assumption that with any of those proposals, or through a custom scheme we are able to encrypt our data and we are able to ensure that data access can only be done throughout contract code, then we will find it useful to  create two lists – a read-only user and a read-write user. The code for that will be very similar to what we have written for our single array users. We will just need four administration methods for each list (readUsers and writeUsers).

 

Creating access Control:

To vet a user against the access control list, we need to match it with the list of addresses. We can create a simple method that accepts a user address and tries to match it with the list:

function isUser(address candidate, string method) returns (bool){

  for(uint8 i = 0; i < users.length; i++){

            

            if (users[i] == candidate){               

              return true;

            }

         }      

      return false;

    }

 

The logic is simple to understand if we find the user in the list, we exit the loop with true, if we run out of elements in the list, then return false. The string method parameter that is passed is going to be used later when we talk about eventing to log the access of the user and the result of the access attempt.

 

Logging Access Using Events:

The basic concept defined in security principles is AAA (Authentication, Authorization, and Auditing).

The authentication is provided by ethereum when the user initiates a transaction with a certain account. It is assumed that the user owns the account as he is able to use it to initiate the transaction. The responsibility of that validation which is results in the authentication of the user with his account password is done at the ethereum node client level.

The Authorization part of the AAA has been implemented by the ACLContract where Role based access control is enforced.

The third part of AAA is Auditing- The requirement of auditing is that user access should be logged. To do this we will use Events.

 

Solidity Events:

Events are just like normal instance variables of a contract, they can be inherited like any variable. Events are used to return values to the client in case a list of values is to be returned, but we will only examine them as a means to create logs on the blockchain. Events when they are “fired” from a part of the blockchain with the parameters that are supplied to them.

 

To implement a log event we will define an Event variable:

event LogAccess(address indexed by, uint indexed accessTime, string method, string desc);

 

This event will be used in our access control method isUser() to log the access that is being attempted with its result.

 

function isUser(address candidate, string method) returns (bool){

        for(uint8 i = 0; i < users.length; i++){

            

            if (users[i] == candidate){

              LogAccess(candidate, now, method, "successful access");

              return true;

            }

         }

      LogAccess(candidate, now,method,  "failed access");

      return false;

    }

 

The event is parameterizing the accessing account (candidate) the time, (now) the resource (method) and the result (failed access, successful access). We are using a string to describe the result, but it’s recommended to use uint constants to enable easier search.

The event will be fired each time isUser() is accessed and will log the access on the blockchain for audit purposes.   The events can be accessed by a web3 client call which we will see in the following section.

Putting ACLContract to use:

The contract that we created can be inherited by any other contract to reuse the ACL lists provided by it. Our data contract was inheriting it using the construct.

 

contrat DataContract is ACLContract {



To use the ACL facility, we will be intercepting the method calls in the contract with the isUser() :



function updateCustomer(uint index, string name) {

if (isUser(msg.sender, "updateCustomer")) {

          if (index > count) throw;

           customers[index].name = name;

}

else throw;

}



function updateCustomerStatus(uint index, uint status) {

if (isUser(msg.sender, "updateCustomer")) {

       if (index > count) throw;

       customers[index].status = status;

        }  

else throw;

}



function createCustomer(

uint id,

string name,

uint dateOfBirth,

uint social){

       if (isUser(msg.sender, "createCustomer")) {

        customers[count] = Customer(id, name, dateOfBirth, social, pending);

        count++;

         }

else throw;

}

 

We are only supplying Access Control restrictions to methods that update the data, if we had encrypted the customer data within the contract, we could implement restriction on its read capabilities too.

 

The contract cannot access the events that it creates, so to access the events, we will need our web3 client to create a call:

 

Assuming the ABI has contracted ABI and address is its location on the blockchain.

var DataContract = web3.eth.contract(abi).at(address);

var eventLogs = DataContract.allEvents().get();

console.log(eventLogs);

There is a lot more to events than how we are using it above, our object is to retrieve them for auditing purposes.

 

Attribute-based Access Control:

It’s possible to have another type of access control where authorized users are identified by an attribute. We are discussing this to exemplify the “Attribute-based access control” we talked about in the introduction.  This is just a variation of the isUser() call:

function isUserAuthorized(address candidate, string method) returns (bool){

        for(uint8 i = 0; i < users.length; i++){

            

            if (candidate.balance > 100){

              LogAccess(msg.sender, now, method, "successful access");

              return true;

            }

         }

      LogAccess(msg.sender, now,method,  "failed access");

      return false;

    }

 

This is the very trivial example where an authorized user is the one who has a balance ethers greater than 100.

 

Join over 115,115 Members
Angel Investors, Startups & Blockchain developers...
Yes, Get Access!
No Thanks!