Waffle Recipes

How-To's and Tutorials for the ActiveWAFL Framework

How to Secure Model Read/Write Operations

Posted Nov 25, 2014

ActiveWAFL provides methods on the client-side for loading and persisting data.
While this can be extremely convenient, it can also be a security risk.

By default, any web client can connect to your application and issue requests to write data to your database.
However, you can leverage ActiveWAFL Model Instance Security, Model Property Security, and/or Client-Side Model Access Security to authenticate those requests before acting on them.

All models have a protected field named $instantiatedFromClientSide that can be used to determine if the current model originated from a client-side call or not.
Server-side instantiated models are inherently more secure than their client-side counterparts because the user cannot perform server-side instantiations directly.

Model Instance Security

All models have a method named DoesCurrentUserHaveWriteAccess() which determines if the current user is allowed to insert/update/delete that instance of the model.
To customize the logic that determines if the current user can write that particular model instance, you will need to override the DoesCurrentUserHaveWriteAccess() method for the model you wish to restrict write access to.

A typical implementation of DoesCurrentUserHaveWriteAccess() might check if the current user owns the model instance, and if so, grant them write access to the model.
Other implementations might be more complex, depending on the criteria that you are using to determine if the user has write-access to the model.

DoesCurrentUserHaveWriteAccess() Example on a Functional Model that represents a user's company. Each row in the 'Companies' table represents one company.
  1. namespace MyApp\FunctionalModel;
  2.  
  3. /**
  4.  * Company
  5.  * Represents a row in the database table Companies
  6.  */
  7. class Company
  8. extends \MyApp\DataModel\Company
  9. {
  10.     public function DoesCurrentUserHaveWriteAccess()
  11.     {
  12.         //allow write access if the current user is part of this company,
  13.         //or if this model originated from server-side application code
  14.  
  15.         if ($this->instantiatedFromClientSide)
  16.         {
  17.             $accessAllowed = \Wafl\Core::$CURRENT_USER->Get_CompanyId() == $this->_companyId;
  18.         } else {
  19.             $accessAllowed = true;
  20.         }
  21.  
  22.         return $accessAllowed;
  23.     }
  24. }

In the above code example, the application will never overwrite a row in the Companies table unless the current user is a member of that company, or if the application is trying to write the row from server-side code.
So, if an ill-intented person sent a request from the client to the application that, for example, changed the company's name, the request would be ignored.

There is also a method to restrict the reading of a model, DoesCurrentUserHaveReadAccess().

By default, both DoesCurrentUserHaveWriteAccess() and DoesCurrentUserHaveReadAccess() return true.

Model Property Security

All models have a method named CanCurrentUserSetProperty(string $propertyName) that determines if the current user has permission to set specific property values on the model.
You can override this method on specific models to implement custom property-setting restrictions.

For regular (non-persistant) Models the default value is always true.
For persistant models, the default value is always false when the Model is instantiated from client-side logic, and true when the Model is instantiated from server-side logic.

CanCurrentUserSetProperty() example on the 'Title' property of the hypothetical 'Company' model.
  1. namespace MyApp\FunctionalModel;
  2.  
  3. /**
  4.  * Company
  5.  * Represents a row in the database table Companies
  6.  */
  7. class Company
  8. extends \MyApp\DataModel\Company
  9. {
  10.     public function CanCurrentUserSetProperty($propertyName)
  11.     {
  12.         //Allow any user to set any property if instantiated from server-side logic.
  13.         //If instantiated from the client-side, only allow the user to set the 'Title' property.
  14.  
  15.         if (!$this->instantiatedFromClientSide || $propertyName == 'Title')
  16.         {
  17.             $accessAllowed = true;
  18.         } else {
  19.             $accessAllowed = false;
  20.         }
  21.  
  22.         return $accessAllowed;
  23.     }
  24. }

Client-Side Model Access Security

The ActiveWAFL framework exposes a global API for reading/writing model data.
This is the API that the client-side models use to load and/or persist themselves from the client-side when using the DblEj.Data.Model.LoadInstanceFromKey and DblEj.Data.Model.DeleteByKey static methods and the .SaveToServer() instance method.
You can add authentication routines to these operations, so that they will only execute if the user has permission to do so.

To specify the global authentication routines for intrinsic API model operations, you use the Wafl\Api\SetAuthenticateWriteAccessMethod and Wafl\Api\SetAuthenticateReadAccessMethod utility methods.

Each of these methods accepts a callback function as it's only paramter.
These callback functions will be called before every read/write operation.
If the callback returns false, the operation will not be allowed to continue.

The callback function that you specify should accept one argument, which will be a DblEj\Data\PersistableModel object.
Within your callback function, you can check the state of the PersistableModel and decide if the current user should be allowed to complete the request.

Function signatures for the API model read/write authentication callback functions
  1. function SomeCallbackFunction(DblEj\Data\PersistableModel $modelToBeReadOrWritten)

You can specify the callbacks from anywhere in your application.
If you want them setup prior to any model reading/writing via the API, a good place to do this is in the GlobalEventHandlers.php file.
Here is an example:

Example of setting custom model read/write authentication functions from within the GlobalEventHandlers file
  1. //After the app initializes, specify custom client-side model read/write authentication methods
  2. \Wafl\Core::$RUNNING_APPLICATION->AddHandler
  3. (
  4.     new \DblEj\EventHandling\DynamicEventHandler
  5.     (
  6.         \Wafl\Application\Application::EVENT_APPLICATION_AFTER_INITIALIZE,
  7.         function()
  8.         {
  9.             //setup client-side model load/save security
  10.             \Wafl\Api\DataModelHandlers::SetAuthenticateReadAccessMethod(
  11.                 function(\DblEj\Data\PersistableModel $model)
  12.                 {
  13.                     //always allow read access to all models via tha API,
  14.                     //unless there is separate authentication for individual model instances,
  15.                     //as described in the Model Instance Security section of this article.
  16.                     return true;
  17.                 });
  18.             \Wafl\Api\DataModelHandlers::SetAuthenticateWriteAccessMethod(
  19.                 function(\DblEj\Data\PersistableModel $model)
  20.                 {
  21.                     //only allow write access to the hypothetical UserRating model/table,
  22.                     //and only if it is new or if it is owned by the current user.
  23.                     if (is_a($model, "\\MyApp\\FunctionalModel\\UserRating"))
  24.                     {
  25.                         return (!$model->Get_UserRatingId() || ($model->Get_UserId() == \Wafl\Core::$CURRENT_USER->Get_UserId()))
  26.                     } else {
  27.                         return false;
  28.                     }
  29.                 });
  30.         }
  31.     )
  32. );

Conclusion

Utilizing the methods described in this article you are able to quickly setup simple security on ActiveWAFL's built-in client-side model read/write operations.