├── .env.dist
├── src
└── Psecio
│ └── Gatekeeper
│ ├── Resolve.php
│ ├── Exception
│ ├── DataNotFoundException.php
│ ├── PasswordResetInvalid.php
│ ├── PasswordResetTimeout.php
│ ├── UserInactiveException.php
│ ├── UserNotFoundException.php
│ ├── GroupNotFoundException.php
│ ├── ModelNotFoundException.php
│ ├── PolicyNotFoundException.php
│ ├── InvalidExpressionException.php
│ ├── RestrictionFailedException.php
│ ├── ThrottleNotFoundException.php
│ └── PermissionNotFoundException.php
│ ├── Handler
│ ├── Save.php
│ ├── Delete.php
│ ├── Create.php
│ ├── Count.php
│ ├── CloneInstance.php
│ └── FindBy.php
│ ├── Provider
│ ├── Laravel5
│ │ ├── AuthManager.php
│ │ ├── AuthServiceProvider.php
│ │ ├── UserProvider.php
│ │ └── UserAuthenticatable.php
│ └── Laravel.php
│ ├── PolicyCollection.php
│ ├── SecurityQuestionCollection.php
│ ├── AuthTokenCollection.php
│ ├── Resolve
│ └── Permissions.php
│ ├── Restrict
│ ├── Throttle.php
│ └── Ip.php
│ ├── Restriction.php
│ ├── GroupParentModel.php
│ ├── GroupPermissionModel.php
│ ├── PermissionParentModel.php
│ ├── PhinxMigration.php
│ ├── UserGroupModel.php
│ ├── UserPermissionModel.php
│ ├── GroupCollection.php
│ ├── UserCollection.php
│ ├── PermissionCollection.php
│ ├── AuthTokenModel.php
│ ├── Collection
│ └── Mysql.php
│ ├── UserGroupCollection.php
│ ├── UserPermissionCollection.php
│ ├── DataSource
│ ├── Stub.php
│ └── Mysql.php
│ ├── SecurityQuestionModel.php
│ ├── Handler.php
│ ├── PolicyModel.php
│ ├── DataSource.php
│ ├── Model
│ └── Mysql.php
│ ├── ThrottleModel.php
│ ├── PermissionModel.php
│ ├── GroupModel.php
│ └── Session
│ └── RememberMe.php
├── tests
├── bootstrap.php
└── Psecio
│ └── Gatekeeper
│ ├── MockPdo.php
│ ├── MockModel.php
│ ├── Base.php
│ ├── SecurityQuestionCollectionTest.php
│ ├── GroupCollectionTest.php
│ ├── UserCollectionTest.php
│ ├── DataSourceTest.php
│ ├── PermissionCollectionTest.php
│ ├── PolicyCollectionTest.php
│ ├── Restrict
│ └── IpTest.php
│ ├── DataSource
│ └── MysqlTest.php
│ ├── UserPermissionCollectionTest.php
│ ├── UserGroupCollectionTest.php
│ ├── GatekeeperTest.php
│ ├── PermissionModelTest.php
│ ├── PolicyModelTest.php
│ ├── ThrottleModelTest.php
│ ├── Session
│ └── RememberMeTest.php
│ └── GroupModelTest.php
├── .travis.yml
├── examples
├── README.md
└── login-form.html
├── mkdocs.yml
├── migrations
├── 20150612202904_add_last_login_column.php
├── 20150702224804_add_permission_group_expire.php
├── 20150703015048_add_user_permission_group_expire.php
├── 20150109213039_create_group_parent_xref.php
├── 20150111162554_create_permission_parent_xref.php
├── 20150202164329_create_auth_token_table.php
├── 20141211205817_create_group_user_table.php
├── 20150218151720_create_security_questions_table.php
├── 20141212101534_create_permissions_table.php
├── 20141211204500_create_group_table.php
├── 20141213161408_create_user_permission_table.php
├── 20141214082627_create_group_permission_table.php
├── 20141216132116_create_throttle_table.php
├── 20150528223137_create_policy_table.php
├── 20150124094757_fix_unique_indexes.php
└── 20141211092209_create_user_table.php
├── .gitignore
├── phpunit.xml
├── phinx.dist.yml
├── docs
├── password-reset.md
├── index.md
├── restrictions.md
├── security-questions.md
├── providers.md
├── permissions.md
├── authentication.md
├── installation-and-configuration.md
├── working-with-objects.md
├── policies.md
├── groups.md
└── users.md
├── composer.json
├── README.md
└── bin
└── setup.sh
/.env.dist:
--------------------------------------------------------------------------------
1 | DB_USER=%%USERNAME%%
2 | DB_PASS=%%PASSWORD%%
3 | DB_HOST=%%HOSTNAME%%
4 | DB_NAME=%%DBNAME%%
--------------------------------------------------------------------------------
/src/Psecio/Gatekeeper/Resolve.php:
--------------------------------------------------------------------------------
1 | getArguments();
15 | return $this-getDb()->save($args[0]);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/Psecio/Gatekeeper/Provider/Laravel5/AuthManager.php:
--------------------------------------------------------------------------------
1 | app['config']['auth.driver'];
17 | }
18 | }
--------------------------------------------------------------------------------
/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'
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | ./tests
13 |
14 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/src/Psecio/Gatekeeper/Resolve/Permissions.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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
35 |
36 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Gatekeeper: An Authentication & Authorization Library
2 | ==========
3 |
4 | [](http://travis-ci.org/psecio/gatekeeper)
5 | [](https://codeclimate.com/github/psecio/gatekeeper)
6 | [](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 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/tests/Psecio/Gatekeeper/PermissionCollectionTest.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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/src/Psecio/Gatekeeper/GroupCollection.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/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/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/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/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 | }
--------------------------------------------------------------------------------
/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`.
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/DataSource/Stub.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 | }
--------------------------------------------------------------------------------
/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 | ```
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------