-
Notifications
You must be signed in to change notification settings - Fork 7
Support aggregation of attributes from multiple databases #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…_docs Add documentation for UNIX socket connections
…ation queries, attribute queries selectable based on which authentication query was successful
…es on PHP 8.1 and 8.2
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #20 +/- ##
============================================
- Coverage 71.42% 66.03% -5.40%
- Complexity 44 148 +104
============================================
Files 2 5 +3
Lines 147 530 +383
============================================
+ Hits 105 350 +245
- Misses 42 180 +138 🚀 New features to boost your workflow:
|
f6a7250 to
8c21582
Compare
|
@nathanjrobertson Could you please re-generate the |
tvdijen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good Nathan! Let's see if Ben has any remarks.
@tvdijen Done. I ended up creating a new Interface to make the inheritance in the test hierarchy more obvious to phpstan, which brought the number of exceptions needed in the |
|
Perfect! Just two more things..
|
…ev in composer.json
Thanks Tim. Both fixed now. I think you meant |
|
I will give this a more detailed viewing starting in the next days. A bit of my initial reading is around the PasswordVerify which you mention is secondary to your use cases. Initial thoughts are that we should move The intent of checking each returned I would like to simplify the logic of the new I am always a bit suspicious of code with a if(...) { continue; } block right at the bottom of a for/while loop. |
Yes, my bad! Thanks for all your hard work! |
Although I don't personally use it, I respect it needs to be there and be 100% backward compatible. I don't see it as a second class citizen - more just a feature I don't personally have a need for, but that somebody else definitely does.
That's fair enough. I can do that.
Indeed, the intent is that the first success is the final success, and any later authqueries are never evaluated. Hmm. My intent was to retain the existing functionality as you describe - the inner foreach loop is "foreach row returned for this query", and this elseif checks the If you can see some way I'm not covering that case then that's a bug. 100% backward compatibility with new functionality added is explicitly the goal.
No. The only The code is conservative and throws an error in the case where all the tuple rows don't match in the Let me know if you've got a preference on this one - there's no backward compatibility risk, as Version 1 never supported multiple authqueries. My feeling is that throwing the error is better, as there's a decent chance this authquery was supposed to be the winning one (given the user exists in the database) if it weren't for a data issue.
The continue goes to the outermost foreach loop is "foreach authquery". It's essentially the same as the existing PasswordVerify::login(), where after the end of the "foreach returned tuple, check all passwordhashcolumn values are the same" section. Because the old code only supports a single auth query, it throws the |
That one is fixed. Yes, it is a fair bit tidier and easier to read. Thank you. |
|
Just the one bug picked up in our first round of integration testing - extra config like |
src/Auth/Source/SQL2.php
Outdated
| $hashColumn, | ||
| )); | ||
| throw new Error\Error('WRONGUSERPASS'); | ||
| } elseif (strlen($row[$hashColumn]) === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this strlen test is better as just a raw if near the top of this foreach (above line 448 for example). The strlen($row[$hashColumn]) must always be > 0 regardless of anything else that is happening.
I can analyse that if $passwordHash === null and strlen == 0 and $passwordHash != $row[$hashColumn] will not be true when $passwordHash == null and strlen == 0, so the final elseif will run. But it is simpler logic to just lift the strlen($row[$hashColumn]) > 0 test out of this if/elseif/elsif block. If it is a single if() for every result inspected in the result set it is very clear it always runs and negates bad data without much thought needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, fair enough. I think I've captured the above in a495d6b.
|
|
||
| Options | ||
| ------- | ||
| - Most commonly, as a part of the SQL query itself (ie. using SQL functions to hash a parameterized password and compare that to a value stored in the database). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These docs are great stuff!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. What gets documented (well) gets used.
| } | ||
| } | ||
|
|
||
| // At the end, disconnect from all databases |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if $db is still set from above then at least one database will be reported as disconnected but will still be open?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Fixed in 6b4e981.
I've been thinking through this one in the past few days. I think after this PR is merged I'll write and submit some improvements to the cleanup process. In the case where there are multiple databases on the same physical server, it means we are holding a number of connections open by the end, and we could easily hit a connection limit. I'm expecting to have this problem myself - we might have 5-10 attribute queries against 5-10 different databases on the one physical database host, and if there are concurrent connection attempts that could cause us to run out of connections.
My thinking is to be a bit more smart and aggressive on reading ahead as to whether a connection needs to be retained, and if not, closing it earlier than the very end.
But for the minute, for this PR, I'll leave it as is. But know I'm thinking of making the disconnect cleanup process more aggressive in the future.
| } | ||
| } else { | ||
| throw new Exception('Missing required attribute \'databases\' for authentication source ' . $this->authId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is simpler for this to check if array_key_exists('databases', $config) is not true and throw and then the code can just continue from there. Having this in an else here means you have to check back to what that else is for and work it out.
If the key is not set then an exception. The code can just flow on from there without any more if/else logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 630316d
src/Auth/Source/SQL2.php
Outdated
| throw new Exception( | ||
| 'Missing required attribute \'auth_queries\' for authentication source ' . | ||
| $this->authId, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the if/else above on line 112.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 618e0d3
|
I noticed that |
|
Thanks for the feedback @monkeyiq. I'll get to addressing it all later this week or early next week. |
Now that you've pointed that out it annoyed me enough to fix the variable name. Fixed in 9b83f78.
Thanks for pointing that out. |
|
@monkeyiq Quick bump - this one is ready for re-review. Thanks. |
|
I am planning on taking a further look at the Use PSR-14 events PR and then returning to this. The interaction of the PSR-14 and SSP 2.5 release date sort of pushed that PR to the top. I haven't forgotten about this PR and will return to it likely on the weekend or soon after. Sorry about the delay. |
|
Oh whoops, that wasn't supposed to happen :') Please keep this on your radar @monkeyiq |
sqlauth:SQLhas support for multiple queries, however currently they must all run against the one SQL database. In larger environments, it would be useful to be able to gather attributes from several separate databases.So that's how this one started out. In exploring that requirement, I quickly found that separating the database configuration from the query configuration was required, and my hacks to the existing ("Version 1") configuration format was getting messy.
So, this PR proposes a new configuration format for
authsources.php. The new ("Version 2") format separates the three key entities - databases, authentication queries and authorization queries, and as a result allows multiple of each type:Before anyone gets too upset, there is a full set of backward compatible classes (
sqlauth:SQL1Compat,sqlauth:PasswordVerify1Compat). In addition, the existingsqlauth:SQLandsqlauth:PasswordVerifyare untouched (for the minute), but the intent is that after the new code and "compat" interfaces prove stable,SQL.phpbecomesclass SQL extends SQL1Compat {}(likewise forPasswordVerify.php), meaning people are transparently moved across to the new code without any configuration changes. So, it maintains full backward compatibility from a configuration interface point of view, but without having to drag the old configuration format baggage.The main benefits of the new "Version 2" configuration format are:
:username) not being the shared common key between an Authentication query and an Attribute query. There was an issue before if somebody logged in with only a candidate key (eg. a unique email address), but the primary key shared between tables was something else (eg. a numeric customer ID), there wasn't a way to say "grab this ID in the authentication query and provide it for use in attribute queries. Now there is (extract_userid_from).select [...] from [...] where (integer_id=:username or email_address=:username) and password=[...]will cause an SQL error when trying to login with an email address (asinteger_id=:usernamewill fail with an int vs string comparison error). Multiple authentication queries with theusername_regexset on each (one query for the integer version, one for the email version) gets around this.only_for_authparameter makes it possible to only run a given attribute query if the user authenticated using one of the authentication queries referenced in an "allow list".password_verify()support).Also note:
I don't have a production use case for the
password_verify()support, so the unit tests are the extent of my Version 2 and compat testing with that functionality. It's here such that Version 2 has all the features Version 1 did. Version 2 implements it in the main module, not as a separatePasswordVerifysubclass (with much code duplication) as was done in Version 1.Internally we're testing all of this at the moment, starting with the "compat" interfaces, then we'll switch to "Version 2" configuration, before starting to use all the multi-database support. If the project maintainers could take a look at this one and give feedback on what changes you'd like for this to be merged then that'd be great.