├── .env.dist ├── .gitignore ├── .travis.yml ├── README.md ├── bin └── setup.sh ├── composer.json ├── docs ├── authentication.md ├── groups.md ├── index.md ├── installation-and-configuration.md ├── password-reset.md ├── permissions.md ├── policies.md ├── providers.md ├── restrictions.md ├── security-questions.md ├── users.md └── working-with-objects.md ├── examples ├── README.md └── login-form.html ├── migrations ├── 20141211092209_create_user_table.php ├── 20141211204500_create_group_table.php ├── 20141211205817_create_group_user_table.php ├── 20141212101534_create_permissions_table.php ├── 20141213161408_create_user_permission_table.php ├── 20141214082627_create_group_permission_table.php ├── 20141216132116_create_throttle_table.php ├── 20150109213039_create_group_parent_xref.php ├── 20150111162554_create_permission_parent_xref.php ├── 20150124094757_fix_unique_indexes.php ├── 20150202164329_create_auth_token_table.php ├── 20150218151720_create_security_questions_table.php ├── 20150528223137_create_policy_table.php ├── 20150612202904_add_last_login_column.php ├── 20150702224804_add_permission_group_expire.php └── 20150703015048_add_user_permission_group_expire.php ├── mkdocs.yml ├── phinx.dist.yml ├── phpunit.xml ├── src └── Psecio │ └── Gatekeeper │ ├── AuthTokenCollection.php │ ├── AuthTokenModel.php │ ├── Collection │ └── Mysql.php │ ├── DataSource.php │ ├── DataSource │ ├── Mysql.php │ └── Stub.php │ ├── Exception │ ├── DataNotFoundException.php │ ├── GroupNotFoundException.php │ ├── InvalidExpressionException.php │ ├── ModelNotFoundException.php │ ├── PasswordResetInvalid.php │ ├── PasswordResetTimeout.php │ ├── PermissionNotFoundException.php │ ├── PolicyNotFoundException.php │ ├── RestrictionFailedException.php │ ├── ThrottleNotFoundException.php │ ├── UserInactiveException.php │ └── UserNotFoundException.php │ ├── Gatekeeper.php │ ├── GroupCollection.php │ ├── GroupModel.php │ ├── GroupParentModel.php │ ├── GroupPermissionModel.php │ ├── Handler.php │ ├── Handler │ ├── CloneInstance.php │ ├── Count.php │ ├── Create.php │ ├── Delete.php │ ├── FindBy.php │ └── Save.php │ ├── Model │ └── Mysql.php │ ├── PermissionCollection.php │ ├── PermissionModel.php │ ├── PermissionParentModel.php │ ├── PhinxMigration.php │ ├── PolicyCollection.php │ ├── PolicyModel.php │ ├── Provider │ ├── Laravel.php │ └── Laravel5 │ │ ├── AuthManager.php │ │ ├── AuthServiceProvider.php │ │ ├── UserAuthenticatable.php │ │ └── UserProvider.php │ ├── Resolve.php │ ├── Resolve │ └── Permissions.php │ ├── Restrict │ ├── Ip.php │ └── Throttle.php │ ├── Restriction.php │ ├── SecurityQuestionCollection.php │ ├── SecurityQuestionModel.php │ ├── Session │ └── RememberMe.php │ ├── ThrottleModel.php │ ├── UserCollection.php │ ├── UserGroupCollection.php │ ├── UserGroupModel.php │ ├── UserModel.php │ ├── UserPermissionCollection.php │ └── UserPermissionModel.php └── tests ├── Psecio └── Gatekeeper │ ├── Base.php │ ├── DataSource │ └── MysqlTest.php │ ├── DataSourceTest.php │ ├── GatekeeperTest.php │ ├── GroupCollectionTest.php │ ├── GroupModelTest.php │ ├── MockModel.php │ ├── MockPdo.php │ ├── PermissionCollectionTest.php │ ├── PermissionModelTest.php │ ├── PolicyCollectionTest.php │ ├── PolicyModelTest.php │ ├── Restrict │ └── IpTest.php │ ├── SecurityQuestionCollectionTest.php │ ├── Session │ └── RememberMeTest.php │ ├── ThrottleModelTest.php │ ├── UserCollectionTest.php │ ├── UserGroupCollectionTest.php │ ├── UserModelTest.php │ └── UserPermissionCollectionTest.php └── bootstrap.php /.env.dist: -------------------------------------------------------------------------------- 1 | DB_USER=%%USERNAME%% 2 | DB_PASS=%%PASSWORD%% 3 | DB_HOST=%%HOSTNAME%% 4 | DB_NAME=%%DBNAME%% -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer 2 | vendor 3 | composer.phar 4 | composer.lock 5 | 6 | # IntelliJ - PhpStorm and PyCharm 7 | .idea 8 | *.ipr 9 | *.iws 10 | 11 | # Logs 12 | logs 13 | error.log 14 | access.log 15 | 16 | # Netbeans 17 | nbproject 18 | .nbproject 19 | .nbproject/* 20 | nbproject/* 21 | nbproject/private/ 22 | build/ 23 | nbbuild/ 24 | dist/ 25 | nbdist/ 26 | nbactions.xml 27 | nb-configuration.xml 28 | 29 | # Mac OSX 30 | .DS_Store 31 | # Thumbnails 32 | ._* 33 | # Files that might appear on external disk 34 | .Spotlight-V100 35 | .Trashes 36 | 37 | # SublimeText project files 38 | /*.sublime-project 39 | *.sublime-workspace 40 | 41 | .env -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | before_script: 4 | - wget http://getcomposer.org/composer.phar 5 | - php composer.phar install --dev 6 | php: 7 | - 5.4 8 | - 7.0 9 | script: phpunit 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gatekeeper: An Authentication & Authorization Library 2 | ========== 3 | 4 | [![Travis-CI Build Status](https://secure.travis-ci.org/psecio/gatekeeper.png?branch=master)](http://travis-ci.org/psecio/gatekeeper) 5 | [![Code Climate](https://codeclimate.com/github/psecio/gatekeeper/badges/gpa.svg)](https://codeclimate.com/github/psecio/gatekeeper) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/psecio/gatekeeper.svg?style=flat-square)](https://packagist.org/packages/psecio/gatekeeper) 7 | 8 | The Gatekeeper library is a simple drop-in library that can be used to manage users, permissions and groups for your application. The goal is to make securing your application as simple as possible while still providing a solid and secure foundation to base your user system around. 9 | 10 | Gatekeeper is best classified as a Role-Base Access Control (RBAC) system with users, groups and permissions. It is framework-agnostic and is set up to use its own database for the user handling. 11 | 12 | **More Information:** For more information on the library, please see [the official project documentation](http://gatekeeper-auth.readthedocs.org/en/latest/). 13 | -------------------------------------------------------------------------------- /bin/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -e "--- Executing Gatekeeper setup ----------\n" 4 | YAML='phinx.yml' 5 | 6 | # pre1. Check to see if the yaml file exists 7 | if [ -r $YAML ]; 8 | then 9 | message="\033[31m$YAML file found, running migrations\n\033[0m" 10 | echo -e $message 11 | vendor/bin/phinx migrate 12 | exit 13 | fi 14 | 15 | # 1. ask for the database info 16 | echo -e "> No configuration found, please enter database information:\n" 17 | read -p "Hostname [localhost]: " hostname 18 | if [ -z $hostname ]; then 19 | hostname="localhost" 20 | fi 21 | 22 | read -p "Database name [gatekeeper]: " dbname 23 | if [ -z $dbname ]; then 24 | dbname="gatekeeper" 25 | fi 26 | 27 | read -p "Username: " username 28 | if [ -z $username ]; then 29 | echo -e "\033[31mUsername cannot be empty!\033[0m" 30 | exit; 31 | fi 32 | 33 | read -sp "Password: " password 34 | if [ -z $password ]; then 35 | echo -e "\n\033[31mPassword cannot be empty!\033[0m" 36 | exit; 37 | else 38 | echo -e "\n" # extra newline 39 | fi 40 | 41 | 42 | # 2. verify it can be reached 43 | echo -e "--- Testing database connection ----------\n" 44 | 45 | RESULT=`mysql -u $username --password=$password -e "show databases" 2>/dev/null | grep "$dbname"` 46 | 47 | if [ "$RESULT" != "$dbname" ]; then 48 | echo -e "\033[31mMySQL connection failure!\n\033[0m" 49 | echo -e "Please verify the following: 50 | - The username/password you provided are correct 51 | - That the database has been created 52 | - That the user you're providing has been correctly granted permission to the database 53 | " 54 | exit; 55 | fi 56 | 57 | echo -e "--- Setting up configuration ----------\n" 58 | 59 | # Our connection details are good, lets copy the file 60 | cp ./vendor/psecio/gatekeeper/phinx.dist.yml ./phinx.yml 61 | 62 | # And make our replacements for phinx 63 | sed -i -e "s/%%DBNAME%%/$dbname/g" ./phinx.yml 64 | sed -i -e "s/%%HOSTNAME%%/$hostname/g" ./phinx.yml 65 | sed -i -e "s/%%USERNAME%%/$username/g" ./phinx.yml 66 | sed -i -e "s/%%PASSWORD%%/$password/g" ./phinx.yml 67 | rm ./phinx.yml-e 68 | 69 | # Now lets move the .env file into place. If it exists, append 70 | if [ -f ./.env ]; then 71 | sed -i '' -e '$a\' ./.env 72 | cat ./vendor/psecio/gatekeeper/.env.dist >> ./.env 73 | else 74 | cp ./vendor/psecio/gatekeeper/.env.dist ./.env 75 | fi 76 | 77 | # And make the replacements here too 78 | sed -i -e "s/%%DBNAME%%/$dbname/g" ./.env 79 | sed -i -e "s/%%HOSTNAME%%/$hostname/g" ./.env 80 | sed -i -e "s/%%USERNAME%%/$username/g" ./.env 81 | sed -i -e "s/%%PASSWORD%%/$password/g" ./.env 82 | rm ./.env-e 83 | 84 | echo -e "--- Running migrations ----------\n" 85 | 86 | # Finally we run the migrations 87 | vendor/bin/phinx migrate 88 | 89 | echo -e "DONE!\n\n" 90 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"psecio/gatekeeper", 3 | "type":"library", 4 | "description":"A simple PHP authentication and authorization library", 5 | "keywords":["authentication", "authorization"], 6 | "homepage":"https://github.com/psecio/gatekeeper.git", 7 | "license":"MIT", 8 | "authors":[ 9 | { 10 | "name":"Chris Cornutt", 11 | "email":"ccornutt@phpdeveloper.org", 12 | "homepage":"http://www.phpdeveloper.org/" 13 | } 14 | ], 15 | "require":{ 16 | "php":">=5.4.0", 17 | "enygma/modler": "2.*", 18 | "robmorgan/phinx": "*", 19 | "ircmaxell/password-compat": "^1.0.4", 20 | "ircmaxell/random-lib": "^1.1.0", 21 | "vlucas/phpdotenv": "~2", 22 | "symfony/expression-language": "^2.5", 23 | "monolog/monolog": "^1.13" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "4.1.4" 27 | }, 28 | "autoload": { 29 | "psr-0": { 30 | "Psecio": "src/" 31 | } 32 | }, 33 | "bin": ["bin/setup.sh"] 34 | } 35 | -------------------------------------------------------------------------------- /docs/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | One of the main features of the library is validating a `username` and `password` combination against a current user record. Is it achieved with the `authenticate` method: 4 | 5 | ```php 6 | 'ccornutt', 9 | 'password' => 'valid-password' 10 | ); 11 | if (Gatekeeper::authenticate($credentials) == true) { 12 | echo 'valid!'; 13 | } 14 | ?> 15 | ``` 16 | 17 | ## Remember Me 18 | 19 | In most applications there's a concept of session lasting longer than just one login. It's common to see apps allowing a "Remember Me" kind of handling and Gatekeeper includes this functionality in a simple, easy to use way. There's two functions in the main `Gatekeeper` class that take care of the hard work for you: 20 | 21 | ```php 22 | username; 34 | } 35 | 36 | ?> 37 | ``` 38 | 39 | Using the `checkRememberMe` method, you can automatically verify the existence of the necessary cookie values and return the user they match. The default timeout for the "remember me" cookies is **14 days**. This can be changed by passing in an `interval` configuration option when the `rememberMe` function is called: 40 | 41 | ``` 42 | '+4 weeks' 47 | ); 48 | if (Gatekeeper::rememberMe($user, $config) === true) { 49 | echo 'this user is now remembered for 14 days!'; 50 | } 51 | 52 | ?> 53 | ``` 54 | 55 | The `interval` format here is any supported by the [PHP DateTime handling](http://php.net/manual/en/datetime.formats.php) in the constructor. 56 | 57 | > **NOTE:** As the "remember me" handling uses cookies to store the token, use of this feature should happen before any other content is output to the page. This is a limitation with how cookies must be set (as headers). 58 | 59 | #### Remember Me & Authentication 60 | 61 | In addition to the more manual handling of the "remember me" functionality above, you can also have the `authenicate` method kick off the process when the user successfully authenticates with a second optional parameter: 62 | 63 | ``` 64 | 'ccornutt', 67 | 'password' => 'valid-password' 68 | ); 69 | if (Gatekeeper::authenticate($credentials, true) == true) { 70 | echo 'valid!'; 71 | } 72 | ?> 73 | ``` 74 | 75 | The only difference here is that second parameter, the `true`, that is a switch to turn on the "remember" handling. By default this is disabled, so if you want to use this automatically, you'll need to enable it here. With that enabled, you can then use the `checkRememberMe` method mentioned above to get the user that matches the token. 76 | -------------------------------------------------------------------------------- /docs/groups.md: -------------------------------------------------------------------------------- 1 | # Groups 2 | 3 | Groups are represented as objects in the Gatekeeper system with the following properties: 4 | 5 | - description 6 | - name 7 | - id 8 | - created 9 | - updated 10 | - users (relational) 11 | 12 | Gatekeeper also supports hierarchical groups (see below). 13 | 14 | ## Getting All Groups 15 | 16 | You can use the `findGroupss` method on the `Gatekeeper` class to get a list (returns a `GroupCollection`) of the current groups: 17 | 18 | ```php 19 | $groups = Gatekeeper::findGroups(); 20 | 21 | // You can then slice it up how you need, like getting the first three 22 | $shortGroupList = $groups->slice(1, 3); 23 | ``` 24 | 25 | ## Creating a Group 26 | 27 | Making a new group is as easy as making a new user. One thing to note, the *group name* must be **unique**: 28 | 29 | ```php 30 | 'group1', 33 | 'description' => 'Group #1' 34 | ); 35 | Gatekeeper::createGroup($attrs); 36 | ?> 37 | ``` 38 | 39 | You can also create a group with an expiration timeout, allowing users in that group only a certain timeframe for their access. You use the `expires` value on the creation to set this with a Unix timestamp: 40 | 41 | ```php 42 | 'group1', 45 | 'description' => 'Group #1', 46 | 'expires' => strtotime('+1 day') 47 | ); 48 | Gatekeeper::createGroup($attrs); 49 | ?> 50 | ``` 51 | 52 | You can then check to see if a group has expired with the `isExpired` method: 53 | 54 | ```php 55 | 61 | ``` 62 | 63 | ## Getting Group Users 64 | 65 | Much like you can easily get the groups the user belongs to, you can also get the members of a group. This will return a collection of user objects: 66 | 67 | ```php 68 | users; 70 | 71 | foreach ($users as $user) { 72 | echo 'Username: '.$user->username."\n"; 73 | } 74 | ?> 75 | ``` 76 | 77 | ## Adding a user to a group 78 | 79 | You can add a user to a group by giving the `addUser` method one of two kinds of inputs: 80 | 81 | ```php 82 | addUser(1); 85 | 86 | // Or a user model, it will extract the ID 87 | $user = new UserModel(); 88 | Gatekeeper::findGroupById(1)->addUser($user); 89 | ?> 90 | ``` 91 | 92 | ## Removing a user from a group 93 | 94 | You can remove a user from a group in much the same way, either by an ID or a User model instance with the `removeUser` method: 95 | 96 | ``` 97 | removeUser(1); 100 | 101 | // Or a user model, it will extract the ID 102 | $user = new UserModel(); 103 | Gatekeeper::findGroupById(1)->removeUser($user); 104 | ?> 105 | ``` 106 | 107 | ## Checking to see if a user is in a group 108 | 109 | You can use the `inGroup` method to check and see if a user ID is in a group: 110 | 111 | ```php 112 | inGroup($userId) === true) { 115 | echo 'User is in the group!'; 116 | } 117 | ?> 118 | ``` 119 | 120 | ## Adding a permission to a group 121 | 122 | You can add permissions to groups too. These are related to the groups, not the users directly, so if you get the permissions for a user, these will not show in the list. 123 | 124 | ```php 125 | addPermission($permId); 128 | 129 | // Or you can use a PermissionModel object 130 | $permission = Gatekeeper::findPermissionById(1); 131 | Gatekeeper::findGroupById(1)->addPermission($permission); 132 | ?> 133 | ``` 134 | 135 | ## Removing a permission from a group 136 | 137 | A permission can be removed from a group in the same way a user can, just with the `removePermission` method: 138 | 139 | ``` 140 | removePermission($permId); 143 | 144 | // Or you can use a PermissionModel object 145 | $permission = Gatekeeper::findPermissionById(1); 146 | Gatekeeper::findGroupById(1)->removePermission($permission); 147 | ?> 148 | ``` 149 | 150 | ## Getting the permissions associated with the group 151 | 152 | Since groups can have permissions attached, you can fetch those through the `permissions` property much in the same way you can for users: 153 | 154 | ```php 155 | permissions; 157 | foreach ($permissions as $permission) { 158 | echo 'Description: '.$permission->description."\n"; 159 | } 160 | ?> 161 | ``` 162 | 163 | ## Hierarchical Groups 164 | 165 | Groups can also be added as children of other groups to help make categorizing easier. They can either be added by ID or model instance: 166 | 167 | ```php 168 | addChild(2); 171 | // or 172 | $childGroup = Gatekeeper::findGroupById(2); 173 | $group->addChild($childGroup); 174 | ?> 175 | ``` 176 | 177 | You can find the child groups using the `children` property from a group instance: 178 | 179 | ``` 180 | children; 182 | ?> 183 | ``` 184 | 185 | You can also remove child groups similarly: 186 | 187 | ``` 188 | removeChild(2); 193 | // or by model instance 194 | $group2 = Gatekeeper::findGroupById(2); 195 | $group->removeChild($group2); 196 | ?> 197 | ``` 198 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # **Gatekeeper:** An Authentication & Authorization Library 2 | 3 | ## Introduction 4 | 5 | **[Find Gatekeeper on GitHub](https://github.com/psecio/gatekeeper)** 6 | 7 | The [Gatekeeper](https://github.com/psecio/gatekeeper) library is a simple drop-in library that can be used to manage users, permissions and groups for 8 | your application. The goal is to make securing your application as simple as possible while still providing a solid and 9 | secure foundation to base your user system around. 10 | 11 | *Gatekeeper* is best classified as a Role-Base Access Control (RBAC) system with users, groups and permissions. It is 12 | framework-agnostic and is set up to use its own database for the user handling. 13 | 14 | *Gatekeeper* provides a standard NIST Level 2 Role Based Access Control system that also includes support for child permissions and child groups supported in a tree structure. 15 | 16 | ## Contact 17 | 18 | If you have any questions or suggestions about this library, please let me know by adding an issue [in the Gatekeeper Issues list](https://github.com/psecio/gatekeeper/issues) on GitHub. 19 | 20 | Thanks! I hope you find *Gatekeeper* useful! 21 | 22 | Chris Cornutt 23 | -------------------------------------------------------------------------------- /docs/installation-and-configuration.md: -------------------------------------------------------------------------------- 1 | # Installation & Configuration 2 | 3 | ## Installation 4 | 5 | The best way to install Gatekeeper is through Composer (though I suppose you could clone the repo if you want to do it the hard way). Use the following command to add it to your current project: 6 | 7 | ``` 8 | composer require psecio/gatekeeper 9 | ``` 10 | 11 | ## Dependencies 12 | 13 | *Gatekeeper* makes use of several other PHP dependencies to help reduce code duplication: 14 | 15 | - [Modler](http://github.com/enygma/modler) 16 | - [Phinx](http://github.com/robmorgan/phinx) 17 | - [password_compat](http://github.com/ircmaxell/password-compat) 18 | - [phpdotenv](http://github.com/vlucas/phpdotenv) 19 | 20 | 21 | ## Setup Quick Start 22 | 23 | There's a "quick start" method for getting Gatekeeper up and running in two steps: 24 | 25 | 1. Create your Gatekeeper database and user: 26 | 27 | ``` 28 | create database gatekeeper; 29 | CREATE USER 'gk-user'@'localhost' IDENTIFIED BY 'some-password-here'; 30 | grant all on gatekeeper.* to 'gk-user'@'localhost'; 31 | flush privileges; 32 | ``` 33 | 34 | 2. Execute the `vendor/bin/setup.sh` file. This script will ask several questions about your database setup, write the needed files and run the migrations for you. 35 | 36 | That's it - you're all done! 37 | 38 | 39 | ## You're ready to go! 40 | 41 | You can now start using the *Gatekeeper* functionality in your application. You only need to call the `init` function to set 42 | up the connection and get the instance configured: 43 | 44 | ```php 45 | 51 | ``` 52 | 53 | ## Configuration options 54 | 55 | You can pass in options to the `init` call if you don't necesarily want to use the `.env` configuration file handling. There's a few options: 56 | 57 | ```php 58 | 'mysql', 65 | 'username' => 'gatekeeper-user', 66 | 'password' => 'gatekeeper-pass', 67 | 'name' => 'gatekeeper', 68 | 'host' => 'gatekeeper-db.localhost' 69 | ); 70 | Gatekeeper::init(null, $config); 71 | ?> 72 | ``` 73 | 74 | ## Throttling 75 | 76 | By default Gatekeeper will have throttling enabled. This means that, on incorrect login by a user, the information will be logged. When they hit a threshold (defaults to 5) in a certain amount of time (default of 1 minute) they'll be marked as blocked and will not be allowed to log in. 77 | 78 | You can disable this feature in one of two ways: 79 | 80 | ```php 81 | false)); 84 | 85 | // Or through a method call 86 | Gatekeeper::disableThrottle(); 87 | 88 | // And to reenable 89 | Gatekeeper::enableThrottle(); 90 | ?> 91 | ``` 92 | 93 | You can also check the status of the throttling: 94 | 95 | ```php 96 | 101 | ``` 102 | 103 | -------------------------------------------------------------------------------- /docs/password-reset.md: -------------------------------------------------------------------------------- 1 | # Password Reset Handling 2 | 3 | *Gatekeeper* also includes some password reset handling functionality. It doesn't try to send an email or output a web page 4 | with the functionality. Instead, it provides methods to generate and validate a unique code. When the code is generated, it is 5 | added into the user's record and stored for evaluation. 6 | 7 | The code will expire in *one hour* from the time it was generated. 8 | 9 | ```php 10 | getResetPasswordCode(); 13 | 14 | echo 'Your password reset code is: '.$code."\n"; 15 | 16 | // Now lets verify it... 17 | $code = $_GET['code']; 18 | if ($user->checkResetPasswordCode($code) === true) { 19 | echo 'valid!'; 20 | } 21 | ?> 22 | ``` 23 | 24 | If the code is valid, it and the timeout are cleared from the user's record. -------------------------------------------------------------------------------- /docs/permissions.md: -------------------------------------------------------------------------------- 1 | # Permissions 2 | 3 | The system supports the concept of *permissions*, a common part of a role-based access control system. In the Gatekeeper 4 | system the permissions have these properties: 5 | 6 | - id 7 | - name 8 | - description 9 | - created date 10 | - updated date 11 | 12 | ## Creating a permission 13 | 14 | When creating a permission, you need to specify a name and description value. The `name` must be unique: 15 | 16 | ```php 17 | 'perm1', 20 | 'description' => 'Permission #1' 21 | ); 22 | if (Gatekeeper::createPermission($perm) === true) { 23 | echo 'Permission created successfully!'; 24 | } 25 | ?> 26 | ``` 27 | 28 | You can also set an expiration date on your permissions using the `expire` property: 29 | 30 | ```php 31 | 'perm1', 34 | 'description' => 'Permission #1', 35 | 'expire' => strtotime('+1 day') 36 | ]; 37 | ?> 38 | ``` 39 | 40 | These values are stored as Unix timestamps on the permission records themselves. This will cause the permission to exire, **not** the permission to no longer be allowed for a user (that's in the user-to-permission relationship). You can also check to see if a permission is expired with the `isExpired` method: 41 | 42 | ```php 43 | isExpired() === true) { 46 | echo 'Oh noes, the permission expired!'; 47 | } 48 | ?> 49 | ``` 50 | 51 | You can also update the expiration time directly when you have a permission object in hand: 52 | 53 | ```php 54 | expire = strtotime('+1 month'); 57 | $permission->save(); 58 | ?> 59 | ``` 60 | 61 | ## Adding Child Permissions 62 | 63 | Much like groups, permissions also support the concept of children. Adding a permission as a child to a parent is easy and can be done in one of two ways: 64 | 65 | ``` 66 | addChild(1); 71 | // or with another model instance 72 | $permission2 = Gatekeeper::findPermissionById(2); 73 | $permission->addChild($permission); 74 | ?> 75 | ``` 76 | 77 | ## Removing Child Permissions 78 | 79 | You can also remove child permissions in a similar way: 80 | 81 | ``` 82 | removeChild(2); 87 | // Or by model instance 88 | $permission2 = Gatekeeper::findPermissionById(1); 89 | $permission1->removeChild($permission2); 90 | ?> 91 | ``` 92 | 93 | ## Finding child permissions 94 | 95 | If you want to find the permissions that are children of the current instance, you can use the `children` property: 96 | 97 | ``` 98 | children; 102 | ?> 103 | ``` 104 | 105 | This will return an array of permission objects representing the children. -------------------------------------------------------------------------------- /docs/policies.md: -------------------------------------------------------------------------------- 1 | # Policies 2 | 3 | Policies are one of the more complex parts of the Gatekeeper system. While most evaluations only need to check a user for groups or permissions, some things are a bit more complicated. That's where *policies* come in. A **policy** is a more complex evaluation that happens based on given objects and an *expression* to determine a pass or fail status. 4 | 5 | Let's see an example of how we could translate an existing check for a permission into a policy. First off, here's how to check the permission: 6 | 7 | ```php 8 | hasPermission('permission1')) { 13 | echo 'They have the permission! Score!'; 14 | } 15 | 16 | ?> 17 | ``` 18 | 19 | This is a pretty simple example, but I wanted to start with the basics to show the use of expressions. So, lets translate this into a policy that can be reused across the whole system easily. The key to the policies lies in the use of the [Symfony Expression Language](http://symfony.com/doc/current/components/expression_language/syntax.html) syntax, a well-documented standards in use for configuring the Symfony framework. 20 | 21 | Here's how to translate the same evaluation into a policy: 22 | 23 | ```php 24 | 'perm1-test', 28 | 'expression' => '"permission1" in user.permissions.getName()', 29 | 'description' => 'See if a user has "permission1"' 30 | )); 31 | 32 | // Now, we need to evaluate the user against the policy 33 | if (Gatekeeper::evaluatePolicy('perm1-test', $user) === true) { 34 | echo 'They have the permission! Rock on!'; 35 | } 36 | 37 | ?> 38 | ``` 39 | 40 | Once we've created this policy then we only need to use the `evaluatePolicy` check with the name and data to perform the check. This will return a boolean `true` or `false` based on the evaluation. This makes it easy to reuse the same logic all over your application. In this example Gatekeeper knows to translate the `UserModel` object into the `user` the expression is looking for. You could could also define multiple objects for reference in the expression by passing in an array of objects, either with indexes or without. For example: 41 | 42 | ```php 43 | 'perm-group-test', 50 | 'expression' => '"permission1" in user.permissions.getName() and group.name = "test1"', 51 | 'description' => 'See if a user has "permission1" and the group is named "group1"' 52 | )); 53 | 54 | $data = [ $user, $group ]; 55 | if (Gatekeeper::evaluatePolicy('perm-group-test', $data) === true) { 56 | echo "They're good - move along..."; 57 | } 58 | 59 | ?> 60 | ``` 61 | 62 | In this example, Gatekeeper is once again resolving the `UserModel` and `GroupModel` objects to `user` and `group` respectively (based on the class names). You can also define what you want the objects to be named for reference in the expression by providing indexes: 63 | 64 | ```php 65 | $user, 69 | 'group' => $group 70 | ]; 71 | 72 | ?> 73 | ``` 74 | 75 | Additionally, if you ever have the need to evaluate a policy directly, you can do so with the `evaluate` method on the `PolicyModel` object: 76 | 77 | ```php 78 | evaluate($user) === true) { 82 | echo "Awesome, they're good to go!"; 83 | } 84 | 85 | ?> 86 | ``` 87 | 88 | ## Using Closures as Policies 89 | 90 | You can also use the same structure of you'd like to define policies as closures, letting you do a bit more programmatic evaluation of the data provided. The same rules apply above to how they're evaluated, you just define them differently: 91 | 92 | ```php 93 | 'eval-closure', 97 | 'description' => 'Using a closure to validate policy', 98 | 'expression' => function($data) { 99 | return ($data->username === 'ccornutt'); 100 | }; 101 | ]); 102 | 103 | if (Gatekeeper::evaluatePolicy('eval-closure', $user) === true) { 104 | echo "Sweet success!"; 105 | } 106 | ?> 107 | ``` 108 | 109 | Here we've define the check for a `username` match as a closure and run an evaluation on it. 110 | 111 | > **NOTE:** Any checks that happen to have the same name as a policy that lives in the database will be overridden by a closure-based check (which may be advantageous depending on your needs). 112 | 113 | -------------------------------------------------------------------------------- /docs/providers.md: -------------------------------------------------------------------------------- 1 | # Providers 2 | 3 | ## Laravel 5 4 | 5 | All the files you'll need to use Gatekeeper as an authentication provider with Laravel 5 are included. These instructions assume you've already followed the Gatekeeper installation instructions and things there are working. Here's how to set it up: 6 | 7 | 1. You'll then need to add the Auth provider to be loaded. Update your `app\config\app.php` and add this to your `providers` list: 8 | 9 | ```php 10 | \Psecio\Gatekeeper\Provider\Laravel5\AuthServiceProvider::class 11 | ``` 12 | 13 | 2. Update your `app\config\auth.php` settings to change the "driver" setting to "gatekeeper": 14 | 15 | ```php 16 | 'driver' => 'gatekeeper' 17 | ``` 18 | 19 | 3. Add the Gatekeeper configuration to your `.env` file for the Laravel application: 20 | 21 | ```php 22 | GATEKEEPER_USER=gk42 23 | GATEKEEPER_PASS=gk42 24 | GATEKEEPER_HOST=127.0.0.1 25 | GATEKEEPER_DATABASE=gatekeeper 26 | ``` 27 | 28 | **This information is just an example**, so be sure you fill in your actual information here. 29 | 30 | That's it - you should be all set to use the standard Laravel authentication handling and it will use Gatekeeper behind the scenes. 31 | 32 | 33 | ## Laravel 4 34 | 35 | **NOTE:** The current Laravel support is for 4.x based versions. 36 | 37 | A Laravel authentication provider is included with the Gatekeeper package in `Psecio\Gatekeeper\Provider\Laravel`. 38 | It's easy to add into your Laravel application and seamlessly works with the framework's `Auth` handling. 39 | 40 | **Step 1:** Add the database configuration into your `app/config/database.php` file: 41 | 42 | ``` 43 | 'gatekeeper' => array( 44 | 'driver' => 'mysql', 45 | 'host' => 'localhost', 46 | 'database' => 'gatekeeper', 47 | 'username' => 'your-username', 48 | 'password' => 'your-password', 49 | 'charset' => 'utf8', 50 | 'collation' => 'utf8_unicode_ci', 51 | 'prefix' => '', 52 | ) 53 | ``` 54 | 55 | **Step 2:** In the `app/start/global.php` file, add the following to inject the provider and make it available: 56 | 57 | ```php 58 | 65 | ``` 66 | 67 | **Step 3:** Finally, in your `app/config/auth.php` file, change the `driver` value to "gatekeeper": 68 | 69 | ```php 70 | 'driver' => 'gatekeeper' 71 | ``` -------------------------------------------------------------------------------- /docs/restrictions.md: -------------------------------------------------------------------------------- 1 | # Restrictions 2 | 3 | You can place restrictions on the authentication of your users via Gatekeeper. They can be added with the `restrict` method on the 4 | main Gatekeeper class. For example, if we want to add IP-based restrictions: 5 | 6 | ```php 7 | '127.*' 10 | )); 11 | ``` 12 | 13 | This restriction is then added to the set that is evaluated on authentication. If any of the checks fail, the authentication is 14 | stopped and a `\Psecio\Gatekeeper\Exception\RestrictionFailedException` is thrown. 15 | 16 | ## Restriction Evaluation 17 | 18 | Restrictions are currently only evaluated on user login (with the `authenticate` method). 19 | 20 | ## IP Restriction 21 | 22 | You can allow or deny users based on their `REMOTE_ADDR` value when they try to access the application. Here's a simple set up to 23 | deny users from localhost (127.0.0.1): 24 | 25 | ``` 26 | '127.*' 29 | )); 30 | ?> 31 | ``` 32 | 33 | In this example, we're setting a `DENY` check for anything in the `127.*` range (so, localhost). The `*` (asterisk) operates as a 34 | wildcard character and can be used to replace any number set in the IPv4 format. So, you can use it like: 35 | 36 | - 127.* 37 | - 192.168.1.* 38 | - 192.*.1.100 39 | 40 | You can also set up more complex rules with the `ALLOW` check too: 41 | 42 | ``` 43 | '127.*', 46 | 'ALLOW' => '145.12.14.*' 47 | )); 48 | ?> 49 | ``` 50 | 51 | In this example we're both denying anything from localhost and only allowing things matching the `145.12.14.*` pattern. 52 | 53 | **NOTE:** The `ALLOW` and `DENY` restrictions will be evaluated if they exist. So, you can either: just use `DENY`, just use `ALLOw` or combine them into something more complex. If you have a pattern that matches the current IP in both, it will fail closed with a `DENY`. -------------------------------------------------------------------------------- /docs/security-questions.md: -------------------------------------------------------------------------------- 1 | # Security Questions 2 | 3 | Gatekeeper includes the concept of security questions to act as a secondary mechanism for authenticating the user. Instead of trying to provide a set of questions with the installation, the tool only provides the functionality to create and verify 4 | the answers. 5 | 6 | The answers for the questions are stored as `bcrypt` strings instead of in plain-text to prevent simple exposure if the database is compromised. It currently uses the [password hashing](http://php.net/manual/en/ref.password.php) handling in PHP for hash creation and verification. It evaluates the hashes directly and, as such, the answer is *case sensitive* and must match the answer exactly. 7 | 8 | Additionally, Gatekeeper also prevents the user from providing an answer that's the same as their current password. 9 | 10 | ## Adding a question 11 | 12 | To add a security question for a user, you'll need to first find the user then call the `addSecurityQuestion` method on that user object: 13 | 14 | ```php 15 | addSecurityQuestion(array( 18 | 'question' => 'What...is your favorite color?', 19 | 'answer' => 'Blue...no, yellow!' 20 | )); 21 | 22 | if ($result === true) { 23 | echo 'Question added successfully'; 24 | } 25 | 26 | ?> 27 | ``` 28 | 29 | ## Getting a user's questions and answers 30 | 31 | You can get the list of questions for a user by using the `securityQuestions` property: 32 | 33 | ```php 34 | securityQuestions; 39 | ?> 40 | ``` 41 | 42 | ## Validating the answer given 43 | 44 | You can use the `verifyAnswer` method on the `SecurityQuestionModel` object to verify the answer to the given question. For example, we can pull the questions and check to be sure the answer to the first one is correct: 45 | 46 | ```php 47 | securityQuestions; 49 | $answer = "this is my answer that's correct"; 50 | 51 | if ($questions[0]->verifyAnswer($answer) === true) { 52 | echo 'The answer was correct!'; 53 | } 54 | ?> 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | We'll start with the **User** handling. Gatekeeper makes it simple to manage users and perform the usual CRUD (create, read update, delete) operations on their data. 4 | 5 | Users are represented as objects in the code with the following properties: 6 | 7 | - username 8 | - password 9 | - email 10 | - firstName 11 | - lastName 12 | - status 13 | - id 14 | - resetCode 15 | - resetCodeTimeout 16 | - groups 17 | - created 18 | - updated 19 | - groups (relational) 20 | - permissions (relational) 21 | - loginAttempts (relational) 22 | 23 | You can access this data on a populated user object as you would any other object properties: 24 | 25 | ```php 26 | firstName.' '.$user->lastName."\n"; 28 | ?> 29 | ``` 30 | 31 | ## Getting All Users 32 | 33 | You can use the `findUsers` method on the `Gatekeeper` class to get a list (returns a `UserCollection`) of the current users: 34 | 35 | ```php 36 | $users = Gatekeeper::findUsers(); 37 | 38 | // You can then slice it up how you need, like getting the first three 39 | $shortUserList = $users->slice(1, 3); 40 | ``` 41 | 42 | ## Creating Users 43 | 44 | To create a user, you only need to provide the user details to the `register` method: 45 | 46 | ```php 47 | 'ccornutt', 50 | 'password' => 'test1', 51 | 'email' => 'ccornutt@phpdeveloper.org', 52 | 'first_name' => 'Chris', 53 | 'last_name' => 'Cornutt' 54 | ); 55 | Gatekeeper::register($credentials); 56 | ?> 57 | ``` 58 | 59 | The return value from the `register` call is a *boolean* indicating the pass/fail status of the registration. 60 | Additionally, you can also link the user to permissions at create time: 61 | 62 | ```php 63 | 'ccornutt', 66 | 'password' => 'test1', 67 | 'email' => 'ccornutt@phpdeveloper.org', 68 | 'first_name' => 'Chris', 69 | 'last_name' => 'Cornutt' 70 | ); 71 | // Use can use permission names 72 | $credentials['permissions'] = array('perm1', 'perm2'); 73 | // or use IDs 74 | $credentials['permissions'] = array(1, 2); 75 | 76 | Gatekeeper::register($credentials); 77 | ?> 78 | ``` 79 | 80 | **NOTE:** The permissions by the name/id you use must exist *before* the user, otherwise the link is not created. 81 | 82 | You can also create groups the same way: 83 | 84 | ```php 85 | 'ccornutt', 88 | 'password' => 'test1', 89 | 'email' => 'ccornutt@phpdeveloper.org', 90 | 'first_name' => 'Chris', 91 | 'last_name' => 'Cornutt' 92 | ); 93 | // Use can use permission names 94 | $credentials['groups'] = array('group1', 'group2'); 95 | // or use IDs 96 | $credentials['groups'] = array(1, 2); 97 | 98 | Gatekeeper::register($credentials); 99 | ?> 100 | ``` 101 | 102 | ## Removing users 103 | 104 | Deleting user records can be done with the `deleteUserById` method: 105 | 106 | ```php 107 | 115 | ``` 116 | 117 | ## Activating/Deactivating Users 118 | 119 | You can mark a user as active or inactive in the system easily. Inactive users will not be able to log in using the `authenticate` method. Changing the user status is easy: 120 | 121 | ```php 122 | activate(); 125 | 126 | // Change the user status to inactive 127 | Gatekeeper::findUserById($userId)->deactivate(); 128 | ?> 129 | ``` 130 | 131 | ## Get User Groups 132 | 133 | You can use the `groups` relational property to find the groups the user is a member of. It will return an iterable collection 134 | you can use like any other array of data: 135 | 136 | ```php 137 | groups; 139 | foreach($groups as $group) { 140 | echo 'Group name: '.$group->name."\n"; 141 | } 142 | ?> 143 | ``` 144 | 145 | ## See if a user is in a group 146 | 147 | You can check to see if a user is in a group with the `inGroup` method: 148 | 149 | ```php 150 | inGroup($groupId) === true) { 154 | echo 'The user is in the group!'; 155 | } 156 | 157 | ?> 158 | ``` 159 | 160 | ## Adding a user to a group 161 | 162 | You can add a user to a group by using the group ID: 163 | 164 | ```php 165 | addGroup($groupId) === true) { 168 | echo "User added successfully!"; 169 | } 170 | ?> 171 | ``` 172 | 173 | You can also grant the group to a user with an expiration time, giving them permissions until a certain time. You set the expiration as a second value on the `addGroup` method by passing in a Unix timestamp: 174 | 175 | ```php 176 | addGroup(1, strtotime('+1 day')) === true) { 178 | echo "User added successfully!"; 179 | } 180 | ``` 181 | 182 | ## Revoking access to a group 183 | 184 | You can also remove a user from a group by revoking their access: 185 | 186 | ```php 187 | revokeGroup($groupId) === true) { 190 | echo "User removed from group successfully!"; 191 | } 192 | ?> 193 | ``` 194 | 195 | ## Checking to see if a user has a permission 196 | 197 | You can check the user's immediate permissions (not the ones on groups they belong to) with the `hasPermission` method: 198 | 199 | ```php 200 | hasPermission($permissionId) === true) { 203 | echo "They've got it!"; 204 | } 205 | ?> 206 | ``` 207 | 208 | You'll need to have the `id` value for the permission you want to check and provide that as the parameter in the call. 209 | 210 | ## Get a list of user permissions 211 | 212 | You can use the `permissions` property to get the full set of user permissions. These are the permissions **directly assigned** to the user, not to any groups they may be a part of: 213 | 214 | ```php 215 | permissions; 217 | foreach ($permissions as $perm) { 218 | echo $perm->description."\n"; 219 | } 220 | ?> 221 | ``` 222 | 223 | ## Giving a user a permission 224 | 225 | You can assign a permission **directly** to a user (not through a group) with the `addPermission` method: 226 | 227 | ```php 228 | addPermission($permissionId) === true) { 232 | echo 'Permission added!'; 233 | } 234 | ?> 235 | ``` 236 | 237 | You can also provide an optional second parameter with an expiration time if you only want to allow the user the permission for a limited about of time. This parameter should be in the form of a Unix timestamp: 238 | 239 | ```php 240 | addPermission($permissionId, strtotime('+1 day')); 242 | ?> 243 | ``` 244 | 245 | When fetching a user's permission list (like with `$user->permissions`) it will only return the non-expired or permanent permissions. 246 | 247 | ## Revoking a permission 248 | 249 | You can remove a permission from a user by revoking it: 250 | 251 | ``` 252 | revokePermission($permissionId) === true) { 256 | echo 'Permission revoked!'; 257 | } 258 | ?> 259 | ``` 260 | 261 | ## Using "grant" 262 | 263 | There's also a method on the User object that can be used to grant a user access to multiple permissions and groups all at the same time: `grant`. Here's an example: 264 | 265 | ```php 266 | grant(array( 268 | 'permissions' => array(1, 3), 269 | 'groups' => array(1) 270 | )); 271 | ?> 272 | ``` 273 | 274 | You can either specify a `permissions` and `groups` values as an array of IDs or you can feed in objects...or a mix of both: 275 | 276 | ```php 277 | grant(array( 282 | 'permissions' => array($perm1, 3), 283 | 'groups' => array($group1) 284 | )); 285 | ?> 286 | ``` 287 | 288 | Additionally, much like manually adding groups and permissions for a user, you can also set an expiration time: 289 | 290 | ```php 291 | grant(array( 297 | 'permissions' => array($perm1, 3), 298 | 'groups' => array($group1) 299 | ), $expireTime); 300 | ?> 301 | ``` 302 | 303 | ## Check if a user is currently banned (throttling) 304 | 305 | If the user login has had too many failed attempts, they'll be marked as "banned" in the system. You can find a user's ban status with the `isBanned` check: 306 | 307 | ```php 308 | isBanned() === true) { 310 | echo "User is banned!"; 311 | } 312 | ?> 313 | ``` 314 | 315 | ## Get full user throttle information 316 | 317 | You can also get the full throttling information for a user using the `throttle` property: 318 | 319 | ```php 320 | throttle; 322 | 323 | // This gives you properties like: 324 | $throttle->attempts; 325 | $throttle->status; 326 | $throttle->lastAttempt; 327 | $throttle->statusChange; 328 | ?> 329 | ``` 330 | 331 | ## Get the number of login attempts 332 | 333 | You can also get information about the number of times a login has been attempted for a user (valid or invalid) with the `loginAttempts` property: 334 | 335 | ```php 336 | loginAttempts; 338 | echo "The user has tried to log in ".$attempts." times."; 339 | ?> 340 | ``` 341 | -------------------------------------------------------------------------------- /docs/working-with-objects.md: -------------------------------------------------------------------------------- 1 | # Working with Objects 2 | 3 | Each item in the system is represented by an object (a `model` type). When you perform an operation like a `find` or `create`, an instance of the corresponding model is created. There's some common things that you can do on models all across the system. Rather than duplicate that information across multiple pages of the documentation, I'm going to put it here in one place. 4 | 5 | ## Finding 6 | 7 | You can use the magic "find by" handling to locate records of the various types. Most commonly, this would be used to locate a record by its unique ID number (all records have this). Here's some examples: 8 | 9 | ```php 10 | 18 | ``` 19 | 20 | Each of these will return an object you can pull properties from. For example, a `User` object has properties like `username`, `email` and `firstName`. These can be accessed directly just like any other PHP property: 21 | 22 | ```php 23 | firstName.', username is '.$user->username.' and email is '.$user->email; 27 | ?> 28 | ``` 29 | 30 | If no records are found given the criteria you provided, one of the "not found" `Exception` options will be thrown. 31 | 32 | **NOTE:** You can find the list of properties on the pages for each of the different types (like `Users` and `Groups`). 33 | 34 | ## Deleting 35 | 36 | You can also use common functionality to delete records from the system. This uses a format similar to the "find by" methods but instead uses a "delete by". 37 | 38 | ```php 39 | 47 | ``` 48 | 49 | The delete calls can also use any property on the object, but there is one thing to watch out for. If you provide information that matches more than one record in the system, the operation will fail. For example, if there were five users with a first name of "Chris", the call doesn't know which one you want to remove, so it returns false. 50 | 51 | In this case, you'll need to run a `find` operation and locate the record you want. When you have the model instance you want, you can just call `delete` directly on it: 52 | 53 | ```php 54 | delete(); 57 | ?> 58 | ``` 59 | 60 | ## Cloning 61 | 62 | You can clone certain kinds of objects in the Gatekeeper system, duplicating the type of object and its relations. 63 | 64 | #### Users 65 | 66 | You can clone a user with the `Gatekeeper::cloneUser` method. This will create a new user with the data provided and link this new user to the same permissions and groups as the user you're cloning. 67 | 68 | ```php 69 | 'ccornutt1', 74 | 'password' => 'super-secret', 75 | 'email' => 'ccornut2@mydomain.com', 76 | 'firstName' => 'Chris', 77 | 'lastName' => 'Cornutt2' 78 | ]); 79 | 80 | if ($result === true) { 81 | echo 'User cloned successfully!'; 82 | } 83 | ?> 84 | ``` 85 | 86 | In this case user `cornutt1` will have the same permissions and groups as `ccornutt`. The result of the `cloneUser` function call is the success/fail status of the creation. 87 | 88 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Gatekeeper Examples 2 | ========================= 3 | 4 | This directory contains some examples of the `Gatekeeper` system in use. Thses examples are just a *starting place* 5 | to help you get up and running quickly and to make it easier to understand how to use the system. 6 | 7 | Examples: 8 | 9 | - [Sample login form](login-form.html) -------------------------------------------------------------------------------- /examples/login-form.html: -------------------------------------------------------------------------------- 1 | $_POST['username'], 14 | 'password' => $_POST['password'] 15 | ); 16 | if (g::authenticate($credentials) === true) { 17 | echo 'Login successful!'; 18 | } else { 19 | echo 'Login failed!'; 20 | } 21 | } 22 | 23 | ?> 24 | 25 | 26 | 27 | 28 | 29 |
30 | Username:
31 | Password:
32 |
33 | 34 |
35 | 36 | -------------------------------------------------------------------------------- /migrations/20141211092209_create_user_table.php: -------------------------------------------------------------------------------- 1 | getTableName(); 13 | $users = $this->table($tableName); 14 | $users->addColumn('username', 'string', array('limit' => 20)) 15 | ->addColumn('password', 'string', array('limit' => 100)) 16 | ->addColumn('email', 'string', array('limit' => 100)) 17 | ->addColumn('first_name', 'string', array('limit' => 30)) 18 | ->addColumn('last_name', 'string', array('limit' => 30)) 19 | ->addColumn('status', 'string', array('limit' => 30, 'default' => 'active')) 20 | ->addColumn('created', 'datetime') 21 | ->addColumn('updated', 'datetime', array('default' => null)) 22 | ->addIndex(array('username'), array('unique' => true)) 23 | ->save(); 24 | 25 | // Manually add these as there seems to be a bug in Phinx... 26 | $this->execute('alter table '.$tableName.' add password_reset_code VARCHAR(100)'); 27 | $this->execute('alter table '.$tableName.' add password_reset_code_timeout DATETIME'); 28 | } 29 | 30 | /** 31 | * Migrate Down, remove the user table 32 | */ 33 | public function down() 34 | { 35 | $this->dropTable($this->getTableName()); 36 | } 37 | } -------------------------------------------------------------------------------- /migrations/20141211204500_create_group_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $groups->addColumn('description', 'string', array('limit' => 20)) 14 | ->addColumn('created', 'datetime') 15 | ->addColumn('updated', 'datetime', array('default' => null)) 16 | ->addcolumn('name', 'string', array('limit' => 100)) 17 | ->addIndex(array('name'), array('unique' => true)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20141211205817_create_group_user_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $groups->addColumn('group_id', 'integer') 14 | ->addColumn('user_id', 'integer') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->addIndex(array('group_id', 'user_id'), array('unique' => true)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20141212101534_create_permissions_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $groups->addColumn('name', 'string', array('limit' => 100)) 14 | ->addColumn('description', 'text') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->addIndex(array('name'), array('unique' => true)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20141213161408_create_user_permission_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $permissions->addColumn('permission_id', 'integer') 14 | ->addColumn('user_id', 'integer') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->addIndex(array('permission_id', 'user_id'), array('unique' => true)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20141214082627_create_group_permission_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $permissions->addColumn('permission_id', 'integer') 14 | ->addColumn('group_id', 'integer') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->addIndex(array('permission_id', 'group_id'), array('unique' => true)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20141216132116_create_throttle_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $throttle->addColumn('user_id', 'integer') 14 | ->addColumn('attempts', 'integer') 15 | ->addColumn('status', 'string') 16 | ->addColumn('last_attempt', 'datetime') 17 | ->addColumn('status_change', 'datetime') 18 | ->addColumn('created', 'datetime') 19 | ->addColumn('updated', 'datetime', array('default' => null)) 20 | ->save(); 21 | } 22 | 23 | /** 24 | * Migrate Down. 25 | */ 26 | public function down() 27 | { 28 | $this->dropTable($this->getTableName()); 29 | } 30 | } -------------------------------------------------------------------------------- /migrations/20150109213039_create_group_parent_xref.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $groupXref->addColumn('group_id', 'integer') 14 | ->addColumn('parent_id', 'integer') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->save(); 18 | } 19 | 20 | /** 21 | * Migrate Down. 22 | */ 23 | public function down() 24 | { 25 | $this->dropTable($this->getTableName()); 26 | } 27 | } -------------------------------------------------------------------------------- /migrations/20150111162554_create_permission_parent_xref.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $permissionXref->addColumn('permission_id', 'integer') 14 | ->addColumn('parent_id', 'integer') 15 | ->addColumn('created', 'datetime') 16 | ->addColumn('updated', 'datetime', array('default' => null)) 17 | ->save(); 18 | } 19 | 20 | /** 21 | * Migrate Down. 22 | */ 23 | public function down() 24 | { 25 | $this->dropTable($this->getTableName()); 26 | } 27 | } -------------------------------------------------------------------------------- /migrations/20150124094757_fix_unique_indexes.php: -------------------------------------------------------------------------------- 1 | execute('create unique index permissionid_userid on '.$this->getPrefix().'user_permission(permission_id, user_id)'); 11 | $this->execute('create unique index groupid_userid on '.$this->getPrefix().'user_group(user_id, group_id)'); 12 | $this->execute('create unique index permissionid_parentid on '.$this->getPrefix().'permission_parent(permission_id, parent_id)'); 13 | $this->execute('create unique index permissionid_groupid on '.$this->getPrefix().'group_permission(permission_id, group_id)'); 14 | } 15 | 16 | /** 17 | * Migrate Down. 18 | */ 19 | public function down() 20 | { 21 | $this->execute('drop index permissionid_userid on '.$this->getPrefix().'user_permission'); 22 | $this->execute('drop index groupid_userid on '.$this->getPrefix().'user_group'); 23 | $this->execute('drop index permissionid_parentid on '.$this->getPrefix().'permission_parent'); 24 | $this->execute('drop index permissionid_groupid on '.$this->getPrefix().'group_permission'); 25 | } 26 | } -------------------------------------------------------------------------------- /migrations/20150202164329_create_auth_token_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $tokens->addColumn('token', 'string', array('limit' => 100)) 14 | ->addColumn('user_id', 'integer') 15 | ->addColumn('expires', 'datetime') 16 | ->addColumn('created', 'datetime') 17 | ->addColumn('updated', 'datetime', array('default' => null)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20150218151720_create_security_questions_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 13 | $tokens->addColumn('question', 'string') 14 | ->addColumn('answer', 'string', array('limit' => 100)) 15 | ->addColumn('user_id', 'integer') 16 | ->addColumn('created', 'datetime') 17 | ->addColumn('updated', 'datetime', array('default' => null)) 18 | ->save(); 19 | } 20 | 21 | /** 22 | * Migrate Down. 23 | */ 24 | public function down() 25 | { 26 | $this->dropTable($this->getTableName()); 27 | } 28 | } -------------------------------------------------------------------------------- /migrations/20150528223137_create_policy_table.php: -------------------------------------------------------------------------------- 1 | table($this->getTableName()); 15 | $tokens->addColumn('expression', 'string') 16 | ->addColumn('name', 'string') 17 | ->addColumn('description', 'text') 18 | ->addColumn('created', 'datetime') 19 | ->addColumn('updated', 'datetime', array('default' => null)) 20 | ->save(); 21 | 22 | $this->execute('create unique index policy_name on '.$this->getPrefix().'policies(name)'); 23 | } 24 | 25 | /** 26 | * Migrate Down. 27 | */ 28 | public function down() 29 | { 30 | $this->dropTable($this->getTableName()); 31 | } 32 | } -------------------------------------------------------------------------------- /migrations/20150612202904_add_last_login_column.php: -------------------------------------------------------------------------------- 1 | execute('alter table users add last_login DATETIME'); 13 | } 14 | 15 | /** 16 | * Migrate Down. 17 | */ 18 | public function down() 19 | { 20 | $this->execute('alter table users drop column last_login'); 21 | } 22 | } -------------------------------------------------------------------------------- /migrations/20150702224804_add_permission_group_expire.php: -------------------------------------------------------------------------------- 1 | execute('alter table permissions add expire INT'); 13 | $this->execute('alter table groups add expire INT'); 14 | } 15 | 16 | /** 17 | * Migrate Down. 18 | */ 19 | public function down() 20 | { 21 | $this->execute('alter table permissions drop column expire'); 22 | $this->execute('alter table groups drop column expire'); 23 | } 24 | } -------------------------------------------------------------------------------- /migrations/20150703015048_add_user_permission_group_expire.php: -------------------------------------------------------------------------------- 1 | execute('alter table user_permission add expire INT'); 13 | $this->execute('alter table group_permission add expire INT'); 14 | } 15 | 16 | /** 17 | * Migrate Down. 18 | */ 19 | public function down() 20 | { 21 | $this->execute('alter table user_permission drop column expire'); 22 | $this->execute('alter table group_permission drop column expire'); 23 | } 24 | } -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Gatekeeper 2 | theme: readthedocs 3 | repo_url: http://github.com/psecio/gatekeeper 4 | site_url: http://gatekeeper-auth.readthedocs.org 5 | pages: 6 | - 'index.md' 7 | - 'installation-and-configuration.md' 8 | - 'working-with-objects.md' 9 | - 'authentication.md' 10 | - 'users.md' 11 | - 'permissions.md' 12 | - 'groups.md' 13 | - 'password-reset.md' 14 | - 'security-questions.md' 15 | - 'providers.md' 16 | - 'policies.md' -------------------------------------------------------------------------------- /phinx.dist.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | migrations: vendor/psecio/gatekeeper/migrations 3 | 4 | environments: 5 | default_migration_table: phinxlog 6 | default_database: development 7 | production: 8 | adapter: mysql 9 | host: localhost 10 | name: production_db 11 | user: username 12 | pass: 'password' 13 | port: 3306 14 | charset: utf8 15 | 16 | development: 17 | adapter: mysql 18 | host: %%HOSTNAME%% 19 | name: %%DBNAME%% 20 | user: %%USERNAME%% 21 | pass: '%%PASSWORD%%' 22 | port: 3306 23 | charset: utf8 24 | 25 | testing: 26 | adapter: mysql 27 | host: localhost 28 | name: testing_db 29 | user: username 30 | pass: 'password' 31 | port: 3306 32 | charset: utf8 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | ./tests 13 | 14 | 15 | 16 | 17 | ./src 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/AuthTokenCollection.php: -------------------------------------------------------------------------------- 1 | $userId]; 16 | 17 | $results = $this->getDb()->fetch($sql, $data); 18 | if ($results !== false) { 19 | foreach ($results as $result) { 20 | $token = new AuthTokenModel($this->getDb(), $result); 21 | $this->add($token); 22 | } 23 | } 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/AuthTokenModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Token ID', 20 | 'column' => 'id', 21 | 'type' => 'integer' 22 | ), 23 | 'token' => array( 24 | 'description' => 'Token value', 25 | 'column' => 'token', 26 | 'type' => 'varchar' 27 | ), 28 | 'verifier' => array( 29 | 'description' => 'Verifier value', 30 | 'column' => 'verifier', 31 | 'type' => 'varchar' 32 | ), 33 | 'userId' => array( 34 | 'description' => 'User ID', 35 | 'column' => 'user_id', 36 | 'type' => 'integer' 37 | ), 38 | 'user' => array( 39 | 'description' => 'User related to token', 40 | 'type' => 'relation', 41 | 'relation' => array( 42 | 'model' => '\\Psecio\\Gatekeeper\\UserModel', 43 | 'method' => 'findByUserId', 44 | 'local' => 'userId' 45 | ) 46 | ), 47 | 'expires' => array( 48 | 'description' => 'Date Token Expires', 49 | 'column' => 'expires', 50 | 'type' => 'datetime' 51 | ), 52 | 'created' => array( 53 | 'description' => 'Date Created', 54 | 'column' => 'created', 55 | 'type' => 'datetime' 56 | ), 57 | 'updated' => array( 58 | 'description' => 'Date Updated', 59 | 'column' => 'updated', 60 | 'type' => 'datetime' 61 | ), 62 | ); 63 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Collection/Mysql.php: -------------------------------------------------------------------------------- 1 | setDb($db); 33 | } 34 | 35 | /** 36 | * Set the current DB object instance 37 | * 38 | * @param object $db Database object 39 | */ 40 | public function setDb($db) 41 | { 42 | $this->db = $db; 43 | } 44 | 45 | /** 46 | * Get the current database object instance 47 | * 48 | * @return object Database instance 49 | */ 50 | public function getDb() 51 | { 52 | return $this->db; 53 | } 54 | 55 | /** 56 | * Get the current model's table name 57 | * 58 | * @return string Table name 59 | */ 60 | public function getTableName() 61 | { 62 | $dbConfig = $this->db->config; 63 | return (isset($dbConfig['prefix'])) 64 | ? $dbConfig['prefix'].'_'.$this->tableName : $this->tableName; 65 | } 66 | 67 | public function getPrefix() 68 | { 69 | $dbConfig = $this->db->config; 70 | return (isset($dbConfig['prefix'])) ? $dbConfig['prefix'].'_' : ''; 71 | } 72 | 73 | /** 74 | * Get the last error from the database requests 75 | * 76 | * @return string Error message 77 | */ 78 | public function getLastError() 79 | { 80 | return $this->lastError; 81 | } 82 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/DataSource.php: -------------------------------------------------------------------------------- 1 | setConfig($config); 26 | } 27 | 28 | /** 29 | * Set the configuration 30 | * 31 | * @param array $config Config settings 32 | */ 33 | public function setConfig(array $config) 34 | { 35 | $this->config = $config; 36 | } 37 | 38 | /** 39 | * Get the configuration settings 40 | * 41 | * @return array Config settings 42 | */ 43 | public function getConfig() 44 | { 45 | return $this->config; 46 | } 47 | 48 | /** 49 | * Save the given model 50 | * 51 | * @param \Modler\Model $model Model instance 52 | * @return boolean Success/fail of action 53 | */ 54 | public abstract function save(\Modler\Model $model); 55 | 56 | /** 57 | * Create a new record with model given 58 | * 59 | * @param \Modler\Model $model Model instance 60 | * @return boolean Success/fail of action 61 | */ 62 | public abstract function create(\Modler\Model $model); 63 | 64 | /** 65 | * Update the record for the given model 66 | * 67 | * @param \Modler\Model $model Model instance 68 | * @return boolean Success/fail of action 69 | */ 70 | public abstract function update(\Modler\Model $model); 71 | 72 | /** 73 | * Delete the record defined by the model data 74 | * 75 | * @param \Modler\Model $model Model instance 76 | * @return boolean Success/fail of action 77 | */ 78 | public abstract function delete(\Modler\Model $model); 79 | 80 | /** 81 | * Find and populate a model based on the model type and where criteria 82 | * 83 | * @param \Modler\Model $model Model instance 84 | * @param array $where "Where" data to locate record 85 | * @return object Either a collection or model instance 86 | */ 87 | public abstract function find(\Modler\Model $model, array $where = array()); 88 | 89 | /** 90 | * Return the number of entities in DB per condition or in general 91 | * 92 | * @param \Modler\Model $model Model instance 93 | * @param array $where 94 | * @return bool Success/fail of action 95 | * @internal param array $where "Where" data to locate record 96 | */ 97 | public abstract function count(\Modler\Model $model, array $where = array()); 98 | 99 | /** 100 | * Return the last error from action taken on the datasource 101 | * 102 | * @return string Error string 103 | */ 104 | public abstract function getLastError(); 105 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/DataSource/Mysql.php: -------------------------------------------------------------------------------- 1 | buildPdo($config) : $pdo; 22 | $this->setDb($pdo); 23 | parent::__construct($config); 24 | } 25 | 26 | /** 27 | * Build the PDO instance 28 | * 29 | * @param array $config Configuration options 30 | * @return \PDO instance 31 | */ 32 | public function buildPdo(array $config) 33 | { 34 | return new \PDO( 35 | 'mysql:dbname='.$config['name'].';host='.$config['host'].';charset=utf8', 36 | $config['username'], $config['password'] 37 | ); 38 | } 39 | 40 | /** 41 | * Get the set PDO instance 42 | * 43 | * @return \PDO instance 44 | */ 45 | public function getDb() 46 | { 47 | return $this->db; 48 | } 49 | 50 | /** 51 | * Set the PDO instance 52 | * 53 | * @param \PDO $db PDO instance 54 | */ 55 | public function setDb($db) 56 | { 57 | $this->db = $db; 58 | } 59 | 60 | /** 61 | * Save the model and its data (either create or update) 62 | * 63 | * @param \Modler\Model $model Model instance 64 | * @return boolean Success/fail of save action 65 | */ 66 | public function save(\Modler\Model $model) 67 | { 68 | $data = $model->toArray(); 69 | 70 | // see if we have any pre-save 71 | foreach ($data as $name => $value) { 72 | $preMethod = 'pre'.ucwords($name); 73 | if (method_exists($model, $preMethod)) { 74 | $model->$name = $model->$preMethod($value); 75 | } 76 | } 77 | 78 | if ($model->id === null) { 79 | return $this->create($model); 80 | } else { 81 | return $this->update($model); 82 | } 83 | } 84 | 85 | /** 86 | * Create the record based on the data from the model 87 | * 88 | * @param \Modler\Model $model Model instance 89 | * @return boolean Success/fail of create action 90 | */ 91 | public function create(\Modler\Model $model) 92 | { 93 | $relations = array(); 94 | $properties = $model->getProperties(); 95 | $data = $model->toArray(); 96 | 97 | // Remove the ones without a column 98 | foreach ($data as $index => $item) { 99 | // Check the property to see if it's a relation 100 | if ($properties[$index]['type'] == 'relation') { 101 | $relations[$index] = $item; 102 | unset($data[$index]); 103 | } 104 | } 105 | 106 | $data['created'] = date('Y-m-d H:i:s'); 107 | $data['updated'] = date('Y-m-d H:i:s'); 108 | 109 | list($columns, $bind) = $this->setup($data); 110 | foreach ($columns as $index => $column) { 111 | $colName = $properties[$column]['column']; 112 | $columns[$index] = $colName; 113 | } 114 | 115 | $sql = 'insert into '.$model->getTableName() 116 | .' ('.implode(',', $columns).') values ('.implode(',', array_values($bind)).')'; 117 | $result = $this->execute($sql, $data); 118 | if ($result !== false) { 119 | $model->id = $this->getDb()->lastInsertId(); 120 | // Now handle the relations - for each of them, get the model, make it and save it 121 | foreach ($relations as $index => $item) { 122 | $relation = $properties[$index]; 123 | $instance = new $relation['relation']['model']($this); 124 | $instance->create($model, $item); 125 | } 126 | } 127 | 128 | return $result; 129 | } 130 | 131 | /** 132 | * Update a record 133 | * 134 | * @param \Modler\Model $model Model instance 135 | * @return boolean Success/fail of operation 136 | */ 137 | public function update(\Modler\Model $model) 138 | { 139 | $data = $model->toArray(); 140 | $data['created'] = date('Y-m-d H:i:s'); 141 | $data['updated'] = date('Y-m-d H:i:s'); 142 | 143 | list($columns, $bind) = $this->setup($data); 144 | $update = array(); 145 | $properties = $model->getProperties(); 146 | 147 | foreach ($bind as $column => $name) { 148 | $colName = $properties[$column]['column']; 149 | $update[] = $colName.' = '.$name; 150 | } 151 | 152 | $sql = 'update '.$model->getTableName().' set '.implode(',', $update).' where ID = '.$model->id; 153 | return $this->execute($sql, $data); 154 | } 155 | 156 | /** 157 | * Delete a record represented by the model 158 | * 159 | * @param \Modler\Model $model Model instance 160 | * @return boolean Success/failure of deletion 161 | */ 162 | public function delete(\Modler\Model $model) 163 | { 164 | $where = $model->toArray(); 165 | $properties = $model->getProperties(); 166 | list($columns, $bind) = $this->setup($where); 167 | $update = array(); 168 | 169 | foreach ($bind as $column => $name) { 170 | // See if we keep to transfer it over to a column name 171 | if (array_key_exists($column, $properties)) { 172 | $column = $properties[$column]['column']; 173 | } 174 | $update[] = $column.' = '.$name; 175 | } 176 | 177 | $sql = 'delete from '.$model->getTableName().' where '.implode(' and ', $update); 178 | return $this->execute($sql, $model->toArray()); 179 | } 180 | 181 | /** 182 | * Find records matching the "where" data given 183 | * All "where" options are appended via "and" 184 | * 185 | * @param \Modler\Model $model Model instance 186 | * @param array $where Data to use in "where" statement 187 | * @param boolean $multiple Force return of single/multiple 188 | * @return array Fetched data 189 | */ 190 | public function find(\Modler\Model $model, array $where = array(), $multiple = false) 191 | { 192 | $properties = $model->getProperties(); 193 | list($columns, $bind) = $this->setup($where); 194 | $update = array(); 195 | foreach ($bind as $column => $name) { 196 | // See if we keep to transfer it over to a column name 197 | if (array_key_exists($column, $properties)) { 198 | $column = $properties[$column]['column']; 199 | } 200 | $update[] = $column.' = '.$name; 201 | } 202 | 203 | $sql = 'select * from '.$model->getTableName(); 204 | if (!empty($update)) { 205 | $sql .= ' where '.implode(' and ', $update); 206 | } 207 | 208 | $result = $this->fetch($sql, $where); 209 | if ($result !== false && count($result) == 1 && $multiple === false) { 210 | $model->load($result[0]); 211 | return $model; 212 | } elseif (count($result) > 1 || $multiple === true){ 213 | // Make a collection instead 214 | $modelClass = get_class($model); 215 | $collectionNs = str_replace('Model', 'Collection', $modelClass); 216 | if (!class_exists($collectionNs)) { 217 | throw new \InvalidArgumentException('Collection "'.$collectionNs.'" is invalid!'); 218 | } 219 | $collection = new $collectionNs($this); 220 | foreach ($result as $item) { 221 | $itemModel = new $modelClass($this, $item); 222 | $collection->add($itemModel); 223 | } 224 | return $collection; 225 | } 226 | return $model; 227 | } 228 | 229 | /** 230 | * Find count of entities by where conditions. 231 | * All where conditions applied with AND 232 | * 233 | * @param \Modler\Model $model Model instance 234 | * @param array $where Data to use in "where" statement 235 | * @return array Fetched data 236 | */ 237 | public function count(\Modler\Model $model, array $where = array()) 238 | { 239 | $properties = $model->getProperties(); 240 | list($columns, $bind) = $this->setup($where); 241 | 242 | $update = array(); 243 | foreach ($bind as $column => $name) { 244 | // See if we keep to transfer it over to a column name 245 | if (array_key_exists($column, $properties)) { 246 | $column = $properties[$column]['column']; 247 | } 248 | $update[] = $column.' = '.$name; 249 | } 250 | 251 | $sql = 'select count(*) as `count` from '.$model->getTableName(); 252 | if (!empty($update)) { 253 | $sql .= ' where '.implode(' and ', $update); 254 | } 255 | 256 | $result = $this->fetch($sql, $where, true); 257 | return $result; 258 | } 259 | 260 | /** 261 | * Execute the request (not a fetch) 262 | * 263 | * @param string $sql SQL statement to execute 264 | * @param array $data Data to use in execution 265 | * @return boolean Success/fail of the operation 266 | */ 267 | public function execute($sql, array $data) 268 | { 269 | $sth = $this->getDb()->prepare($sql); 270 | $result = $sth->execute($data); 271 | 272 | if ($result === false) { 273 | $error = $sth->errorInfo(); 274 | $this->lastError = 'DB ERROR: ['.$sth->errorCode().'] '.$error[2]; 275 | } 276 | return $result; 277 | } 278 | 279 | /** 280 | * Fetch the data matching the results of the SQL operation 281 | * 282 | * @param string $sql SQL statement 283 | * @param array $data Data to use in fetch operation 284 | * @param boolean $single Only fetch a single record 285 | * @return array|boolean Fetched data or boolean false on error 286 | */ 287 | public function fetch($sql, $data, $single = false) 288 | { 289 | $sth = $this->getDb()->prepare($sql); 290 | $result = $sth->execute($data); 291 | 292 | if ($result === false) { 293 | $error = $sth->errorInfo(); 294 | $this->lastError = 'DB ERROR: ['.$sth->errorCode().'] '.$error[2]; 295 | return false; 296 | } 297 | 298 | $results = $sth->fetchAll(\PDO::FETCH_ASSOC); 299 | return ($single === true) ? array_shift($results) : $results; 300 | } 301 | 302 | /** 303 | * "Set up" the needed values for the database requests 304 | * (for binding to queries) 305 | * 306 | * @param array $data Data to "set up" 307 | * @return array Set containing the columns and bind values 308 | */ 309 | public function setup(array $data) 310 | { 311 | $bind = array(); 312 | foreach ($data as $column => $value) { 313 | $bind[$column] = ':'.$column; 314 | } 315 | 316 | return array(array_keys($data), $bind); 317 | } 318 | 319 | /** 320 | * Return the last error for the data source 321 | * 322 | * @return string Error string 323 | */ 324 | public function getLastError() 325 | { 326 | return $this->lastError; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/DataSource/Stub.php: -------------------------------------------------------------------------------- 1 | getPrefix(); 15 | $data = array('groupId' => $groupId); 16 | $sql = 'select g.* from '.$prefix.'groups g, '.$prefix.'group_parent gp' 17 | .' where g.id = gp.group_id' 18 | .' and gp.parent_id = :groupId'; 19 | 20 | $results = $this->getDb()->fetch($sql, $data); 21 | 22 | foreach ($results as $result) { 23 | $group = new GroupModel($this->getDb(), $result); 24 | $this->add($group); 25 | } 26 | } 27 | 28 | /** 29 | * Find the groups that a permission belongs to 30 | * 31 | * @param integer $permId Permission ID 32 | */ 33 | public function findGroupsByPermissionId($permId) 34 | { 35 | $prefix = $this->getPrefix(); 36 | $data = array('permId' => $permId); 37 | $sql = 'select g.* from '.$prefix.'groups g, '.$prefix.'group_permission gp' 38 | .' where gp.permission_id = :permId' 39 | .' and gp.group_id = g.id'; 40 | 41 | $results = $this->getDb()->fetch($sql, $data); 42 | 43 | foreach ($results as $result) { 44 | $group = new GroupModel($this->getDb(), $result); 45 | $this->add($group); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/GroupModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Group Description', 20 | 'column' => 'description', 21 | 'type' => 'varchar' 22 | ), 23 | 'id' => array( 24 | 'description' => 'Group ID', 25 | 'column' => 'id', 26 | 'type' => 'integer' 27 | ), 28 | 'name' => array( 29 | 'description' => 'Group name', 30 | 'column' => 'name', 31 | 'type' => 'varchar' 32 | ), 33 | 'expire' => array( 34 | 'description' => 'Expiration Date', 35 | 'column' => 'expire', 36 | 'type' => 'datetime' 37 | ), 38 | 'created' => array( 39 | 'description' => 'Date Created', 40 | 'column' => 'created', 41 | 'type' => 'datetime' 42 | ), 43 | 'updated' => array( 44 | 'description' => 'Date Updated', 45 | 'column' => 'updated', 46 | 'type' => 'datetime' 47 | ), 48 | 'users' => array( 49 | 'description' => 'Users belonging to this group', 50 | 'type' => 'relation', 51 | 'relation' => array( 52 | 'model' => '\\Psecio\\Gatekeeper\\UserCollection', 53 | 'method' => 'findByGroupId', 54 | 'local' => 'id' 55 | ) 56 | ), 57 | 'permissions' => array( 58 | 'description' => 'Permissions belonging to this group', 59 | 'type' => 'relation', 60 | 'relation' => array( 61 | 'model' => '\\Psecio\\Gatekeeper\\PermissionCollection', 62 | 'method' => 'findByGroupId', 63 | 'local' => 'id' 64 | ) 65 | ), 66 | 'children' => array( 67 | 'description' => 'Child Groups', 68 | 'type' => 'relation', 69 | 'relation' => array( 70 | 'model' => '\\Psecio\\Gatekeeper\\GroupCollection', 71 | 'method' => 'findChildrenByGroupId', 72 | 'local' => 'id' 73 | ) 74 | ) 75 | ); 76 | 77 | /** 78 | * Add a user to the group 79 | * 80 | * @param integer|UserModel $user Either a user ID or a UserModel instance 81 | */ 82 | public function addUser($user) 83 | { 84 | if ($this->id === null) { 85 | return false; 86 | } 87 | if ($user instanceof UserModel) { 88 | $user = $user->id; 89 | } 90 | $data = array( 91 | 'group_id' => $this->id, 92 | 'user_id' => $user 93 | ); 94 | $groupUser = new UserGroupModel($this->getDb(), $data); 95 | return $this->getDb()->save($groupUser); 96 | } 97 | 98 | /** 99 | * Remove a user from a group 100 | * 101 | * @param integer|UserModel $user User ID or model instance 102 | * @return boolean Success/fail of removal 103 | */ 104 | public function removeUser($user) 105 | { 106 | if ($this->id === null) { 107 | return false; 108 | } 109 | if ($user instanceof UserModel) { 110 | $user = $user->id; 111 | } 112 | $data = array( 113 | 'group_id' => $this->id, 114 | 'user_id' => $user 115 | ); 116 | $groupUser = new UserGroupModel($this->getDb(), $data); 117 | return $this->getDb()->delete($groupUser); 118 | } 119 | 120 | /** 121 | * Check to see if the group has a permission 122 | * 123 | * @param integer|PermissionModel $permission Either a permission ID or PermissionModel 124 | * @return boolean Permission found/not found 125 | */ 126 | public function hasPermission($permission) 127 | { 128 | if ($this->id === null) { 129 | return false; 130 | } 131 | if ($permission instanceof PermissionModel) { 132 | $permission = $permission->id; 133 | } 134 | 135 | $perm = new GroupPermissionModel($this->getDb()); 136 | $perm = $this->getDb()->find($perm, array( 137 | 'permission_id' => $permission, 138 | 'group_id' => $this->id 139 | )); 140 | return ($perm->id !== null && $perm->permissionId == $permission) ? true : false; 141 | } 142 | 143 | /** 144 | * Add a permission relation for the group 145 | * 146 | * @param integer|PermissionModel $permission Either a permission ID or PermissionModel 147 | * @return boolean Success/fail of removal 148 | */ 149 | public function addPermission($permission) 150 | { 151 | if ($this->id === null) { 152 | return false; 153 | } 154 | if ($permission instanceof PermissionModel) { 155 | $permission = $permission->id; 156 | } 157 | $data = array( 158 | 'permission_id' => $permission, 159 | 'group_id' => $this->id 160 | ); 161 | $groupPerm = new GroupPermissionModel($this->getDb(), $data); 162 | return $this->getDb()->save($groupPerm); 163 | } 164 | 165 | /** 166 | * Remove a permission from a group 167 | * 168 | * @param integer|PermissionModel $permission Permission model or ID 169 | * @return boolean Success/fail of removal 170 | */ 171 | public function removePermission($permission) 172 | { 173 | if ($this->id === null) { 174 | return false; 175 | } 176 | if ($permission instanceof PermissionModel) { 177 | $permission = $permission->id; 178 | } 179 | $data = array( 180 | 'permission_id' => $permission, 181 | 'group_id' => $this->id 182 | ); 183 | $groupPerm = new GroupPermissionModel($this->getDb(), $data); 184 | return $this->getDb()->delete($groupPerm); 185 | } 186 | 187 | /** 188 | * Check if the user is in the current group 189 | * 190 | * @param integer $userId User ID 191 | * @return boolean Found/not found in group 192 | */ 193 | public function inGroup($userId) 194 | { 195 | $userGroup = new UserGroupModel($this->getDb()); 196 | $userGroup = $this->getDb()->find($userGroup, array( 197 | 'group_id' => $this->id, 198 | 'user_id' => $userId 199 | )); 200 | return ($userGroup->id !== null) ? true : false; 201 | } 202 | 203 | /** 204 | * Add the given group or group ID as a child of the current group 205 | * 206 | * @param integer|GroupModel $group Group ID or Group model instance 207 | * @return boolean Result of save operation 208 | */ 209 | public function addChild($group) 210 | { 211 | if ($this->id === null) { 212 | return false; 213 | } 214 | if ($group instanceof GroupModel) { 215 | $group = $group->id; 216 | } 217 | $childGroup = new GroupParentModel( 218 | $this->getDb(), 219 | array('groupId' => $group, 'parentId' => $this->id) 220 | ); 221 | return $this->getDb()->save($childGroup); 222 | } 223 | 224 | /** 225 | * Remove a child group either by ID or Group model instance 226 | * 227 | * @param integer|GroupModel $group Group ID or Group model instance 228 | * @return boolean Result of delete operation 229 | */ 230 | public function removeChild($group) 231 | { 232 | if ($this->id === null) { 233 | return false; 234 | } 235 | if ($group instanceof GroupModel) { 236 | $group = $group->id; 237 | } 238 | $childGroup = new GroupParentModel($this->getDb()); 239 | 240 | $childGroup = $this->getDb()->find( 241 | $childGroup, 242 | array('group_id' => $group, 'parent_id' => $this->id) 243 | ); 244 | return $this->getDb()->delete($childGroup); 245 | } 246 | 247 | /** 248 | * Check to see if the group is expired 249 | * 250 | * @return boolean Expired/Not expired result 251 | */ 252 | public function isExpired() 253 | { 254 | return ($this->expire !== null && $this->expire <= time()); 255 | } 256 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/GroupParentModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Record ID', 20 | 'column' => 'id', 21 | 'type' => 'integer' 22 | ), 23 | 'groupId' => array( 24 | 'description' => 'Group ID', 25 | 'column' => 'group_id', 26 | 'type' => 'integer' 27 | ), 28 | 'parentId' => array( 29 | 'description' => 'Parent ID', 30 | 'column' => 'parent_id', 31 | 'type' => 'integer' 32 | ), 33 | 'created' => array( 34 | 'description' => 'Date Created', 35 | 'column' => 'created', 36 | 'type' => 'datetime' 37 | ), 38 | 'updated' => array( 39 | 'description' => 'Date Updated', 40 | 'column' => 'updated', 41 | 'type' => 'datetime' 42 | ) 43 | ); 44 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/GroupPermissionModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Group Id', 20 | 'column' => 'group_id', 21 | 'type' => 'integer' 22 | ), 23 | 'permissionId' => array( 24 | 'description' => 'Permission ID', 25 | 'column' => 'permission_id', 26 | 'type' => 'integer' 27 | ), 28 | 'id' => array( 29 | 'description' => 'ID', 30 | 'column' => 'id', 31 | 'type' => 'integer' 32 | ), 33 | 'created' => array( 34 | 'description' => 'Date Created', 35 | 'column' => 'created', 36 | 'type' => 'datetime' 37 | ), 38 | 'updated' => array( 39 | 'description' => 'Date Updated', 40 | 'column' => 'updated', 41 | 'type' => 'datetime' 42 | ) 43 | ); 44 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler.php: -------------------------------------------------------------------------------- 1 | setArguments($arguments); 35 | $this->setName($name); 36 | $this->setDb($datasource); 37 | } 38 | 39 | /** 40 | * Set the current arguments 41 | * 42 | * @param array $arguments Method arguments 43 | */ 44 | public function setArguments(array $arguments) 45 | { 46 | $this->arguments = $arguments; 47 | } 48 | 49 | /** 50 | * Get the current set of arguments 51 | * 52 | * @return array Arguemnt data set 53 | */ 54 | public function getArguments() 55 | { 56 | return $this->arguments; 57 | } 58 | 59 | /** 60 | * Set method name called for handler 61 | * 62 | * @param string $name Method name called 63 | */ 64 | public function setName($name) 65 | { 66 | $this->name = $name; 67 | } 68 | 69 | /** 70 | * Get the method name called 71 | * 72 | * @return string Method name 73 | */ 74 | public function getName() 75 | { 76 | return $this->name; 77 | } 78 | 79 | /** 80 | * Set the current data source 81 | * 82 | * @param \Psecio\Gatekeeper\DataSource $datasource data source instance (DB) 83 | */ 84 | public function setDb(\Psecio\Gatekeeper\DataSource $datasource) 85 | { 86 | $this->datasource = $datasource; 87 | } 88 | 89 | /** 90 | * Get the current data source instance 91 | * 92 | * @return \Psecio\Gatekeeper\DataSource instance 93 | */ 94 | public function getDb() 95 | { 96 | return $this->datasource; 97 | } 98 | 99 | /** 100 | * Execute the handler logic 101 | * 102 | * @return mixed 103 | */ 104 | abstract public function execute(); 105 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/CloneInstance.php: -------------------------------------------------------------------------------- 1 | getArguments(); 16 | $name = $this->getName(); 17 | $method = ucwords($name); 18 | 19 | if (method_exists($this, $method) === true) { 20 | return $this->$method($args[0], $args[1]); 21 | } 22 | return false; 23 | } 24 | 25 | public function CloneUser($user, $data) 26 | { 27 | $ds = Gatekeeper::getDatasource(); 28 | $newUser = new \Psecio\Gatekeeper\UserModel($ds, $data); 29 | $result = $newUser->save(); 30 | 31 | if ($result == false) { 32 | return false; 33 | } 34 | 35 | // Get the user's groups and add 36 | foreach ($user->groups as $group) { 37 | $newUser->addGroup($group); 38 | } 39 | 40 | // Get the user's permissions and add 41 | foreach ($user->permissions as $permission) { 42 | $newUser->addPermission($permission); 43 | } 44 | 45 | return true; 46 | } 47 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/Count.php: -------------------------------------------------------------------------------- 1 | getArguments(); 16 | $name = $this->getName(); 17 | 18 | $model = '\\Psecio\\Gatekeeper\\' . str_replace('count', '', 19 | $name) . 'Model'; 20 | if (class_exists($model) === true) { 21 | $instance = new $model($this->getDb()); 22 | 23 | $count = (!$args) ? $this->getDb()->count($instance) : $this->getDb()->count($instance, 24 | $args[0]); 25 | return (int)$count['count']; 26 | } else { 27 | throw new \Psecio\Gatekeeper\Exception\ModelNotFoundException( 28 | 'Model type ' . $model . ' could not be found' 29 | ); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/Create.php: -------------------------------------------------------------------------------- 1 | getArguments(); 16 | $name = $this->getName(); 17 | 18 | $model = '\\Psecio\\Gatekeeper\\'.str_replace('create', '', $name).'Model'; 19 | if (class_exists($model) === true) { 20 | $instance = new $model($this->getDb(), $args[0]); 21 | $instance = $this->getDb()->save($instance); 22 | return $instance; 23 | } else { 24 | throw new \Psecio\Gatekeeper\Exception\ModelNotFoundException( 25 | 'Model type '.$model.' could not be found' 26 | ); 27 | } 28 | return false; 29 | } 30 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/Delete.php: -------------------------------------------------------------------------------- 1 | getArguments(); 16 | $name = $this->getName(); 17 | 18 | $model = g::buildModel('delete', $name, $args); 19 | return $this->getDb()->delete($model); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/FindBy.php: -------------------------------------------------------------------------------- 1 | getName(); 17 | $args = $this->getArguments(); 18 | 19 | return $this->handleFindBy($name, $args); 20 | } 21 | 22 | 23 | /** 24 | * Handle the "findBy" calls for data 25 | * 26 | * @param string $name Function name called 27 | * @param array $args Arguments 28 | * @throws \Exception\ModelNotFoundException If model type is not found 29 | * @throws \Exception If Data could not be found 30 | * @return object Model instance 31 | */ 32 | public function handleFindBy($name, $args) 33 | { 34 | $action = 'find'; 35 | $name = str_replace($action, '', $name); 36 | preg_match('/By(.+)/', $name, $matches); 37 | 38 | if (empty($matches) && strtolower(substr($name, -1)) === 's') { 39 | return $this->handleFindByMultiple($name, $args, $matches); 40 | } else { 41 | return $this->handleFindBySingle($name, $args, $matches); 42 | } 43 | 44 | return $instance; 45 | } 46 | 47 | /** 48 | * Handle the "find by" when a single record is requested 49 | * 50 | * @param string $name Name of function called 51 | * @param array $args Arguments list 52 | * @param array $matches Matches from regex 53 | * @return \Modler\Collection collection 54 | */ 55 | public function handleFindBySingle($name, $args, $matches) 56 | { 57 | $property = lcfirst($matches[1]); 58 | $model = str_replace($matches[0], '', $name); 59 | $data = array($property => $args[0]); 60 | 61 | $modelNs = '\\Psecio\\Gatekeeper\\'.$model.'Model'; 62 | if (!class_exists($modelNs)) { 63 | throw new \Psecio\Gatekepper\Exception\ModelNotFoundException('Model type '.$model.' could not be found'); 64 | } 65 | $instance = new $modelNs($this->getDb()); 66 | $instance = $this->getDb()->find($instance, $data); 67 | 68 | if ($instance->id === null) { 69 | return false; 70 | } 71 | 72 | return $instance; 73 | } 74 | 75 | /** 76 | * Handle the "find by" when multiple are requested 77 | * 78 | * @param string $name Name of function called 79 | * @param array $args Arguments list 80 | * @param array $matches Matches from regex 81 | * @return \Modler\Collection collection 82 | */ 83 | public function handleFindByMultiple($name, $args, $matches) 84 | { 85 | $data = (isset($args[0])) ? $args[0] : array(); 86 | $model = substr($name, 0, strlen($name) - 1); 87 | $collectionNs = '\\Psecio\\Gatekeeper\\'.$model.'Collection'; 88 | if (!class_exists($collectionNs)) { 89 | throw new \Psecio\Gatekeeper\Exception\ModelNotFoundException('Collection type '.$model.' could not be found'); 90 | } 91 | $model = g::modelFactory($model.'Model'); 92 | $collection = new $collectionNs($this->getDb()); 93 | $collection = $this->getDb()->find($model, $data, true); 94 | 95 | return $collection; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Handler/Save.php: -------------------------------------------------------------------------------- 1 | getArguments(); 15 | return $this-getDb()->save($args[0]); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Model/Mysql.php: -------------------------------------------------------------------------------- 1 | setDb($db); 22 | parent::__construct($data); 23 | } 24 | 25 | /** 26 | * Get the current data source instance 27 | * 28 | * @return \Psecio\Gatekeeper\DataSource instance 29 | */ 30 | public function getDb() 31 | { 32 | return $this->db; 33 | } 34 | 35 | /** 36 | * Set the datasource instance 37 | * 38 | * @param \Psecio\Gatekeeper\DataSource $db Data source instance 39 | */ 40 | public function setDb(\Psecio\Gatekeeper\DataSource $db) 41 | { 42 | $this->db = $db; 43 | } 44 | 45 | /** 46 | * Get the current model's table name 47 | * 48 | * @return string Table name 49 | */ 50 | public function getTableName() 51 | { 52 | $dbConfig = $this->db->config; 53 | return (isset($dbConfig['prefix'])) 54 | ? $dbConfig['prefix'].'_'.$this->tableName : $this->tableName; 55 | } 56 | 57 | /** 58 | * Make a new model instance 59 | * 60 | * @param string $model Model namespace "path" 61 | * @return object Model instance 62 | */ 63 | public function makeModelInstance($model) 64 | { 65 | $instance = new $model($this->getDb()); 66 | return $instance; 67 | } 68 | 69 | /** 70 | * Load the given data into the current model 71 | * 72 | * @param array $data Property data 73 | * @param boolean $enforceGuard Enforce guarded properties 74 | * @return boolean True when complete 75 | */ 76 | public function load(array $data, $enforceGuard = true) 77 | { 78 | $loadData = array(); 79 | foreach ($this->getProperties() as $propertyName => $propertyDetail) { 80 | // If it's a normal column 81 | if (isset($propertyDetail['column'])) { 82 | $column = $propertyDetail['column']; 83 | if (isset($data[$column]) || isset($data[$propertyName])) { 84 | $value = isset($data[$column]) ? $data[$column] : $data[$propertyName]; 85 | $loadData[$propertyName] = $value; 86 | } 87 | // Or for relations... 88 | } elseif ($propertyDetail['type'] == 'relation') { 89 | if (isset($data[$propertyName])) { 90 | $loadData[$propertyName] = $data[$propertyName]; 91 | } 92 | } 93 | } 94 | parent::load($loadData, $enforceGuard); 95 | return true; 96 | } 97 | 98 | /** 99 | * Save the current model instance (gets datasource and calls save) 100 | * 101 | * @return boolean Success/fail result of save 102 | */ 103 | public function save() 104 | { 105 | $ds = \Psecio\Gatekeeper\Gatekeeper::getDatasource(); 106 | return $ds->save($this); 107 | } 108 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PermissionCollection.php: -------------------------------------------------------------------------------- 1 | getPrefix(); 15 | $data = array('groupId' => $groupId); 16 | $sql = 'select p.* from '.$prefix.'permissions p, '.$prefix.'group_permission gp' 17 | .' where p.id = gp.permission_id' 18 | .' and gp.group_id = :groupId'; 19 | 20 | $results = $this->getDb()->fetch($sql, $data); 21 | 22 | if ($results !== false) { 23 | foreach ($results as $result) { 24 | $perm = new PermissionModel($this->getDb(), $result); 25 | $this->add($perm); 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Find child permission by the parent permission ID 32 | * 33 | * @param integer $permId Permission ID 34 | */ 35 | public function findChildrenByPermissionId($permId) 36 | { 37 | $prefix = $this->getPrefix(); 38 | $data = array('permId' => $permId); 39 | $sql = 'select p.* from '.$prefix.'permissions p, '.$prefix.'permission_parent pp' 40 | .' where p.id = pp.permission_id' 41 | .' and p.parent_id = :permId'; 42 | 43 | $results = $this->getDb()->fetch($sql, $data); 44 | 45 | foreach ($results as $result) { 46 | $group = new PermissionModel($this->getDb(), $result); 47 | $this->add($group); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PermissionModel.php: -------------------------------------------------------------------------------- 1 | array( 28 | 'description' => 'Group Name', 29 | 'column' => 'name', 30 | 'type' => 'varchar' 31 | ), 32 | 'description' => array( 33 | 'description' => 'Description', 34 | 'column' => 'description', 35 | 'type' => 'text' 36 | ), 37 | 'id' => array( 38 | 'description' => 'Group ID', 39 | 'column' => 'id', 40 | 'type' => 'integer' 41 | ), 42 | 'created' => array( 43 | 'description' => 'Date Created', 44 | 'column' => 'created', 45 | 'type' => 'datetime' 46 | ), 47 | 'updated' => array( 48 | 'description' => 'Date Updated', 49 | 'column' => 'updated', 50 | 'type' => 'datetime' 51 | ), 52 | 'expire' => array( 53 | 'description' => 'Expiration Date', 54 | 'column' => 'expire', 55 | 'type' => 'datetime' 56 | ), 57 | 'groups' => array( 58 | 'description' => 'Groups the permission belongs to', 59 | 'type' => 'relation', 60 | 'relation' => array( 61 | 'model' => '\\Psecio\\Gatekeeper\\GroupCollection', 62 | 'method' => 'findGroupsByPermissionId', 63 | 'local' => 'id' 64 | ) 65 | ), 66 | 'users' => array( 67 | 'description' => 'Users that have the permission', 68 | 'type' => 'relation', 69 | 'relation' => array( 70 | 'model' => '\\Psecio\\Gatekeeper\\UserCollection', 71 | 'method' => 'findUsersByPermissionId', 72 | 'local' => 'id' 73 | ) 74 | ), 75 | 'children' => array( 76 | 'description' => 'Child Permissions', 77 | 'type' => 'relation', 78 | 'relation' => array( 79 | 'model' => '\\Psecio\\Gatekeeper\\PermissionCollection', 80 | 'method' => 'findChildrenByPermissionId', 81 | 'local' => 'id' 82 | ) 83 | ) 84 | ); 85 | 86 | /** 87 | * Add a permission as a child of the current instance 88 | * 89 | * @param integer|PermissionModel $permission Either permission ID or model instance 90 | * @return boolean Result of save operation 91 | */ 92 | public function addChild($permission) 93 | { 94 | if ($this->id === null) { 95 | return false; 96 | } 97 | if ($permission instanceof PermissionModel) { 98 | $permission = $permission->id; 99 | } 100 | $childPermission = new PermissionParentModel( 101 | $this->getDb(), 102 | array('permission_id' => $permission, 'parent_id' => $this->id) 103 | ); 104 | return $this->getDb()->save($childPermission); 105 | } 106 | 107 | /** 108 | * Remove a permission as a child of this instance 109 | * 110 | * @param integer|PermissionModel $permission Either permission ID or model instance 111 | * @return boolean Resultk of delete operation 112 | */ 113 | public function removeChild($permission) 114 | { 115 | if ($this->id === null) { 116 | return false; 117 | } 118 | if ($permission instanceof PermissionModel) { 119 | $permission = $permission->id; 120 | } 121 | $childPermission = new PermissionParentModel($this->getDb()); 122 | 123 | $childPermission = $this->getDb()->find( 124 | $childPermission, 125 | array('permission_id' => $permission, 'parent_id' => $this->id) 126 | ); 127 | return $this->getDb()->delete($childPermission); 128 | } 129 | 130 | /** 131 | * Test if the permission is expired 132 | * 133 | * @return boolean Expired/not expired 134 | */ 135 | public function isExpired() 136 | { 137 | return ($this->expire !== null && $this->expire <= time()); 138 | } 139 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PermissionParentModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Record ID', 20 | 'column' => 'id', 21 | 'type' => 'integer' 22 | ), 23 | 'permissionId' => array( 24 | 'description' => 'Permission ID', 25 | 'column' => 'permission_id', 26 | 'type' => 'integer' 27 | ), 28 | 'parentId' => array( 29 | 'description' => 'Parent ID', 30 | 'column' => 'parent_id', 31 | 'type' => 'integer' 32 | ), 33 | 'created' => array( 34 | 'description' => 'Date Created', 35 | 'column' => 'created', 36 | 'type' => 'datetime' 37 | ), 38 | 'updated' => array( 39 | 'description' => 'Date Updated', 40 | 'column' => 'updated', 41 | 'type' => 'datetime' 42 | ) 43 | ); 44 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PhinxMigration.php: -------------------------------------------------------------------------------- 1 | setPrefix($_SERVER['DB_TABLE_PREFIX']); 25 | } 26 | } 27 | 28 | /** 29 | * Set the current prefix value 30 | * 31 | * @param string $prefix Table prefix value 32 | */ 33 | public function setPrefix($prefix) 34 | { 35 | $this->prefix = $prefix; 36 | } 37 | 38 | /** 39 | * Get the current prefix value 40 | * If defined, returns the value plus an underscore ("_") 41 | * 42 | * @return string Formatted prefix or empty string 43 | */ 44 | public function getPrefix() 45 | { 46 | return (strlen($this->prefix) > 0) ? $this->prefix.'_' : ''; 47 | } 48 | 49 | /** 50 | * Get the current table name value 51 | * 52 | * @return string Name of current migration's table 53 | */ 54 | public function getTableName() 55 | { 56 | return $this->getPrefix().$this->tableName; 57 | } 58 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PolicyCollection.php: -------------------------------------------------------------------------------- 1 | getDb()->fetch($sql); 19 | 20 | foreach ($results as $result) { 21 | $policy = new PolicyModel($this->getDb(), $result); 22 | $this->add($policy); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/PolicyModel.php: -------------------------------------------------------------------------------- 1 | array( 30 | 'description' => 'User ID', 31 | 'column' => 'id', 32 | 'type' => 'integer' 33 | ), 34 | 'expression' => array( 35 | 'description' => 'Policy Expression', 36 | 'column' => 'expression', 37 | 'type' => 'string' 38 | ), 39 | 'description' => array( 40 | 'description' => 'Policy Description', 41 | 'column' => 'description', 42 | 'type' => 'string' 43 | ), 44 | 'name' => array( 45 | 'description' => 'Policy Name', 46 | 'column' => 'name', 47 | 'type' => 'string' 48 | ), 49 | 'created' => array( 50 | 'description' => 'Date Created', 51 | 'column' => 'created', 52 | 'type' => 'datetime' 53 | ), 54 | 'updated' => array( 55 | 'description' => 'Date Updated', 56 | 'column' => 'updated', 57 | 'type' => 'datetime' 58 | ), 59 | ); 60 | 61 | public function evaluate($data, $expression = null) 62 | { 63 | if ($this->id === null) { 64 | throw new \InvalidArgumentException('Policy not loaded!'); 65 | } 66 | $expression = ($expression === null) ? $this->expression : $expression; 67 | if (!is_array($data)) { 68 | $data = array($data); 69 | } 70 | $context = array(); 71 | foreach ($data as $index => $item) { 72 | if (is_numeric($index)) { 73 | // Resolve it to a class name 74 | $ns = explode('\\', get_class($item)); 75 | $index = str_replace('Model', '', array_pop($ns)); 76 | } 77 | $context[strtolower($index)] = $item; 78 | } 79 | 80 | $language = new ExpressionLanguage(); 81 | try { 82 | return $language->evaluate($expression, $context); 83 | } catch (\Exception $e) { 84 | throw new Exception\InvalidExpressionException($e->getMessage()); 85 | } 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Provider/Laravel.php: -------------------------------------------------------------------------------- 1 | $config['driver'], 24 | 'username' => $config['username'], 25 | 'password' => $config['password'], 26 | 'host' => $config['host'], 27 | 'name' => 'gatekeeper' 28 | )); 29 | } 30 | 31 | /** 32 | * Retrieve a user by their unique identifier. 33 | * 34 | * @param integer $identifier User ID 35 | * @return \Illuminate\Auth\UserInterface 36 | */ 37 | public function retrieveByID($identifier) 38 | { 39 | $user = Gatekeeper::modelFactory('UserModel'); 40 | $user->findById($identifier); 41 | return $this->returnUser($user); 42 | } 43 | 44 | /** 45 | * Retrieve a user by the given credentials. 46 | * 47 | * @param array $credentials 48 | * @return \Illuminate\Auth\UserInterface 49 | */ 50 | public function retrieveByCredentials(array $credentials) 51 | { 52 | $user = Gatekeeper::modelFactory('UserModel'); 53 | $result = $user->findByUsername($credentials['username']); 54 | return $this->returnUser($user); 55 | } 56 | 57 | /** 58 | * Validate a user against the given credentials. 59 | * 60 | * @param \Illuminate\Auth\UserInterface $user 61 | * @param array $credentials 62 | * @return bool 63 | */ 64 | public function validateCredentials(\Illuminate\Auth\UserInterface $user, array $credentials) 65 | { 66 | return Gatekeeper::authenticate($credentials); 67 | } 68 | 69 | /** 70 | * Get the current "remember me" token value 71 | * 72 | * @param string $identifier Identifier for token location 73 | * @param string $token [description] 74 | * @throws \Exception Not implemented 75 | */ 76 | public function retrieveByToken($identifier, $token) 77 | { 78 | return new \Exception('not implemented'); 79 | } 80 | 81 | /** 82 | * Update the "remember me" token (not supported...yet) 83 | * 84 | * @param UserInterface $user User object 85 | * @param string $token Token string 86 | * @throws \Exception Not implemented 87 | */ 88 | public function updateRememberToken(UserInterface $user, $token) 89 | { 90 | return new \Exception('not implemented'); 91 | } 92 | 93 | /** 94 | * Reformat the Gatekeeper user into the Laravel user format 95 | * 96 | * @param \Psecio\Gatekeeper\UserModel $user User model instance 97 | * @return GenericUser instance 98 | */ 99 | protected function returnUser(\Psecio\Gatekeeper\UserModel $user) 100 | { 101 | $attrs = array( 102 | 'id' => $user->id, 103 | 'username' => $user->username, 104 | 'password' => $user->password, 105 | 'name' => $user->firstName.' '.$user->lastName, 106 | 'email' => $user->email 107 | ); 108 | return new GenericUser($attrs); 109 | } 110 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Provider/Laravel5/AuthManager.php: -------------------------------------------------------------------------------- 1 | app['config']['auth.driver']; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Provider/Laravel5/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | env('GATEKEEPER_USER'), 22 | 'password' => env('GATEKEEPER_PASS'), 23 | 'host' => env('GATEKEEPER_HOST'), 24 | 'name' => env('GATEKEEPER_DATABASE'), 25 | ); 26 | Gatekeeper::init(null, $config); 27 | } 28 | 29 | /** 30 | * Boot the provider, adding the "gatekeeper" type to the Auth handling 31 | * 32 | * @param Router $router Laravel router instance 33 | */ 34 | public function boot(Router $router) 35 | { 36 | // Add Gatekeeper to the Auth provider list 37 | Auth::extend('gatekeeper', function($app) { 38 | return new UserProvider(); 39 | }); 40 | 41 | // Create a new "unique" (gkunique) validator for unique user checking 42 | Validator::extend('gk_unique', function($attribute, $value, $parameters) { 43 | $type = (isset($parameters[0])) ? $parameters[0] : 'user'; 44 | 45 | // strip a training "s" if there is one 46 | if (substr($type, -1) === 's') { 47 | $type = substr($type, 0, strlen($type)-1); 48 | } 49 | $method = 'find'.ucwords($type).'By'.ucwords($attribute); 50 | try { 51 | $user = Gatekeeper::$method($value); 52 | return ($user === false); 53 | } catch (\Exception $e) { 54 | return false; 55 | } 56 | }); 57 | 58 | parent::boot($router); 59 | } 60 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Provider/Laravel5/UserAuthenticatable.php: -------------------------------------------------------------------------------- 1 | model = $model; 32 | } 33 | 34 | /** 35 | * Allow the fetching of properties directly from the model 36 | * 37 | * @param string $name Name of the property to fetch 38 | * @return mixed Property value 39 | */ 40 | public function __get($name) 41 | { 42 | return $this->model->$name; 43 | } 44 | 45 | public function getModel() 46 | { 47 | return $this->model; 48 | } 49 | 50 | /** 51 | * Allow for the direct calling of methods on the object 52 | * 53 | * @param string $name Function name 54 | * @param array $args Function arguments 55 | * @return mixed Function call return value 56 | */ 57 | public function __call($name, array $args) 58 | { 59 | return call_user_func_array([$this->model, $name], $args); 60 | } 61 | 62 | /** 63 | * Get the primary identifier for the curent user 64 | * 65 | * @return string Username 66 | */ 67 | public function getAuthIdentifier() 68 | { 69 | return $this->model->username; 70 | } 71 | 72 | /** 73 | * Get the current user's password (hashed value) 74 | * 75 | * @return string Hashed password string 76 | */ 77 | public function getAuthPassword() 78 | { 79 | return $this->model->password; 80 | } 81 | 82 | /** 83 | * Get the current token value for the "remember me" handling 84 | * 85 | * @return string Token value (hash) 86 | */ 87 | public function getRememberToken() 88 | { 89 | $tokens = $this->model->authTokens; 90 | if (isset($tokens[0])) { 91 | return $tokens[0]->token; 92 | } 93 | } 94 | 95 | /** 96 | * Set the "remember me" token value 97 | * 98 | * @param string $value Token value 99 | */ 100 | public function setRememberToken($value) 101 | { 102 | $tokens = $this->model->authTokens; 103 | if (isset($tokens[0])) { 104 | $token = $tokens[0]; 105 | $token->token($value); 106 | $token->save(); 107 | } else { 108 | // No token found, make one 109 | $token = new AuthTokenModel(Gatekeeper::getDatasource(), [ 110 | 'token' => $value, 111 | 'user_id' => $this->model->id, 112 | 'expires' => strtotime('+14 days') 113 | ]); 114 | $token->save(); 115 | } 116 | } 117 | 118 | /** 119 | * Get the name for the current "remember me" token 120 | * 121 | * @return string Token name 122 | */ 123 | public function getRememberTokenName() 124 | { 125 | return $this->tokenName; 126 | } 127 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Provider/Laravel5/UserProvider.php: -------------------------------------------------------------------------------- 1 | authTokens; 43 | 44 | if ($user === false || (isset($tokens[0]) && $tokens[0]->token !== $token)) { 45 | return null; 46 | } 47 | return new UserAuthenticatable($user); 48 | } 49 | 50 | /** 51 | * Update the user's "remember me" token value 52 | * 53 | * @param Authenticatable $user User instance 54 | * @param string $token Token value 55 | * @return ? 56 | */ 57 | public function updateRememberToken(Authenticatable $user, $token) 58 | { 59 | $tokens = $user->getModel()->authTokens; 60 | 61 | if (isset($tokens[0])) { 62 | $token = $tokens[0]; 63 | $token->token($token); 64 | $token->save(); 65 | } 66 | } 67 | 68 | /** 69 | * Return \Illuminate\Contracts\Auth\Authenticatable 70 | * 71 | * @param array $credentials Credentials to use in locating the user 72 | * @return \Illuminate\Contracts\Auth\Authenticatable instance|null 73 | */ 74 | public function retrieveByCredentials(array $credentials) 75 | { 76 | if (isset($credentials['email'])) { 77 | $user = Gatekeeper::findUserByEmail($credentials['email']); 78 | } elseif (isset($credentials['username'])) { 79 | $user = Gatekeeper::findUserByUsername($credentials['username']); 80 | } 81 | if ($user === false) { 82 | return null; 83 | } 84 | $userAuth = new UserAuthenticatable($user); 85 | return $userAuth; 86 | } 87 | 88 | /** 89 | * Validate a user against the given credentials. 90 | * 91 | * @param \Illuminate\Contracts\Auth\Authenticatable $user 92 | * @param array $credentials 93 | * @return bool 94 | */ 95 | public function validateCredentials(Authenticatable $user, array $credentials) 96 | { 97 | $username = $user->getAuthIdentifier(); 98 | $credentials = [ 99 | 'username' => $username, 100 | 'password' => $credentials['password'] 101 | ]; 102 | return Gatekeeper::authenticate($credentials); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Resolve.php: -------------------------------------------------------------------------------- 1 | permissions; 18 | 19 | // Now find the ones in the user's groups too 20 | foreach ($user->groups as $group) { 21 | foreach ($group->permissions as $permission) { 22 | $permissions->add($permission); 23 | } 24 | } 25 | 26 | return $permissions; 27 | } 28 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Restrict/Ip.php: -------------------------------------------------------------------------------- 1 | getConfig(); 20 | 21 | if ($this->check($config, 'DENY', $ip) === true) { 22 | return false; 23 | } 24 | if ($this->check($config, 'ALLOW', $ip) === false) { 25 | return false; 26 | } 27 | return true; 28 | } 29 | 30 | /** 31 | * Check to see if the value matches against the configuration type 32 | * 33 | * @param array $config Configuration options 34 | * @param string $type Configuration type (ALLOW or DENY) 35 | * @param string $value Value to compare against 36 | * @return boolean Found/not found by matching 37 | */ 38 | public function check(array $config, $type, $value) 39 | { 40 | if (!isset($config[$type])) { 41 | return false; 42 | } 43 | $found = false; 44 | $config = (!is_array($config[$type])) ? array($config[$type]) : $config[$type]; 45 | 46 | foreach ($config as $pattern) { 47 | $result = $this->validateIpContains($value, $pattern); 48 | if ($result === true && $found === false) { 49 | $found = true; 50 | } 51 | } 52 | return $found; 53 | } 54 | 55 | /** 56 | * Evaluate to see if the pattern given matches the IP address value 57 | * 58 | * @param string $ipAddress IPv4 address 59 | * @param string $pattern Pattern to match against 60 | * @return boolean Contains/does not contain 61 | */ 62 | public function validateIpContains($ipAddress, $pattern) 63 | { 64 | // Replace wildcards (*) with regex matches and escape dots 65 | $pattern = str_replace(array('.', '*'), array('\.', '.+'), $pattern); 66 | return (preg_match('#'.$pattern.'#', $ipAddress) == true); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Restrict/Throttle.php: -------------------------------------------------------------------------------- 1 | getConfig(); 17 | $throttle = \Psecio\Gatekeeper\Gatekeeper::getUserThrottle($config['userId']); 18 | $throttle->updateAttempts(); 19 | $this->model = $throttle; 20 | 21 | // See if they're blocked 22 | if ($throttle->status === \Psecio\Gatekeeper\ThrottleModel::STATUS_BLOCKED) { 23 | $result = $throttle->checkTimeout(); 24 | if ($result === false) { 25 | return false; 26 | } 27 | } else { 28 | $result = $throttle->checkAttempts(); 29 | if ($result === false) { 30 | return false; 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Restriction.php: -------------------------------------------------------------------------------- 1 | setConfig($config); 21 | } 22 | 23 | /** 24 | * Set the configuration property 25 | * 26 | * @param array $config Configuration settings 27 | */ 28 | public function setConfig(array $config) 29 | { 30 | $this->config = $config; 31 | } 32 | 33 | /** 34 | * Get the confguration settings 35 | * 36 | * @return array Configuration settings 37 | */ 38 | public function getConfig() 39 | { 40 | return $this->config; 41 | } 42 | 43 | /** 44 | * Evaluate the restriction based on given data 45 | * 46 | * @return boolean Pass/fail of restriction 47 | */ 48 | abstract public function evaluate(); 49 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/SecurityQuestionCollection.php: -------------------------------------------------------------------------------- 1 | $userId); 15 | $sql = 'select * from '.$this->getPrefix().'security_questions where user_id = :userId'; 16 | 17 | $results = $this->getDb()->fetch($sql, $data); 18 | 19 | foreach ($results as $result) { 20 | $question = new SecurityQuestionModel($this->getDb(), $result); 21 | $this->add($question); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/SecurityQuestionModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Question ID', 20 | 'column' => 'id', 21 | 'type' => 'integer' 22 | ), 23 | 'question' => array( 24 | 'description' => 'Security Question', 25 | 'column' => 'question', 26 | 'type' => 'varchar' 27 | ), 28 | 'answer' => array( 29 | 'description' => 'Security Answer', 30 | 'column' => 'answer', 31 | 'type' => 'varchar' 32 | ), 33 | 'userId' => array( 34 | 'description' => 'User ID', 35 | 'column' => 'user_id', 36 | 'type' => 'varchar' 37 | ), 38 | 'created' => array( 39 | 'description' => 'Date Created', 40 | 'column' => 'created', 41 | 'type' => 'datetime' 42 | ), 43 | 'updated' => array( 44 | 'description' => 'Date Updated', 45 | 'column' => 'updated', 46 | 'type' => 'datetime' 47 | ) 48 | ); 49 | 50 | /** 51 | * Hash the answer 52 | * 53 | * @param string $value Answer to question 54 | * @return string Hashed answer 55 | */ 56 | public function preAnswer($value) 57 | { 58 | if (password_needs_rehash($value, PASSWORD_DEFAULT) === true) { 59 | $value = password_hash($value, PASSWORD_DEFAULT); 60 | } 61 | return $value; 62 | } 63 | 64 | /** 65 | * Verify the answer to the question 66 | * 67 | * @param string $value Answer input from user 68 | * @return boolean Match/no match on answer 69 | */ 70 | public function verifyAnswer($value) 71 | { 72 | if ($this->id === null) { 73 | return false; 74 | } 75 | return password_verify($value, $this->answer); 76 | } 77 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/Session/RememberMe.php: -------------------------------------------------------------------------------- 1 | datasource = $datasource; 47 | 48 | if (!empty($data)) { 49 | $this->data = $data; 50 | } 51 | if ($user !== null) { 52 | $this->user = $user; 53 | } 54 | if (isset($this->data['interval'])) { 55 | $this->expireInterval = $this->data['interval']; 56 | } 57 | } 58 | 59 | /** 60 | * Get the current data for the evaluation 61 | */ 62 | public function getData() 63 | { 64 | return $this->data; 65 | } 66 | 67 | /** 68 | * Get the current user for evaluation 69 | */ 70 | public function getUser() 71 | { 72 | return $this->user; 73 | } 74 | 75 | /** 76 | * Get the current expiration interval 77 | */ 78 | public function getExpireInterval() 79 | { 80 | return $this->expireInterval; 81 | } 82 | 83 | /** 84 | * Setup the "remember me" session and cookies 85 | * 86 | * @param \Psecio\Gatekeeper\UserModel|null $user User model instance [optional] 87 | * @return boolean Success/fail of setting up the session/cookies 88 | */ 89 | public function setup(\Psecio\Gatekeeper\UserModel $user = null) 90 | { 91 | $user = ($user === null) ? $this->user : $user; 92 | $userToken = $this->getUserToken($user); 93 | 94 | if ($userToken->id !== null || $this->isExpired($userToken)) { 95 | return false; 96 | } 97 | $token = $this->generateToken(); 98 | $tokenModel = $this->saveToken($token, $user); 99 | if ($tokenModel === false) { 100 | return false; 101 | } 102 | $this->setCookies($tokenModel, $token); 103 | 104 | return true; 105 | } 106 | 107 | /** 108 | * Verify the token if it exists 109 | * Removes the old token and sets up a new one if valid 110 | * 111 | * @param \Psecio\Gatekeeper\AuthTokenModel $token Token model instance 112 | * @return boolean Pass/fail result of the validation 113 | */ 114 | public function verify(\Psecio\Gatekeeper\AuthTokenModel $token = null) 115 | { 116 | if (!isset($this->data[$this->tokenName])) { 117 | return false; 118 | } 119 | 120 | if ($token === null) { 121 | $tokenParts = explode(':', $this->data[$this->tokenName]); 122 | $token = $this->getById($tokenParts[0]); 123 | } 124 | 125 | if ($token === false) { 126 | return false; 127 | } 128 | 129 | $user = $token->user; 130 | $userToken = $token->token; 131 | 132 | // Remove the token (a new one will be made later) 133 | $this->datasource->delete($token); 134 | 135 | if (\Psecio\Gatekeeper\Gatekeeper::hash_equals($this->data[$this->tokenName], $token->id.':'.hash('sha256', $userToken)) === false) { 136 | return false; 137 | } 138 | 139 | $this->setup($user); 140 | return $user; 141 | } 142 | 143 | /** 144 | * Get the token information searching on given token string 145 | * 146 | * @param string $tokenValue Token string for search 147 | * @return boolean|\Psecio\Gatekeeper\AuthTokenModel Instance if no query errors 148 | */ 149 | public function getByToken($tokenValue) 150 | { 151 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource); 152 | $result = $this->datasource->find($token, array('token' => $tokenValue)); 153 | return $result; 154 | } 155 | 156 | /** 157 | * Get a token by its unique ID 158 | * 159 | * @param integer $tokenId Token ID 160 | * @return boolean|\Psecio\Gatekeeper\AuthTokenModel instance 161 | */ 162 | public function getById($tokenId) 163 | { 164 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource); 165 | $result = $this->datasource->find($token, array('id' => $tokenId)); 166 | return $result; 167 | } 168 | 169 | /** 170 | * Get the token by user ID 171 | * Also performs evaluation to check if token is expired, returns false if so 172 | * 173 | * @param \Psecio\Gatekeeper\UserModel $user User model instance 174 | * @return boolean|\Psecio\Gatekeeper\AuthTokenModel instance 175 | */ 176 | public function getUserToken(\Psecio\Gatekeeper\UserModel $user) 177 | { 178 | $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource); 179 | return $this->datasource->find($tokenModel, array('userId' => $user->id)); 180 | } 181 | 182 | /** 183 | * Check to see if the token has expired 184 | * 185 | * @param \Psecio\Gatekeeper\AuthTokenModel $token Token model instance 186 | * @param boolean $delete Delete/don't delete the token if expired [optional] 187 | * @return boolean Token expired/not expired 188 | */ 189 | public function isExpired(\Psecio\Gatekeeper\AuthTokenModel $token, $delete = true) 190 | { 191 | if ($token->expires !== null && new \Datetime() > new \DateTime($token->expires)) { 192 | if ($delete === true) { 193 | $this->deleteToken($token->token); 194 | } 195 | return true; 196 | } 197 | return false; 198 | } 199 | 200 | /** 201 | * Save the new token to the data source 202 | * 203 | * @param string $token Token string 204 | * @param \Psecio\Gatekeeper\UserModel $user User model instance 205 | * @return boolean|\Psecio\Gatekeeper\AuthTokenModel Success/fail of token creation or AuthTokenModel instance 206 | */ 207 | public function saveToken($token, \Psecio\Gatekeeper\UserModel $user) 208 | { 209 | $expires = new \DateTime($this->expireInterval); 210 | $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource, array( 211 | 'token' => $token, 212 | 'userId' => $user->id, 213 | 'expires' => $expires->format('Y-m-d H:i:s') 214 | )); 215 | $result = $this->datasource->save($tokenModel); 216 | return ($result === false) ? false : $tokenModel; 217 | } 218 | 219 | /** 220 | * Delete the token by token string 221 | * 222 | * @param string $token Token hash string 223 | * @return boolean Success/fail of token record deletion 224 | */ 225 | public function deleteToken($token) 226 | { 227 | $tokenModel = new \Psecio\Gatekeeper\AuthTokenModel($this->datasource); 228 | $token = $this->datasource->find($tokenModel, array('token' => $token)); 229 | if ($token !== false) { 230 | return $this->datasource->delete($token); 231 | } 232 | return false; 233 | } 234 | 235 | /** 236 | * Generate the token value 237 | * 238 | * @return string Token hash 239 | */ 240 | public function generateToken() 241 | { 242 | $factory = new \RandomLib\Factory; 243 | $generator = $factory->getMediumStrengthGenerator(); 244 | 245 | return base64_encode($generator->generate(24)); 246 | } 247 | 248 | /** 249 | * Set the cookies with the main and auth tokens 250 | * 251 | * @param \Psecio\Gatekeeper\AuthTokenModel $tokenModel Auth token model instance 252 | * @param string $token Token hash 253 | * @param boolean $https Enable/disable HTTPS setting on cookies [optional] 254 | * @param string $domain Domain value to set cookies on 255 | */ 256 | public function setCookies(\Psecio\Gatekeeper\AuthTokenModel $tokenModel, $token, $https = false, $domain = null) 257 | { 258 | if ($domain === null && isset($_SERVER['HTTP_HOST'])) { 259 | $domain = ($_SERVER['HTTP_HOST'] != 'localhost') ? $_SERVER['HTTP_HOST'] : false; 260 | } 261 | 262 | $tokenValue = $tokenModel->id.':'.hash('sha256', $token); 263 | $expires = new \DateTime($this->expireInterval); 264 | return setcookie($this->tokenName, $tokenValue, $expires->format('U'), '/', $domain, $https, true); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/ThrottleModel.php: -------------------------------------------------------------------------------- 1 | array( 37 | 'description' => 'Record ID', 38 | 'column' => 'id', 39 | 'type' => 'integer' 40 | ), 41 | 'userId' => array( 42 | 'description' => 'User ID', 43 | 'column' => 'user_id', 44 | 'type' => 'integer' 45 | ), 46 | 'attempts' => array( 47 | 'description' => 'Number of Attempts', 48 | 'column' => 'attempts', 49 | 'type' => 'integer' 50 | ), 51 | 'status' => array( 52 | 'description' => 'Throttle status', 53 | 'column' => 'status', 54 | 'type' => 'string' 55 | ), 56 | 'lastAttempt' => array( 57 | 'description' => 'Last Attempt', 58 | 'column' => 'last_attempt', 59 | 'type' => 'datetime' 60 | ), 61 | 'statusChange' => array( 62 | 'description' => 'Date of Last Status Change', 63 | 'column' => 'status_change', 64 | 'type' => 'datetime' 65 | ), 66 | 'created' => array( 67 | 'description' => 'Date Created', 68 | 'column' => 'created', 69 | 'type' => 'datetime' 70 | ), 71 | 'updated' => array( 72 | 'description' => 'Date Updated', 73 | 'column' => 'updated', 74 | 'type' => 'datetime' 75 | ) 76 | ); 77 | 78 | /** 79 | * Find the throttle information for the given user ID 80 | * 81 | * @param integer $userId User ID 82 | * @return boolean Success/fail of find call 83 | */ 84 | public function findByUserId($userId) 85 | { 86 | return $this->getDb()->find( 87 | $this, array('user_id' => $userId) 88 | ); 89 | } 90 | 91 | /** 92 | * Update the number of attempts for the current record 93 | * 94 | * @return boolean Success/fail of save operation 95 | */ 96 | public function updateAttempts() 97 | { 98 | $this->lastAttempt = date('Y-m-d H:i:s'); 99 | $this->attempts = $this->attempts + 1; 100 | return $this->getDb()->save($this); 101 | } 102 | 103 | /** 104 | * Mark a user as allowed (status change) 105 | * 106 | * @return boolean Success/fail of save operation 107 | */ 108 | public function allow() 109 | { 110 | $this->statusChange = date('Y-m-d H:i:s'); 111 | $this->status = ThrottleModel::STATUS_ALLOWED; 112 | return $this->getDb()->save($this); 113 | } 114 | 115 | // See how long it was since the last change (to blocked) 116 | /** 117 | * Check the timeout to see if it has passed 118 | * 119 | * @param string $timeout Alternative timeout string (ex: "-1 minute") 120 | * @return boolean True if user is reendabled, false if still disabled 121 | */ 122 | public function checkTimeout($timeout = null) 123 | { 124 | $timeout = ($timeout === null) ? $this->timeout : $timeout; 125 | 126 | $lastChange = new \DateTime($this->statusChange); 127 | $timeout = new \DateTime($timeout); 128 | 129 | if ($lastChange <= $timeout) { 130 | $this->allow(); 131 | return true; 132 | } 133 | return false; 134 | } 135 | 136 | /** 137 | * Check the number of attempts to see if it meets the threshold 138 | * 139 | * @return boolean False if they were blocked, true otherwise 140 | */ 141 | public function checkAttempts() 142 | { 143 | if ($this->attempts >= $this->allowedAttemts) { 144 | $this->status = ThrottleModel::STATUS_BLOCKED; 145 | return $this->getDb()->save($this); 146 | } 147 | return true; 148 | } 149 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/UserCollection.php: -------------------------------------------------------------------------------- 1 | getPrefix(); 15 | $data = array('groupId' => $groupId); 16 | $sql = 'select u.* from '.$prefix.'users u, '.$prefix.'user_group ug' 17 | .' where ug.group_id = :groupId' 18 | .' and ug.user_id = u.id'; 19 | 20 | $results = $this->getDb()->fetch($sql, $data); 21 | 22 | foreach ($results as $result) { 23 | $user = new UserModel($this->getDb(), $result); 24 | $this->add($user); 25 | } 26 | } 27 | 28 | /** 29 | * Find the users that have a permission defined by the 30 | * given ID 31 | * 32 | * @param integer $permId Permission ID 33 | */ 34 | public function findUsersByPermissionId($permId) 35 | { 36 | $prefix = $this->getPrefix(); 37 | $data = array('permId' => $permId); 38 | $sql = 'select u.* from '.$prefix.'users u, '.$prefix.'user_permission up' 39 | .' where up.permission_id = :permId' 40 | .' and up.user_id = u.id'; 41 | 42 | $results = $this->getDb()->fetch($sql, $data); 43 | 44 | foreach ($results as $result) { 45 | $user = new UserModel($this->getDb(), $result); 46 | $this->add($user); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/UserGroupCollection.php: -------------------------------------------------------------------------------- 1 | getPrefix(); 15 | $data = array('userId' => $userId); 16 | $sql = 'select g.* from '.$prefix.'groups g, '.$prefix.'user_group ug' 17 | .' where ug.user_id = :userId' 18 | .' and ug.group_id = g.id'; 19 | 20 | $results = $this->getDb()->fetch($sql, $data); 21 | 22 | foreach ($results as $result) { 23 | $group = new GroupModel($this->getDb(), $result); 24 | $this->add($group); 25 | } 26 | } 27 | 28 | /** 29 | * Create relational records linking the user and group 30 | * 31 | * @param \Gatekeeper\UserModel $model Model instance 32 | * @param array $data Data to use in create 33 | */ 34 | public function create($model, array $data) 35 | { 36 | foreach ($data as $group) { 37 | // Determine if it's an integer (permissionId) or name 38 | if (is_int($group) === true) { 39 | $where = 'id = :id'; 40 | $dbData = array('id' => $group); 41 | } else { 42 | $where = 'name = :name'; 43 | $dbData = array('name' => $group); 44 | } 45 | 46 | $sql = 'select id, name from '.$this->getPrefix().'groups where '.$where; 47 | $results = $this->getDb()->fetch($sql, $dbData); 48 | if (!empty($results) && count($results) == 1) { 49 | // exists, make the relation 50 | $model = new UserGroupModel( 51 | $this->getDb(), 52 | array('groupId' => $results[0]['id'], 'userId' => $model->id) 53 | ); 54 | if ($this->getDb()->save($model) === true) { 55 | $this->add($model); 56 | } 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/UserGroupModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Group Id', 20 | 'column' => 'group_id', 21 | 'type' => 'integer' 22 | ), 23 | 'userId' => array( 24 | 'description' => 'User ID', 25 | 'column' => 'user_id', 26 | 'type' => 'integer' 27 | ), 28 | 'id' => array( 29 | 'description' => 'ID', 30 | 'column' => 'id', 31 | 'type' => 'integer' 32 | ), 33 | 'expire' => array( 34 | 'description' => 'Expiration Date', 35 | 'column' => 'expire', 36 | 'type' => 'datetime' 37 | ), 38 | 'created' => array( 39 | 'description' => 'Date Created', 40 | 'column' => 'created', 41 | 'type' => 'datetime' 42 | ), 43 | 'updated' => array( 44 | 'description' => 'Date Updated', 45 | 'column' => 'updated', 46 | 'type' => 'datetime' 47 | ) 48 | ); 49 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/UserPermissionCollection.php: -------------------------------------------------------------------------------- 1 | getPrefix(); 15 | $data = array('userId' => $userId); 16 | $sql = 'select p.* from '.$prefix.'permissions p, '.$prefix.'user_permission up' 17 | .' where p.id = up.permission_id' 18 | .' and up.user_id = :userId' 19 | .' and (up.expire >= UNIX_TIMESTAMP(NOW()) or up.expire is null)'; 20 | 21 | $results = $this->getDb()->fetch($sql, $data); 22 | 23 | foreach ($results as $result) { 24 | $perm = new PermissionModel($this->getDb(), $result); 25 | $this->add($perm); 26 | } 27 | } 28 | 29 | /** 30 | * Create relational records linking the user and permission 31 | * 32 | * @param \Gatekeeper\UserModel $model Model instance 33 | * @param array $data Data to use in create 34 | */ 35 | public function create($model, array $data) 36 | { 37 | foreach ($data as $permission) { 38 | // Determine if it's an integer (permissionId) or name 39 | if (is_int($permission) === true) { 40 | $where = 'id = :id'; 41 | $dbData = array('id' => $permission); 42 | } else { 43 | $where = 'name = :name'; 44 | $dbData = array('name' => $permission); 45 | } 46 | 47 | $sql = 'select id, name from '.$this->getPrefix().'permissions where '.$where; 48 | $results = $this->getDb()->fetch($sql, $dbData); 49 | if (!empty($results) && count($results) == 1) { 50 | // exists, make the relation 51 | $model = new UserPermissionModel( 52 | $this->getDb(), 53 | array('permissionId' => $results[0]['id'], 'userId' => $model->id) 54 | ); 55 | $this->getDb()->save($model); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Psecio/Gatekeeper/UserPermissionModel.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'description' => 'Permission Id', 20 | 'column' => 'permission_id', 21 | 'type' => 'integer' 22 | ), 23 | 'userId' => array( 24 | 'description' => 'User ID', 25 | 'column' => 'user_id', 26 | 'type' => 'integer' 27 | ), 28 | 'id' => array( 29 | 'description' => 'ID', 30 | 'column' => 'id', 31 | 'type' => 'integer' 32 | ), 33 | 'expire' => array( 34 | 'description' => 'Expiration Date', 35 | 'column' => 'expire', 36 | 'type' => 'datetime' 37 | ), 38 | 'created' => array( 39 | 'description' => 'Date Created', 40 | 'column' => 'created', 41 | 'type' => 'datetime' 42 | ), 43 | 'updated' => array( 44 | 'description' => 'Date Updated', 45 | 'column' => 'updated', 46 | 'type' => 'datetime' 47 | ) 48 | ); 49 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/Base.php: -------------------------------------------------------------------------------- 1 | getMockBuilder('\Psecio\Gatekeeper\DataSource\Stub') 10 | ->setConstructorArgs(array(array())) 11 | ->getMock(); 12 | $ds->method($type) 13 | ->willReturn($return); 14 | 15 | return $ds; 16 | } 17 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/DataSource/MysqlTest.php: -------------------------------------------------------------------------------- 1 | 'foo', 14 | 'password' => 'bar', 15 | 'name' => 'dbname', 16 | 'host' => '127.0.0.1' 17 | ); 18 | $pdo = $this->getMockBuilder('\Psecio\Gatekeeper\MockPdo')->getMock(); 19 | 20 | $mysql = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 21 | ->setConstructorArgs(array($config, $pdo)) 22 | ->setMethods(array('buildPdo')) 23 | ->getMock(); 24 | 25 | $this->assertEquals($mysql->getDb(), $pdo); 26 | } 27 | 28 | /** 29 | * Test the getter/setter of the DB instance 30 | * (just uses a basic object) 31 | */ 32 | public function testGetSetDatabaseInstance() 33 | { 34 | $mysql = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 35 | ->disableOriginalConstructor() 36 | ->setMethods(array('buildPdo')) 37 | ->getMock(); 38 | 39 | $db = (object)array('test' => 'foo'); 40 | $mysql->setDb($db); 41 | 42 | $this->assertEquals($mysql->getDb(), $db); 43 | } 44 | 45 | /** 46 | * Test getting the table name for the model instance 47 | */ 48 | public function testGetTableName() 49 | { 50 | $config = array(); 51 | $pdo = $this->getMockBuilder('\Psecio\Gatekeeper\MockPdo')->getMock(); 52 | 53 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 54 | ->setConstructorArgs(array($config, $pdo)) 55 | ->setMethods(array('buildPdo')) 56 | ->getMock(); 57 | 58 | $mysql = new \Psecio\Gatekeeper\MockModel($ds); 59 | $this->assertEquals('test', $mysql->getTableName()); 60 | } 61 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/DataSourceTest.php: -------------------------------------------------------------------------------- 1 | 'foo'); 13 | $ds = $this->getMockForAbstractClass('\Psecio\Gatekeeper\DataSource', array($config)); 14 | 15 | $ds->setConfig($config); 16 | $this->assertEquals($ds->getConfig(), $config); 17 | } 18 | 19 | /** 20 | * Test the setting for the data source configuration in constructor 21 | */ 22 | public function testGetSetConfigConstruct() 23 | { 24 | $config = array('test' => 'foo'); 25 | $ds = $this->getMockForAbstractClass('\Psecio\Gatekeeper\DataSource', array($config)); 26 | $this->assertEquals($ds->getConfig(), $config); 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/GatekeeperTest.php: -------------------------------------------------------------------------------- 1 | 1); 10 | // Gatekeeper::init(null, $config); 11 | } 12 | public function tearDown() 13 | { 14 | 15 | } 16 | 17 | /** 18 | * Test the getter/setter for datasources 19 | */ 20 | public function testGetSetDatasource() 21 | { 22 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Stub') 23 | ->disableOriginalConstructor() 24 | ->setMethods(array('find', 'delete')) 25 | ->getMock(); 26 | 27 | Gatekeeper::setDatasource($ds); 28 | $this->assertEquals(Gatekeeper::getDatasource(), $ds); 29 | } 30 | 31 | /** 32 | * Test the enable/disable of throttling 33 | */ 34 | public function testEnableDisableThrottle() 35 | { 36 | Gatekeeper::disableThrottle(); 37 | $this->assertFalse(Gatekeeper::throttleStatus()); 38 | 39 | Gatekeeper::enableThrottle(); 40 | $this->assertTrue(Gatekeeper::throttleStatus()); 41 | } 42 | 43 | /** 44 | * Test getting the user's throttle information (model instance) 45 | */ 46 | // public function testGetUserThrottle() 47 | // { 48 | // $userId = 42; 49 | 50 | // // This is our model that will be returned 51 | // $ds = $this->buildMock(null); 52 | // $throttle1 = new ThrottleModel($ds, array('userId' => $userId)); 53 | 54 | // $ds = $this->buildMock($throttle1, 'find'); 55 | // $throttle = new ThrottleModel($ds); 56 | 57 | // $gk = $this->getMockBuilder('\Psecio\Gatekeeper\Gatekeeper') 58 | // ->setMethods(array('findThrottleByUserId')) 59 | // ->getMock(); 60 | 61 | // $config = array('name' => 'test'); 62 | // $gk::init(null, $config, $ds); 63 | 64 | // $gk->method('findThrottleByUserId') 65 | // ->willReturn($throttle); 66 | 67 | // $result = $gk::getUserThrottle($userId); 68 | // $this->assertEquals(42, $result->userId); 69 | // } 70 | 71 | /** 72 | * Test that a restriction is correctly made 73 | */ 74 | public function testCreateRestriction() 75 | { 76 | Gatekeeper::restrict('ip', array()); 77 | $restrict = Gatekeeper::getRestrictions(); 78 | $this->assertCount(1, $restrict); 79 | $this->assertTrue($restrict[0] instanceof \Psecio\Gatekeeper\Restrict\Ip); 80 | } 81 | 82 | /** 83 | * Test the creation of an invalid (not found) restriction 84 | * 85 | * @expectedException \InvalidArgumentException 86 | */ 87 | public function testCreateInvalidRestriction() 88 | { 89 | Gatekeeper::restrict('foobar', array()); 90 | } 91 | 92 | /** 93 | * Test the hash equality checking 94 | */ 95 | public function testHashEqualsValid() 96 | { 97 | $hash = sha1(mt_rand()); 98 | $this->assertTrue(Gatekeeper::hash_equals($hash, $hash)); 99 | } 100 | 101 | /** 102 | * Test that false is returned when the hashes are different lengths 103 | */ 104 | public function testHashEqualsDifferentLength() 105 | { 106 | $hash = sha1(mt_rand()); 107 | $this->assertFalse(Gatekeeper::hash_equals($hash, md5(mt_rand()))); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/GroupCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'group1', 'description' => 'Group #1'), 15 | array('name' => 'group2', 'description' => 'Group #2') 16 | ); 17 | 18 | $ds = $this->buildMock($return, 'fetch'); 19 | $groups = new GroupCollection($ds); 20 | 21 | $groups->findChildrenByGroupId($groupId); 22 | $this->assertCount(2, $groups); 23 | 24 | $groups = $groups->toArray(); 25 | $this->assertTrue($groups[0] instanceof GroupModel); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/GroupModelTest.php: -------------------------------------------------------------------------------- 1 | buildMock(true, 'save'); 13 | $group = new GroupModel($ds, array('id' => 1234)); 14 | 15 | $this->assertTrue($group->addUser(1)); 16 | } 17 | 18 | /** 19 | * Test the addiiton of a user by model to a group 20 | */ 21 | public function testAddUserByModelValid() 22 | { 23 | $ds = $this->buildMock(true, 'save'); 24 | $group = new GroupModel($ds, array('id' => 1234)); 25 | $user = new UserModel($ds, array('id' => 1234)); 26 | 27 | $this->assertTrue($group->addUser($user)); 28 | } 29 | 30 | /** 31 | * Test that a return of false is given when the group is invalid 32 | * (missing an ID) 33 | */ 34 | public function testAddUserInvalidGroup() 35 | { 36 | $ds = $this->buildMock(true, 'save'); 37 | $group = new GroupModel($ds); 38 | 39 | $this->assertFalse($group->addUser(1)); 40 | } 41 | 42 | /** 43 | * Test adding a permission by ID 44 | */ 45 | public function testAddPermissionByIdValid() 46 | { 47 | $ds = $this->buildMock(true, 'save'); 48 | $group = new GroupModel($ds, array('id' => 1234)); 49 | 50 | $this->assertTrue($group->addPermission(1)); 51 | } 52 | 53 | /** 54 | * Test adding a permission by a model instance 55 | */ 56 | public function testAddPermissionByModelValid() 57 | { 58 | $ds = $this->buildMock(true, 'save'); 59 | $group = new GroupModel($ds, array('id' => 1234)); 60 | $perm = new PermissionModel($ds, array('id' => 1234)); 61 | 62 | $this->assertTrue($group->addPermission($perm)); 63 | } 64 | 65 | /** 66 | * Test adding a permission by ID on an invalid group 67 | */ 68 | public function testAddPermissionByIdInvalid() 69 | { 70 | $ds = $this->buildMock(true, 'save'); 71 | $group = new GroupModel($ds); 72 | 73 | $this->assertFalse($group->addPermission(1)); 74 | } 75 | 76 | /** 77 | * Test that a user is in a group 78 | */ 79 | public function testUserInGroup() 80 | { 81 | $data = array( 82 | array('name' => 'group1', 'id' => 1234) 83 | ); 84 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 85 | ->disableOriginalConstructor() 86 | ->setMethods(array('fetch')) 87 | ->getMock(); 88 | 89 | $ds->method('fetch') 90 | ->willReturn($data); 91 | 92 | $group = new GroupModel($ds); 93 | 94 | $this->assertTrue($group->inGroup(1)); 95 | } 96 | 97 | /** 98 | * Test adding a child by group model instance 99 | */ 100 | public function testAddChild() 101 | { 102 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 103 | ->disableOriginalConstructor() 104 | ->setMethods(array('save')) 105 | ->getMock(); 106 | 107 | $ds->method('save') 108 | ->willReturn(true); 109 | 110 | $group1 = new GroupModel($ds, array('id' => 1234)); 111 | $group2 = new GroupModel($ds); 112 | 113 | $this->assertTrue($group1->addChild($group2)); 114 | } 115 | 116 | /** 117 | * Test that false is returned when you try to add a child to a 118 | * group not yet loaded 119 | */ 120 | public function testAddChildNoId() 121 | { 122 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 123 | ->disableOriginalConstructor() 124 | ->getMock(); 125 | 126 | $group1 = new GroupModel($ds); 127 | $group2 = new GroupModel($ds); 128 | 129 | $this->assertFalse($group1->addChild($group2)); 130 | } 131 | 132 | /** 133 | * Remove a valid child of the group by ID 134 | */ 135 | public function testRemoveChildByIdValid() 136 | { 137 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Stub') 138 | ->disableOriginalConstructor() 139 | ->setMethods(array('find', 'delete')) 140 | ->getMock(); 141 | 142 | $group = new GroupModel($ds, array('id' => 1234)); 143 | $ds->method('find')->willReturn($group); 144 | $ds->method('delete')->willReturn(true); 145 | 146 | $this->assertTrue($group->removeChild(1)); 147 | } 148 | 149 | /** 150 | * Remove a valid child of the group by model instance 151 | */ 152 | public function testRemoveChildByModelValid() 153 | { 154 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Stub') 155 | ->disableOriginalConstructor() 156 | ->setMethods(array('find', 'delete')) 157 | ->getMock(); 158 | 159 | $group1 = new GroupModel($ds, array('id' => 1234)); 160 | $group2 = new GroupModel($ds, array('id' => 4321)); 161 | 162 | $ds->method('find')->willReturn($group1); 163 | $ds->method('delete')->willReturn(true); 164 | 165 | $this->assertTrue($group1->removeChild($group2)); 166 | } 167 | 168 | /** 169 | * Test the false return of tyring to remove a child when no ID 170 | * is set on the parent group 171 | */ 172 | public function testRemoveChildNoId() 173 | { 174 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 175 | ->disableOriginalConstructor() 176 | ->getMock(); 177 | 178 | $group = new GroupModel($ds); 179 | $this->assertFalse($group->removeChild(1)); 180 | } 181 | 182 | /** 183 | * Test that a group is not expired 184 | */ 185 | public function testGroupNotExpired() 186 | { 187 | $ds = $this->buildMock(true); 188 | $group = new GroupModel($ds, [ 189 | 'id' => 1234, 190 | 'expire' => strtotime('+1 day') 191 | ]); 192 | 193 | $this->assertFalse($group->isExpired()); 194 | } 195 | 196 | /** 197 | * Test that a group is marked as expired 198 | */ 199 | public function testGroupIsExpired() 200 | { 201 | $ds = $this->buildMock(true); 202 | $group = new GroupModel($ds, [ 203 | 'id' => 1234, 204 | 'expire' => strtotime('-1 day') 205 | ]); 206 | 207 | $this->assertTrue($group->isExpired()); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/MockModel.php: -------------------------------------------------------------------------------- 1 | 'perm1', 'description' => 'Permission #1'), 15 | array('name' => 'perm2', 'description' => 'Permission #2') 16 | ); 17 | 18 | $ds = $this->buildMock($return, 'fetch'); 19 | $permissions = new PermissionCollection($ds); 20 | 21 | $permissions->findByGroupId($groupId); 22 | $this->assertCount(2, $permissions); 23 | } 24 | 25 | /** 26 | * Test to ensure permissions are returned when searched by child id 27 | */ 28 | public function testFindChidlrenByPermissionId() 29 | { 30 | $permId = 1; 31 | $return = array( 32 | array('name' => 'perm1', 'description' => 'Permission #1'), 33 | array('name' => 'perm2', 'description' => 'Permission #2') 34 | ); 35 | 36 | $ds = $this->buildMock($return, 'fetch'); 37 | $permissions = new PermissionCollection($ds); 38 | 39 | $permissions->findChildrenByPermissionId($permId); 40 | $this->assertCount(2, $permissions); 41 | } 42 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/PermissionModelTest.php: -------------------------------------------------------------------------------- 1 | buildMock(true, 'save'); 13 | $perm = new PermissionModel($ds, array('id' => 1234)); 14 | 15 | $this->assertTrue($perm->addChild(1)); 16 | } 17 | 18 | /** 19 | * Test the addition of a child by model instance to a permission 20 | */ 21 | public function testAddChildByModelValid() 22 | { 23 | $ds = $this->buildMock(true, 'save'); 24 | $perm1 = new PermissionModel($ds, array('id' => 1234)); 25 | $perm2 = new PermissionModel($ds, array('id' => 4321)); 26 | 27 | $this->assertTrue($perm1->addChild($perm2)); 28 | } 29 | 30 | /** 31 | * Test the invalid addition of a permission by ID 32 | */ 33 | public function testAddChildByIdInvalid() 34 | { 35 | $ds = $this->buildMock(true, 'save'); 36 | $perm = new PermissionModel($ds); 37 | 38 | $this->assertFalse($perm->addChild(1)); 39 | } 40 | 41 | /** 42 | * Test the valid addition of a child by ID 43 | */ 44 | public function testRemoveChildByIdValid() 45 | { 46 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 47 | ->disableOriginalConstructor() 48 | ->setMethods(array('find', 'delete')) 49 | ->getMock(); 50 | 51 | $perm = new PermissionModel($ds, array('id' => 1234)); 52 | 53 | $ds->method('find')->willReturn($perm); 54 | $ds->method('delete')->willReturn(true); 55 | 56 | $this->assertTrue($perm->removeChild(1)); 57 | } 58 | 59 | /** 60 | * Test the valid removal of a child by model instance 61 | */ 62 | public function testRemoveChildByModelValid() 63 | { 64 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 65 | ->disableOriginalConstructor() 66 | ->setMethods(array('find', 'delete')) 67 | ->getMock(); 68 | 69 | $perm1 = new PermissionModel($ds, array('id' => 1234)); 70 | $perm2 = new PermissionModel($ds); 71 | 72 | $ds->method('find')->willReturn($perm1); 73 | $ds->method('delete')->willReturn(true); 74 | 75 | $this->assertTrue($perm1->removeChild($perm2)); 76 | } 77 | 78 | /** 79 | * Test the invalid removal of a child by ID 80 | */ 81 | public function testRemoveChildByIdInvalid() 82 | { 83 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 84 | ->disableOriginalConstructor() 85 | ->getMock(); 86 | 87 | $perm = new PermissionModel($ds); 88 | $this->assertFalse($perm->removeChild(1)); 89 | } 90 | 91 | /** 92 | * Test that a permission is not expired 93 | */ 94 | public function testPermissionNotExpired() 95 | { 96 | $ds = $this->buildMock(true); 97 | $perm = new PermissionModel($ds, [ 98 | 'id' => 1234, 99 | 'expire' => strtotime('+1 day') 100 | ]); 101 | 102 | $this->assertFalse($perm->isExpired()); 103 | } 104 | 105 | /** 106 | * Test that a permission is marked as expired 107 | */ 108 | public function testPermissionIsExpired() 109 | { 110 | $ds = $this->buildMock(true); 111 | $perm = new PermissionModel($ds, [ 112 | 'id' => 1234, 113 | 'expire' => strtotime('-1 day') 114 | ]); 115 | 116 | $this->assertTrue($perm->isExpired()); 117 | } 118 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/PolicyCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'policy1', 'expression' => 'test expression'), 14 | array('name' => 'policy2', 'expression' => '"group1" in user.groups.getName()') 15 | ); 16 | 17 | $ds = $this->buildMock($return, 'fetch'); 18 | $policies = new PolicyCollection($ds); 19 | 20 | $policies->getList(); 21 | $this->assertCount(2, $policies); 22 | 23 | $policies = $policies->toArray(); 24 | $this->assertTrue($policies[0] instanceof PolicyModel); 25 | } 26 | 27 | /** 28 | * Test the location of policies in the system 29 | */ 30 | public function testFindPoliciesListLimit() 31 | { 32 | $return = array( 33 | array('name' => 'policy1', 'expression' => 'test expression') 34 | ); 35 | 36 | $ds = $this->buildMock($return, 'fetch'); 37 | $policies = new PolicyCollection($ds); 38 | 39 | $policies->getList(1); 40 | $this->assertCount(1, $policies); 41 | 42 | $policies = $policies->toArray(); 43 | $this->assertTrue($policies[0] instanceof PolicyModel); 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/PolicyModelTest.php: -------------------------------------------------------------------------------- 1 | buildMock(true, 'save'); 12 | $this->policy = new PolicyModel($ds, ['id' => 1]); 13 | } 14 | public function tearDown() 15 | { 16 | unset($this->policy); 17 | } 18 | 19 | /** 20 | * Test that the evaluation passes for a single object 21 | * with the type defined (array index) 22 | */ 23 | public function testPolicyEvaluateSingleObject() 24 | { 25 | $this->policy->load(['expression' => 'user.test == "foo"']); 26 | 27 | $user = (object)['test' => 'foo']; 28 | $data = ['user' => $user]; 29 | 30 | $this->assertTrue($this->policy->evaluate($data)); 31 | } 32 | 33 | /** 34 | * Test that the evaluation passes with multiple objects 35 | * with types defined (array index) 36 | */ 37 | public function testPolicyEvaluateMultipleObject() 38 | { 39 | $this->policy->load([ 40 | 'expression' => 'user.test == "foo" and group.name == "test"' 41 | ]); 42 | 43 | $data = [ 44 | 'user' => (object)['test' => 'foo'], 45 | 'group' => (object)['name' => 'test'] 46 | ]; 47 | $this->assertTrue($this->policy->evaluate($data)); 48 | } 49 | 50 | /** 51 | * Test that the resolution of type by classname works 52 | * in expression evaluation 53 | */ 54 | public function testPolicyEvaluateObjectByClassname() 55 | { 56 | $ds = $this->buildMock(true); 57 | $user = new UserModel($ds, ['username' => 'ccornutt']); 58 | 59 | $this->policy->load([ 60 | 'expression' => 'user.username == "ccornutt"' 61 | ]); 62 | $data = [$user]; 63 | $this->assertTrue($this->policy->evaluate($data)); 64 | } 65 | 66 | /** 67 | * Test that an testInvalidExpressionFailure is thrown when 68 | * the expression fails 69 | * 70 | * @expectedException \Psecio\Gatekeeper\Exception\InvalidExpressionException 71 | */ 72 | public function testInvalidExpressionFailure() 73 | { 74 | $this->policy->load([ 75 | 'expression' => 'user.username == "ccornutt"' 76 | ]); 77 | $data = []; 78 | $this->policy->evaluate($data); 79 | } 80 | 81 | /** 82 | * Test that an exception is thrown when no policy expression (by ID) 83 | * is currently loaded 84 | * 85 | * @expectedException \Psecio\Gatekeeper\Exception\InvalidExpressionException 86 | */ 87 | public function testFailureWhenNoPolicyLoaded() 88 | { 89 | $ds = $this->buildMock(true); 90 | $policy = new PolicyModel($ds); 91 | 92 | $this->policy->evaluate([]); 93 | } 94 | 95 | /** 96 | * Test the expression matching when a method is involved 97 | * In this case, get a User's groups list and return just 98 | * the names to see if a group exists/doesn't exist 99 | */ 100 | public function testPolicyEvaluateObjectWithFunction() 101 | { 102 | $ds = $this->buildMock(true); 103 | $groups = new GroupCollection($ds); 104 | $group = new GroupModel($ds, ['name' => 'group1']); 105 | $groups->add($group); 106 | 107 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Stub') 108 | ->setConstructorArgs(array(array())) 109 | ->getMock(); 110 | $ds->method('fetch') 111 | ->willReturn($groups->toArray(true)); 112 | 113 | $user = new UserModel($ds, ['username' => 'ccornutt42']); 114 | 115 | // "group1" does exist 116 | $this->policy->load(['expression' => '"group1" in user.groups.getName()']); 117 | $this->assertTrue($this->policy->evaluate($user)); 118 | 119 | // "group2" does not exist 120 | $this->policy->load(['expression' => '"group2" in user.groups.getName()']); 121 | $this->assertFalse($this->policy->evaluate($user)); 122 | } 123 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/Restrict/IpTest.php: -------------------------------------------------------------------------------- 1 | evaluate(); 17 | } 18 | 19 | /** 20 | * Test that an "Allowed" valid match returns true 21 | */ 22 | public function testEvaluateAllowMatch() 23 | { 24 | $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; 25 | $config = array( 26 | 'ALLOW' => array('192.168.*') 27 | ); 28 | 29 | $ip = new Ip($config); 30 | $this->assertTrue($ip->evaluate()); 31 | } 32 | 33 | /** 34 | * Test that a false is returned when a "deny" match is found 35 | */ 36 | public function testEvaluateDenyMatch() 37 | { 38 | $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; 39 | $config = array( 40 | 'DENY' => array('192.168.*') 41 | ); 42 | 43 | $ip = new Ip($config); 44 | $this->assertFalse($ip->evaluate()); 45 | } 46 | 47 | /** 48 | * Test that a false is returned on an "allow" with 49 | * no match 50 | */ 51 | public function testEvaluateAllowNoMatch() 52 | { 53 | $_SERVER['REMOTE_ADDR'] = '192.168.1.1'; 54 | $config = array( 55 | 'ALLOW' => array('10.0.*') 56 | ); 57 | 58 | $ip = new Ip($config); 59 | $this->assertFalse($ip->evaluate()); 60 | } 61 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/SecurityQuestionCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'Arthur', 'answer' => 'Dent', 'user_id' => $userId], 15 | ['name' => 'Ford', 'description' => 'Prefect', 'user_id' => $userId] 16 | ]; 17 | 18 | $ds = $this->buildMock($return, 'fetch'); 19 | $questions = new SecurityQuestionCollection($ds); 20 | 21 | $questions->findByUserId($userId); 22 | $this->assertCount(2, $questions); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/Session/RememberMeTest.php: -------------------------------------------------------------------------------- 1 | '+1 day'); 10 | $rm = $this->getMockBuilder('\Psecio\Gatekeeper\Session\RememberMe') 11 | ->setConstructorArgs(array($ds, $data)) 12 | ->setMethods($methods) 13 | ->getMock(); 14 | 15 | return $rm; 16 | } 17 | 18 | /** 19 | * Test the initialization of a full object with optional user 20 | */ 21 | public function testInitFullObject() 22 | { 23 | $return = true; 24 | $ds = $this->buildMock($return); 25 | $data = array('interval' => 86400); 26 | $user = new \Psecio\Gatekeeper\UserModel($ds, array( 27 | 'id' => 1234 28 | )); 29 | 30 | $remember = new RememberMe($ds, $data, $user); 31 | $this->assertEquals($remember->getData(), $data); 32 | $this->assertEquals($remember->getUser(), $user); 33 | $this->assertEquals($remember->getExpireInterval(), $data['interval']); 34 | } 35 | 36 | /** 37 | * Test the valid setup of the "remember me" handling for the 38 | * given user (does not set cookies) 39 | */ 40 | public function testSetupUserRememberValid() 41 | { 42 | $ds = $this->buildMock(true); 43 | $data = array('interval' => '+1 day'); 44 | $user = new \Psecio\Gatekeeper\UserModel($ds, array('id' => 1234)); 45 | $token = new \Psecio\Gatekeeper\AuthTokenModel($ds); 46 | 47 | $rm = $this->getMockBuilder('\Psecio\Gatekeeper\Session\RememberMe') 48 | ->setConstructorArgs(array($ds, $data)) 49 | ->setMethods(array('saveToken', 'getUserToken', 'setCookies')) 50 | ->getMock(); 51 | 52 | $rm->method('saveToken')->willReturn($token); 53 | $rm->method('getUserToken')->willReturn($token); 54 | $rm->method('setCookies')->willReturn(true); 55 | 56 | $this->assertTrue($rm->setup($user)); 57 | } 58 | 59 | /** 60 | * Test the setup of "remember me" for a user where the auth 61 | * token has expired 62 | */ 63 | public function testSetupUserRememberExpired() 64 | { 65 | $ds = $this->buildMock(true); 66 | $rm = $this->buildRememberMe( 67 | $ds, array('saveToken', 'getUserToken', 'setCookies', 'deleteToken') 68 | ); 69 | $token = new \Psecio\Gatekeeper\AuthTokenModel($ds, array( 70 | 'expires' => '-1 day' 71 | )); 72 | $user = new \Psecio\Gatekeeper\UserModel($ds, array('id' => 1234)); 73 | 74 | $rm->method('saveToken')->willReturn($token); 75 | $rm->method('getUserToken')->willReturn($token); 76 | $rm->method('setCookies')->willReturn(true); 77 | $rm->method('deleteToken')->willReturn(true); 78 | 79 | $this->assertFalse($rm->setup($user)); 80 | } 81 | 82 | /** 83 | * Test when there's a save error on creating the new remember me auth token 84 | */ 85 | public function testSetupUserRememberNoSave() 86 | { 87 | $ds = $this->buildMock(true); 88 | $rm = $this->buildRememberMe( 89 | $ds, array('saveToken', 'getUserToken') 90 | ); 91 | $user = new \Psecio\Gatekeeper\UserModel($ds, array('id' => 1234)); 92 | $token = new \Psecio\Gatekeeper\AuthTokenModel($ds, array( 93 | 'expires' => '+1 day' 94 | )); 95 | 96 | $rm->method('saveToken')->willReturn(false); 97 | $rm->method('getUserToken')->willReturn($token); 98 | 99 | $this->assertFalse($rm->setup($user)); 100 | } 101 | 102 | /** 103 | * Find a token instance using the token string value 104 | */ 105 | public function testGetTokenByTokenValue() 106 | { 107 | $tokenString = md5('test1234'); 108 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->buildMock(true), array( 109 | 'expires' => '+1 day' 110 | )); 111 | $ds = $this->buildMock($token); 112 | $rm = $this->buildRememberMe($ds, array('saveToken')); 113 | 114 | $result = $rm->getByToken($tokenString); 115 | $this->assertEquals($result, $token); 116 | } 117 | 118 | /** 119 | * Find a token instance using the token string value 120 | */ 121 | public function testGetTokenByTokenId() 122 | { 123 | $tokenId = 1234; 124 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->buildMock(true), array( 125 | 'expires' => '+1 day' 126 | )); 127 | $ds = $this->buildMock($token); 128 | $rm = $this->buildRememberMe($ds, array('saveToken')); 129 | 130 | $result = $rm->getById($tokenId); 131 | $this->assertEquals($result, $token); 132 | } 133 | 134 | /** 135 | * Test locating a token by the given user object 136 | */ 137 | public function testGetTokenByUser() 138 | { 139 | $user = new \Psecio\Gatekeeper\UserModel($this->buildMock(true), array('id' => 1234)); 140 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->buildMock(true), array( 141 | 'expires' => '+1 day', 142 | 'userId' => $user->id 143 | )); 144 | 145 | $ds = $this->buildMock($token); 146 | $rm = $this->buildRememberMe($ds, array('saveToken')); 147 | 148 | $result = $rm->getUserToken($user); 149 | $this->assertEquals($result, $token); 150 | } 151 | 152 | /** 153 | * Test the saving of a token value, that it returns a token instance 154 | * on success 155 | */ 156 | public function testSaveTokenValue() 157 | { 158 | $tokenString = 1234; 159 | $user = new \Psecio\Gatekeeper\UserModel($this->buildMock(true), array('id' => 1234)); 160 | $token = new \Psecio\Gatekeeper\AuthTokenModel($this->buildMock(true), array( 161 | 'expires' => date('Y-m-d H:i:s', time() + 86400), 162 | 'token' => $tokenString, 163 | 'userId' => $user->id 164 | )); 165 | 166 | $ds = $this->buildMock(true, 'save'); 167 | $rm = $this->buildRememberMe($ds, array('deleteToken')); 168 | 169 | $result = $rm->saveToken($tokenString, $user); 170 | $this->assertEquals($result, $token); 171 | } 172 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/ThrottleModelTest.php: -------------------------------------------------------------------------------- 1 | buildMock(true, 'save'); 13 | $throttle = new ThrottleModel($ds, array('attempts' => 1)); 14 | 15 | $throttle->updateAttempts(); 16 | $this->assertEquals(2, $throttle->attempts); 17 | $this->assertNotNull($throttle->lastAttempt); 18 | } 19 | 20 | /** 21 | * Test the changes made when a user is set back to allowed 22 | */ 23 | public function testSetUserToAllow() 24 | { 25 | $ds = $this->buildMock(true, 'save'); 26 | $throttle = new ThrottleModel( 27 | $ds, array('attempts' => 1, 'status' => ThrottleModel::STATUS_BLOCKED) 28 | ); 29 | 30 | $throttle->allow(); 31 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_ALLOWED); 32 | $this->assertNotNull($throttle->statusChange); 33 | } 34 | 35 | /** 36 | * Test that a user is allowed after the default timeout (1 minute) has passed 37 | */ 38 | public function testCheckDefaultTimeoutAllowUser() 39 | { 40 | $ds = $this->buildMock(true, 'save'); 41 | $throttle = new ThrottleModel( 42 | $ds, 43 | array( 44 | 'status' => ThrottleModel::STATUS_BLOCKED, 45 | 'statusChange' => date('Y/m/d H:i:s', strtotime('-5 minutes')) 46 | ) 47 | ); 48 | $throttle->checkTimeout(); 49 | 50 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_ALLOWED); 51 | } 52 | 53 | /** 54 | * Test that, when the status change time hasn't reached the timeout, no 55 | * status change is made. 56 | */ 57 | public function testCheckDefaultTimeoutNoChange() 58 | { 59 | $ds = $this->buildMock(true, 'save'); 60 | $throttle = new ThrottleModel( 61 | $ds, 62 | array( 63 | 'status' => ThrottleModel::STATUS_BLOCKED, 64 | 'statusChange' => date('Y/m/d H:i:s', strtotime('-10 seconds')) 65 | ) 66 | ); 67 | $throttle->checkTimeout(); 68 | 69 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_BLOCKED); 70 | } 71 | 72 | /** 73 | * Test that a user is allowed after the given timeout (-10 minutes) has passed 74 | */ 75 | public function testCheckInputTimeoutAllowUser() 76 | { 77 | $ds = $this->buildMock(true, 'save'); 78 | $throttle = new ThrottleModel( 79 | $ds, 80 | array( 81 | 'status' => ThrottleModel::STATUS_BLOCKED, 82 | 'statusChange' => date('Y/m/d H:i:s', strtotime('-12 minutes')) 83 | ) 84 | ); 85 | $throttle->checkTimeout('-10 minutes'); 86 | 87 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_ALLOWED); 88 | } 89 | 90 | /** 91 | * Check that when the user has reached or gone over the number of attempts 92 | * (default is 5) they're set to blocked 93 | */ 94 | public function testCheckAttemptsBlockUser() 95 | { 96 | $ds = $this->buildMock(true, 'save'); 97 | $throttle = new ThrottleModel( 98 | $ds, array('attempts' => 6) 99 | ); 100 | $throttle->checkAttempts(); 101 | 102 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_BLOCKED); 103 | } 104 | 105 | /** 106 | * Check that when the user hasn't reached or gone over the number of attempts 107 | * (default is 5) they're not blocked 108 | */ 109 | public function testCheckAttemptsNotBlockUser() 110 | { 111 | $ds = $this->buildMock(true, 'save'); 112 | $throttle = new ThrottleModel( 113 | $ds, array('attempts' => 2, 'status' => ThrottleModel::STATUS_ALLOWED) 114 | ); 115 | $throttle->checkAttempts(); 116 | 117 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_ALLOWED); 118 | } 119 | 120 | /** 121 | * Test that the find by user ID works correctly and populates the model 122 | */ 123 | public function testFindByUserId() 124 | { 125 | $userId = 10; 126 | $data = array( 127 | array('userId' => $userId, 'attempts' => 1, 'status' => ThrottleModel::STATUS_ALLOWED) 128 | ); 129 | 130 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 131 | ->disableOriginalConstructor() 132 | ->setMethods(array('fetch')) 133 | ->getMock(); 134 | 135 | $ds->method('fetch') 136 | ->willReturn($data); 137 | 138 | $throttle = new ThrottleModel($ds); 139 | $throttle->findByUserId($userId); 140 | 141 | $this->assertEquals($throttle->attempts, 1); 142 | $this->assertEquals($throttle->userId, 10); 143 | $this->assertEquals($throttle->status, ThrottleModel::STATUS_ALLOWED); 144 | } 145 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/UserCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'testuser1', 'email' => 'testuser1@gmail.com'), 15 | array('username' => 'testuser2', 'email' => 'testuser2@gmail.com'), 16 | array('username' => 'testuser3', 'email' => 'testuser3@gmail.com') 17 | ); 18 | 19 | $ds = $this->buildMock($return, 'fetch'); 20 | $users = new UserCollection($ds); 21 | 22 | $users->findByGroupId($groupId); 23 | $this->assertCount(3, $users); 24 | $this->assertEquals($users->toArray()[1]->username, 'testuser2'); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/UserGroupCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'group1', 'description' => 'Group #1'), 15 | array('name' => 'group2', 'description' => 'Group #2') 16 | ); 17 | 18 | $ds = $this->buildMock($return, 'fetch'); 19 | $groups = new UserGroupCollection($ds); 20 | 21 | $groups->findByUserId($userId); 22 | $this->assertCount(2, $groups); 23 | } 24 | 25 | /** 26 | * Test the creation of new collection items based on data given 27 | */ 28 | public function testCreateRecordsFromModelDataById() 29 | { 30 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 31 | ->disableOriginalConstructor() 32 | ->setMethods(array('save', 'fetch')) 33 | ->getMock(); 34 | 35 | $ds->method('save')->willReturn(true); 36 | 37 | $userModel = new UserModel($ds, array('id' => 1)); 38 | $data = array(array('id' => 1, 'name' => 'Group #1')); 39 | $ds->method('fetch')->willReturn($data); 40 | 41 | $groupIdList = array(1, 2, 3); 42 | 43 | $groups = new UserGroupCollection($ds); 44 | $groups->create($userModel, $groupIdList); 45 | 46 | $this->assertEquals( 47 | count($groups->toArray()), count($groupIdList) 48 | ); 49 | } 50 | 51 | /** 52 | * Test the creation of new collection items based on data given 53 | */ 54 | public function testCreateRecordsFromModelDataByName() 55 | { 56 | $ds = $this->getMockBuilder('\Psecio\Gatekeeper\DataSource\Mysql') 57 | ->disableOriginalConstructor() 58 | ->setMethods(array('save', 'fetch')) 59 | ->getMock(); 60 | 61 | $ds->method('save')->willReturn(true); 62 | 63 | $userModel = new UserModel($ds, array('id' => 1)); 64 | $data = array(array('id' => 1, 'name' => 'Group #1')); 65 | $ds->method('fetch')->willReturn($data); 66 | 67 | $groupNameList = array('group1', 'group2', 'group3'); 68 | 69 | $groups = new UserGroupCollection($ds); 70 | $groups->create($userModel, $groupNameList); 71 | 72 | $this->assertEquals( 73 | count($groups->toArray()), count($groupNameList) 74 | ); 75 | } 76 | } -------------------------------------------------------------------------------- /tests/Psecio/Gatekeeper/UserPermissionCollectionTest.php: -------------------------------------------------------------------------------- 1 | 'perm1', 'description' => 'Permission #1'), 15 | array('name' => 'perm2', 'description' => 'Permission #2') 16 | ); 17 | 18 | $ds = $this->buildMock($return, 'fetch'); 19 | $permissions = new UserPermissionCollection($ds); 20 | 21 | $permissions->findByUserId($userId); 22 | $this->assertCount(2, $permissions); 23 | } 24 | 25 | /** 26 | * Test the creation of a user permission relation by permission ID 27 | */ 28 | public function testCreateUserPermissionById() 29 | { 30 | $return = array( 31 | array('id' => 1, 'name' => 'perm5') 32 | ); 33 | 34 | $ds = $this->buildMock($return, 'fetch'); 35 | $permissions = new UserPermissionCollection($ds); 36 | $model = new UserPermissionModel($ds); 37 | 38 | $data = array(1, 2, 3); 39 | $permissions->create($model, $data); 40 | } 41 | 42 | /** 43 | * Test the creation of a user permission relation by permission model instance 44 | */ 45 | public function testCreateUserPermissionByModel() 46 | { 47 | $return = array( 48 | array('id' => 1, 'name' => 'perm5'), 49 | array('id' => 2, 'name' => 'perm6') 50 | ); 51 | 52 | $ds = $this->buildMock($return, 'fetch'); 53 | $permissions = new UserPermissionCollection($ds); 54 | $model = new UserPermissionModel($ds); 55 | 56 | $data = array( 57 | new PermissionModel($ds, array('id' => 1)), 58 | new PermissionModel($ds, array('id' => 2)), 59 | new PermissionModel($ds, array('id' => 3)) 60 | ); 61 | $permissions->create($model, $data); 62 | } 63 | } -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |