Novel use case scenario for traits enabling code-level user permissions
Size: 85.55 KB
Language: en
Added: Sep 10, 2015
Slides: 17 pages
Slide Content
PHP Traits Presented by Matthew Hellinger Buzz.Tools Co-founder
What is a Trait? Set of methods used across multiple classes Reduces limitations of single inheritance Introduced in PHP 5.4
Example Trait trait simpleTrait { public function common1() {echo ‘1’;} } class simpleClass { use simpleTrait ; } $test = new simpleClass (); $test->common1();
Standard Use Case trait EngineTools { // Convert engine temperature to Celsius function currentTempC () { return ($this-> engineTemp - 32) * (5/9); } } class Plane { use EngineTools ; private $ engineTemp ; function __construct() { $this-> startPlane (); } private function startPlane () { $this-> engineTemp = 210; } private function fly() { // Fly } } class Car { use EngineTools ; private $ engineTemp ; function __construct() { $this-> startCar (); } private function startCar () { $this-> engineTemp = 230; } private function drive() { // Drive } } $plane = new Plane(); $car = new Car(); echo $plane-> currentTempC () . "< br />"; echo $car-> currentTempC () . "< br />";
Why Traits Are Bad Potential namespace conflicts Trait changes alter multiple classes Methods spread outside class
Why Traits Are Good Avoids the “diamond problem” Simpler way to multiple inheritance Provides a new way to implement security*
Traits & Magic Methods Consider: __call & __ callStatic Executed when a method is “inaccessible” Let’s build a new trait
Permissions Trait // For an introduction to the concepts presented here, visit // http ://www.garfieldtech.com/blog/magical-php-call trait PermissionsTrait { private $ FeatureContext ; // Used to track feature this class is associated with private $ FeatureACL ; // Object reference to instantiated class function __ toString () { // Return whether user has access to class // return 'ALLOW' or 'DENY' if ($this-> FeatureACL -> allowed_class ($this-> FeatureContext , __CLASS__)) { return 'ALLOW'; } else { return 'DENY'; } } function __call($method, $ args ) { // Die if user doesn't have permission to access method // We could also log that this function was called if ( substr_count ($method, 0, 1) == ‘_’) { throw new Exception("Fatal error: Call to out of scope private method " . __CLASS__ . "::{$method }()"); } if ($this-> FeatureACL -> allowed_method ($this-> FeatureContext , __CLASS__, $method)) { if ( isset ($this->{$method}) && is_callable ($this->{$method})) { return call_user_func_array ($this->{$method}, $ args ); } else { throw new Exception("Fatal error: Call to undefined method " . __CLASS__ . "::{$method}()"); } } else { throw new Exception("Security error: Call to unauthorized method " . __CLASS__ . "::{$method}()"); } } function __ callStatic ($ method, $ args ) { // Same code as __call, but with static references // … } }
Need a Support Class class featureACL { private $array; // private to prevent later modification function __construct($ featureList_array ) { $this->array = $ featureList_array ; } function allowed_feature ($ feature_name ) { return in_array ($ feature_name , $this->array); } function allowed_class ($ feature_name , $ class_name ) { return array_key_exists ($ class_name , $this->array[$ feature_name ]); } function allowed_method ($ feature_name , $ class_name , $ method_name ) { if ( isset ($this->array[$ feature_name ][$class][$method])) { return true; } else { return false; } } }
Configure It All // ------- CONFIG FILE START ---------------------------- // The following feature list would be defined in a config file (for tracking in normal developer // workflow... means features get mapped to methods and classes in normal course of development $ FeatureList = array(); $ FeatureList ['Feature1'] = array( ' securedClass '=> array('method1', 'method2')); $ FeatureList [ 'Feature2'] = array( ' securedClass '=> array( 'method1', 'method3')); // overloaded // ------- CONFIG FILE END ---------------------------- // Open DB connection and get feature list accessible to client $ GroupFeatureList = array('Feature1', 'Feature2'); // All features client may access $ UserFeatureList = array( 'Feature2'); // All features a specific user may access // Note: the above is simulating the result of a DB call getting back an array (add your own DB logic) // We don't want user to have access to any feature the parent group doesn't have access to // so we compute intersection of the two arrays prior to storing $ FeatureACL = new featureACL ( array_intersect ($ GroupFeatureList , $ UserFeatureList )); // No longer modifiable unset ($ GroupFeatureList ); unset ($ UserFeatureList );
Create a Secure Class class securedClass { // No need to duplicate the same permissions magic for every class: use PermissionsTrait ; // By setting all methods private, they are inaccessible from outside class and therefore // pass through the __call magic method (the permission gatekeeper) private function method1($arg1, $arg2) { return ($arg1 + $arg2); } private function method2($arg1, $arg2) { return ($arg1 - $arg2); } private function method3($arg1, $arg2) { return ($arg1 * $arg2); } // To protect methods from being called from outside the class by using accessPermissions , // we prefix them with an underscore (_) a common convention for private method names private function _ internalMethod () { // This can't be called from __call } }
Attempt to access $ secureFns = new securedClass (); // Attempt to execute a non-permissible method: $result = $ secureFns ->method2(1,2); // Exception is thrown // These, however, would work: $result = $ secureFns -> method1(1,2); $result = $ secureFns -> method3(1,2);
What We Get Ability to lock down CODE execution to USER Ability to track EVERY method call Ability to track larger feature usage Self documenting functionality
Lessons Learned Traits are perfect for “universal” methods Namespace is an important consideration Look! We put magic methods to good use
Other Paths to Explore Method overloading (e.g. based on user type) Detailed logging (trace execution path) Dynamically generated methods
One Last Bit “ insteadof ” and “as” to avoid namespace conflicts class myTools { use screwDrivers , wrenches { screwDrivers :: returnTool insteadof wrenches; wrenches :: returnTool as returnWrench ; } }
Go build something secure! Matthew Hellinger [email protected] linkedin.com/in/ webspace