psecio / gatekeeper Goto Github PK
View Code? Open in Web Editor NEWGatekeeper: An Authentication & Authorization Library
Gatekeeper: An Authentication & Authorization Library
Right now Gatekeeper does no password policy enforcement, it just uses the password as is. The ability should be added, maybe as a callback somehow, to define a set of rules on the value provided by the user.
Possible implementation:
<?php
Gatekeeper::setPasswordPolicy(function($password) {
if (strlen($password) < 8) {
throw new \Exception('Invalid password length!');
}
});
?>
This would need to be appended to the UserModel
somehow so it could be triggered on user create.
➔ ./vendor/bin/setup.sh
--- Executing Gatekeeper setup ----------
> No configuration found, please enter database information:
Hostname [localhost]:
Database name [gatekeeper]:
Username: mysql
Password:
Password cannot be empty!
this is annoying restriction, it's my dev machine do i have create separate user to test this project?
A "Security" page should be added to the documentation talking about the measures Gatekeeper uses to protect the users and their data. This should include:
etc. The idea is that this provides a "behind the scenes" look at how Gatekeeper is working to protect the data it works with and functionality it includes (summary, not really intended for code samples or anything).
Currently there's no visibility around the actions taken as a part of the Gatekeeper handling. Functionality should be added allowing the injection of a PSR-3 compliant logger (following the Psr\Log\LoggerInterface
) and used through out the system.
If the logger exists, actions should be logged using the appropriate level. Example:
authenticate
logged as "info"Think about adding in a logging "level" where only certain actions from a set are logged (HIGH, MED, LOW possibly)
Users should be soft-deletable so that an "undo" action can be easily provided when deleting users. I suggest adding a "deleted" flag into the users (and groups, possibly) table, and a "deleted_on" timestamp.
When removing users, the links between users and groups don't get deleted for related users. This should be set up with foreign keys and DELETE constraints so these extra relationships don't get left behind. Was this done on purpose? I noticed I have to do a lot more cleaning now, something I hadn't expected.
Right now multiple calls are needed to get the user groups and permissions and ensure they have what they need. A way should be introduced, via some kind of schema, to allow checks for permissions/groups with various combinations of ands and ors (or even just permission/group sets).
Here is the page:
http://gatekeeper-auth.readthedocs.org/en/latest/installation-and-configuration/
"Create a database named gatekeeper and create a user for it (this is configured later)"
I'm not sure that users wish to create one more database for storing users... I think they'll try to use existing database. This interferes with issue #6, which seems to suppose same idea... I'm not quite sure if creating different database should be mentioned in the manual.
"Open both of these files and update the connection information to match the database you created earlier."
This is not enough. A developer also needs to change paths.migrations to the actual directory, relative to the directory we run phinx from.
<?php
require_once 'vendor/autoload.php';
Gatekeeper::init();
?>
This short example gives an error, as one should use not just Gatekeeper
, but \Psecio\Gatekeeper\Gatekeeper
.
I use Ubuntu 16.04. Nginx server and installed with composer.
I create Database ok, i can connect to mysql correct. Gatekeeper create database and returns Ok.
But in call the init function:
require_once 'vendor/autoload.php';
use \Psecio\Gatekeeper\Gatekeeper;
Gatekeeper::init();
I returned fatal error:
"PHP message: PHP Fatal error: Uncaught Error: Class 'psecio\gatekeeper\Gatekeeper' not found in /var/www/html/index.php:12
Perhaps is Composer error, i don't know. Require_once is ok but use fails.
If I use following code:
$gkUserData = array(
'username' => $userData['email_address'],
'password' => '111111',
'email' => $userData['email_address'],
'first_name' => $userData['first_name'],
'last_name' => $userData['last_name'],
'groups' => array($groupId),
);
Gatekeeper::register($gkUserData);
//Gatekeeper::findUserByUsername($userData['email_address'])->addGroup($groupId);
foreach (Gatekeeper::findUserByUsername($userData['email_address'])->groups as $group) {
var_dump($group->name);
}
var_dump()
vardumps me nothing.
If I uncomment the line with addGroup()
method, var_dump()
gives me right results.
Also please note that Gatekeeper::register()
doesn't return the id of newly created user, so instead of Gatekeeper::findUserById($userId)
I have to use somewhat inconvenient Gatekeeper::findUserByUsername($userData['email_address'])
Note 2:
http://gatekeeper-auth.readthedocs.org/en/latest/users/#creating-users
// Use can use permission names
$credentials['groups'] = array('group1', 'group2');
should be changed to
// Use can use group names
$credentials['groups'] = array('group1', 'group2');
There's a bit of functionality already in the system to support two-factor authentication tokens, but it needs to be finished out. This includes:
create_auth_token_table
migration to add the verifier
columnVerifier
structureGatekeeper
class for verifying the provided token information.This could be in multiple places too...maybe a method on the UserModel
that takes in an abstract token instance and runs the matching verifier based on the current configuration. This will need some way to get values from the current configuration (like Gatekeeper::getConfig
) for things like secret keys or API credentials.
Something similar to:
hash_hmac
with a one-time nonce (generated from a randomized source)<?php
$token = $nonce.hash_hmac($hash, $secret, $nonce);
// verify with `hash_equals` on validate
if (hash_equals($hash, $input) === true) {
echo "woo!";
}
?>
I suggest renaming setup.sh
to gk_setup.sh
or gatekeeper_setup.sh
. Due to it not being namespaced, it can be hard to figure out what it refers to when a lot of aliases for a lot of different projects are put into vendor/bin
.
Not so much an issue as a suggestion, it would be nice if there was a method to check if the user inherits a specific permission via groups its assigned to.
Currently I can't see any function that does that, specifically mentioning in the docs that:
You can check the user's immediate permissions (not the ones on groups they belong to) with the hasPermission method
But no information on how to check if the user gets a permission from a group.
As I can see here:
http://gatekeeper-auth.readthedocs.org/en/latest/users/#creating-users
there is login, password, first name and last name fields, plus some technical info like id, resetCode, created etc.
Usually a ton more of fields is required.
Today I tried to import some data into users table and found that there is too few fields for a name - only first and last. We here, at Russia, have one more name - you can call it "Middle name", "Second name" or "Patronymic". I think, "middle_name" or "middleName" would be OK for database. One can't store someone's name without a middle name.
Often developers have to store much more additional information, like work phone, mobile phone, web site address, company name and others. Is there a solution for this? Is any work planned to address this issue?
Here is the code:
Gatekeeper::createGroup(array(
'name' => 'spam_receivers',
'description' => 'Получатели рассылки',
));
$group = Gatekeeper::findGroupByName('spam_receivers');
var_dump($group->name, $group->description);
As you can see, there are two words in description.
The PHP script is written in UTF-8.
When I look into the "groups" table, I see this: "Получатели"
It is one word stored in an unknown way for me.
var_dump() gives me this:
string(14) "spam_receivers"
string(20) "Получатели"
It manages to decode the garbage back to cyrillic, but the second word is lost anyway.
Users cyrillic UTF data is stored in the same way - as garbage.
I think UTF-8 should be stored as UTF-8 without any magic.
BTW, groups table has right encoding of "utf8_general_ci".
Suggest adding count
default method into datasources, like find
, save
, etc. Returns total number of rows in given table.
Will prepare PR in relatively near future if someone else doesn't do it first.
There's a hasPermission
method on the user that's currently not mentioned in the documentation that I can see.
<?php
public function hasPermission($permissionId) { ... }
?>
I read here
http://gatekeeper-auth.readthedocs.org/en/latest/groups/#creating-a-group
that "One thing to note, the group name must be unique".
But groups.name
field doesn't have UNIQUE index. I think it should. Currently I can call Gatekeeper::createGroup() with same name multiple times and no errors occur. Additional groups haven't been created, though. Looks like Gatekeeper silently ignores such cases.
And description
field is defined as varchar(20)
. Kind of we cannot put very informative description here =) I think varchar(255)
or text
would be more useful.
Seeing as MySQL is pretty much hardcoded into Gatekeeper and in this state it will never be able to support another SQL engine, no matter how similar, I wonder why foreign relations and constraints are created during the first setup migration? As is, when deleting users or groups, the relations stick around, polluting the database with leftovers.
A method needs to be created to allow the resolution of all of a users permissions and groups (especially the parent/child perms & groups) to combine into one master list that can be passed into the inGroup
or hasPermission
methods for evaluation.
This should probably come in the form of PermissionResolver
and GroupResolver
classes that can be given a User
instance.
I think the update function should not set the 'created' date and only set the 'update' date.
public function update(\Modler\Model $model)
{
$data = $model->toArray();
$data['created'] = date('Y-m-d H:i:s'); // <- This should be removed
$data['updated'] = date('Y-m-d H:i:s');
list($columns, $bind) = $this->setup($data);
$update = array();
$properties = $model->getProperties();
foreach ($bind as $column => $name) {
$colName = $properties[$column]['column'];
$update[] = $colName.' = '.$name;
}
$sql = 'update '.$model->getTableName().' set '.implode(',', $update).' where ID = '.$model->id;
return $this->execute($sql, $data);
}
In typical user management CRUD apps, it's a common case to have to remove users from a group or add them to them - it would be easier if we could do this like so:
$user->changeGroups($_POST['groups']);
Right now, we have to:
This is, for obvious reasons, incredibly impractical.
I suggest implementing a new changeGroups
method which would remove ALL groups from a user, and at the same time add those we're feeding it. The method should be wrapped in a transaction in order to be safe against failures and leaving a user without groups should something break mid-way.
I tried to make a switch over to PHP 7 on a server using this in one of the sites living on it and it borked something. I didn't have enough time to debug it correctly before restoring to PHP 5.6.x but there was something amiss.
Need to run the unit tests on PHP 7 and see if there's any issues.
I suggest moving the check
$envPath = ($envPath !== null) ? $envPath : getcwd();
from the loadConfig
method to the loadDotEnv
method, and throwing a proper exception if the path was specified but loading failed. That calls for an exception, rather than just false, because something unexpected occurred. It's not a case for a soft fallback to false. Took me a while to debug what was happening as I encountered this.
I believe the loadDotEnv
method is a better home for this check, considering that's the only method in which the actual parsing of the file and loading of the config values actually happens.
I realize, however, that this would be a BC break.
Thoughts? Should I submit a PR anyway?
I'm a little bit confused about the documentation. Granted, I am not an advanced programer, I am somewhere in advanced beginner to intermediate.
First, I don't have access to composer on my hosting, so I downloaded the repository and uploded the SRC folder to my project, so apparently the whole quick setup isn't going to work for me. I couldn't find the in the repo anywhere.
I do see that I can configure it manually using
<?php
require_once 'vendor/autoload.php';
use \Psecio\Gatekeeper\Gatekeeper;
Gatekeeper::init();
?>
and
<?php
// You can specify your own .env path
Gatekeeper::init('/path/to/directory');
// You can also force the use of your own database configuration
$config = array(
'type' => 'mysql',
'username' => 'gatekeeper-user',
'password' => 'gatekeeper-pass',
'name' => 'gatekeeper',
'host' => 'gatekeeper-db.localhost'
);
Gatekeeper::init(null, $config);
?>
does this mean for every script I need to start with this code?
I see it says "You can pass in options to the init call if you don't necesarily want to use the .env configuration file handling." but their are no ENV files for me to configure.
What am I missing?
This code gives me false as the result:
use \Psecio\Gatekeeper\Gatekeeper;
Gatekeeper::disableThrottle();
$user = Gatekeeper::findUserById(1);
$password = 'aaaaaa';
$user->password = $password;
$user->save();
$result = Gatekeeper::authenticate(array('username' => $user->username, 'password' => $password));
var_dump($user->id, $result);
I find that hasPermission() have wrong in UserModel.php.
the function return (
should be modified as return (
I have a suggestion
hasPermission() return true only if the requested permission isn't expired
please check it
I tried to run this code:
use \Psecio\Gatekeeper\Gatekeeper;
Gatekeeper::disableThrottle();
$r = Gatekeeper::authenticate(array('username' => 'blah', 'password' => 'meh'), true);
echo '<pre>';
var_dump($r, Gatekeeper::checkRememberMe());
echo '</pre>';
which gave me true
, false
.
Umm, that means that user is authenticated successfully but not.
I tried to investigate and inside Gatekeeper::authenticate() found this line:
if (self::$throttleStatus === true && $result === true) {
Which, if I understood correctly, means that "remember me" works only when throttling is enabled... eh...
Looks like if I remove self::$throttleStatus === true
remembering does work - but recalling still doesn't.
Digging further inside RememberMe::setup() I found this:
if ($userToken->id !== null || $this->isExpired($userToken)) {
return false;
}
$userToken->id !== null
condition triggers and the false
is returned.
I don't understand this logic and cannot suggest anything more. I don't think it works as intended, though... are these bugs?
When I put this POST data directly into the Gatekeeper::register
method:
Then my records go from this:
to this:
and I also get an additional MySQL error: DB ERROR: [23000] Duplicate entry '1-12' for key 'group_id'
In other words, when inserting this data, GK not only tries to insert a group-user pair twice, it also for some reason grabs the ID of the last inserted user-group pair row and uses that ID as the next user ID.
Am investigating further.
Perhaps my own configuration is incorrect, but executing setup.sh
for a remote database caused a 'MySQL Connection Failure' error. A simple edit to the setup.sh
script fixed the issue, just thought i'd mention it here in case others have the same problem.
I don't understand very well Password reset. Password reset give to user chance to change their password? Or only allows user restart their account?
How user can change their password?
EDIT: finally i have made a DataBase query and update password field using pasword_hash native php 7 function.
As of MySQL 5.6 the default is to enable STRICT_TRANS_TABLE which blows up the phinx log table creation. They are unwilling to add the appropriate patch due to Travis's MySQL version incompatibility. See: cakephp/phinx#243
To disable STRICT_TRANS_TABLE you need to edit /etc/my.cnf (and /usr/my.cnf on CentOS)
sql_mode=NO_ENGINE_SUBSTITUTION
I have submitted a new patch (cakephp/phinx#638) that will hopefully be accepted...
There's a lot of stuff I'd expand the User model with to match the needs of my application. However, since it's not injected as a dependency into GK, but instantiated on the spot where needed, this is not easy. One option would be to make a UserModelWrapper class which consumes GK's default User class and thus decorates it with new functionality, but then collections won't work out of the box, and when iterating through them, I would need to manually instantiate my decorator again and feed each GK user into it to get my functionality.
Instead, it would be great if you defined an interface for the User class, and let us set up Gatekeeper to use a fully custom class if needed (atm GK is even fully coupled to MySQL - even if a Postgre datasource existed, it wouldn't work - see line 313 for example).
After executing the full setup procedure, the script will write vars into .env
regardless of whether or not they're already there:
DB_USER=homestead
DB_PASS=secret
DB_HOST=localhost
DB_NAME=adoptify_gk
MAILGUN_KEY=key-foo
MAILGUN_DOMAIN=mydomain-or-sandbox-url
DEBUG=true
DB_USER=homestead
DB_PASS=secret
DB_HOST=localhost
DB_NAME=adoptify_gk
This is a nuisance in projects that come with an example .env
already populated with some values. The script should check if they exist and only replace them.
Furthermore, and somewhat related to #51, these constants should be namespaced as GK_DB_USER
or something, since it's not only possible but also highly likely that a different database (with a different user account and maybe even server) will be used for the actual application.
Executing setup.sh I get this response:
cp: cannot stat ./vendor/psecio/gatekeeper/phinx.dist.yml': No such file or directory sed: can't read ./phinx.yml: No such file or directory sed: can't read ./phinx.yml: No such file or directory sed: can't read ./phinx.yml: No such file or directory sed: can't read ./phinx.yml: No such file or directory rm: cannot remove
./phinx.yml-e': No such file or directory
cp: cannot stat ./vendor/psecio/gatekeeper/.env.dist': No such file or directory sed: can't read ./.env: No such file or directory sed: can't read ./.env: No such file or directory sed: can't read ./.env: No such file or directory sed: can't read ./.env: No such file or directory rm: cannot remove
./.env-e': No such file or directory
--- Running migrations ----------
./setup.sh: line 87: vendor/bin/phinx: No such file or directory
DONE!
No tables has been created in the DB.
Any hint?
P.S.: Is a .sql file available?
Regards
Currently the Gatekeeper tables are relatively commonly named (users
, groups
, etc) and those could conflict with a system's current tables. A prefix option should be added to the .env
handling allowing the prefix to be included on the tables. If no prefix is defined, it will revert to the current default table names.
Consider possibly adding the ability to migrate the data structure too, adding a method that would check for a prefix and, if found and not currently in use, use a SELECT INTO
or something similar to migrate each of the tables. A special set of Phinx migrations could be included for this too.
I authenticate, Ok. Remeber user with cookies Ok. But, how use token session (for permanent login) instead PHPSESSID?
I view that my browser use default PHPSESSID session and not Token generated. How i use this token how id_session?
sorry i am a newbie, but I am confused and havetotally no idea how to setup/install gatekeeper,
I looked at:
"http://gatekeeper-auth.readthedocs.io/en/latest/installation-and-configuration/"
and installed composer and inserted the CMD command and the cmd get stuck there, and nothing happens
I then cloned the whole gatekeeper library with git,
then I accessed bin\setup.sh
and inserted real & correct hostname/username/password etc for a remote server and when I press enter
it showed error connecting or no error at all and the window closed
How do I migrate gatekeeper to the real server instead of localhost?
One can use something like Gatekeeper::findGroupById(1)
to get a group. I thought that this method, if it can't find the record, returns null - I doubt that not finding the record is an exceptional case - but it does throw an exception.
Am I right that there is no way to check if object exists in a clean way? Like:
if (is_null(Gatekeeper::findGroupByName('group_name'))) {
Gatekeeper::createGroup($attrs);
}
Do we have to deal with exceptions every time for this? If it is so, I think it's not a right way.
http://gatekeeper-auth.readthedocs.org/en/latest/authentication/
Here I can read how to log user in - but how to log user out? I mean, when an user presses "Log out" button - what code should be called?
One request from users is to have a way to say "this user has access to [this object] instance". I believe some kind of policy system is a good answer to this and think the Symfony Expression Language functionality might be a good (and more standardized) method to handle it.
Expression Language Documentation
Jonathan Wage's article about it
A token allows you to log in as a user, so should be considered largely equivalent to a password (though they don't have the reuse issue). Why are they stored with a simple hash instead of bcrypt/scrypt/pbkdf2?
I was all set to use Gatekeeper as a "drop in" authorization system for a new site I was working on, but I stopped when I realized it had no functions for updating user information, including passwords, and as noted by others, no support for deleting tokens/cookies when a user "logs off". I also found it was relying on deprecated mcrypt functions in the "randomlib" component and ended up being much more complex than my needs. (I could deal with the complexity if I didn't have to start out by adding fundamental missing features.)
It didn't help my view any that it pulls in eight different components that I had to go research independently to see what they did and how they might be useful.
The documentation says that update of users is provided but that's simply untrue. In fact, methods for updating any of the records are not provided. My mood was not improved by having to decipher the "phinx" files to figure out how the database should be laid out, since I was unable to use the provided "migrate" tool (an artifact of my environment.) It all feels half-done and I've decided my time is better spent doing things on my own more simply. I do appreciate the example of how to deal with some more tricky things such as "remember me" and "forgot password".
Unfortunately in its current configuration an attacker could force a denial of service on a particular user account indefinitely.
If an attacker know's the username of a victim, he/she can lock out a user if you deploy gatekeeper with the throttling enabled, by issuing no less than y fake requests every x minutes which is trivial to discover by probing the auth system and measuring the timings.
Problem:
Using this information leads to a denial of service in which the attacker places y fake requests and leads to gatekeeper locking the username for x minutes, where the attacker then repeats the number of requests, and again locks the account.
Solution:
Allow the use of "login tokens" (see here) to help prevent against login DoS and provide rate limiting on login attempts.
These tokens allow for a "next allowed login time" check to slow down the login process event further. Additional handling would be required to verify the current token timeout, possibly based on internally logged authenticate
attempts.
http://www.sitepoint.com/social-logins-php-hybridauth
http://hybridauth.sourceforge.net/index.html
The system would need to be changed to check if it's a "social" user and if so only require that the connection to the service has been made, that user details are present (name, email, etc) and an identifier is set for the service.
This would also require the settings for the social connections to be set in the .env
configuration file to be passed along to the social service (corresponding to an application set up on their side).
Functionality should exist that allows for a "clone" action on a user (user1) with provided:
...based on a provided user model instance (user1) and create a new user (user2). This should replicate all permissions and groups of user1 into user2.
Possible interface:
<?php
$result = Gatekeeper::cloneUser($user1, [
'username' => 'ccornutt2',
'password' => 'super-secret',
'email' => '[email protected]',
'firstName' => 'Chris',
'lastName' => 'Cornutt2'
]);
// $result should be a boolean based on success of operation
?>
Since 2.x has been out for a while, and we have PHP 7 now, I would recommend (and would like to help with) planning for a full BC-breaking 3.0 version with the following upgrades / requirements:
What do you think? I wouldn't mind directing some of my SitePoint firepower towards this goal, in order to make it the kickass popular package it was always meant to be. Is this realistic?
I accidentally wrote Gatekeeper::getGroupByName()
instead of Gatekeeper::findGroupByName()
and it gave me wonderful we-need-to-go-deeper error:
PHP Fatal error: Class 'Psecio\Gatekeeper\Handler\Exception\ModelNotFoundException' not found in /blah/htdocs/core/lib/vendor/psecio/gatekeeper/src/Psecio/Gatekeeper/Handler/FindBy.php on line 63
I presume, \Psecio\Gatekeeper\Exception\ModelNotFoundException class is meant.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.