Skip to content
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

Have I Been Pwned Integration (Password security enhancements) #832 #974

Closed
wants to merge 57 commits into from

Conversation

amosfolz
Copy link
Contributor

@amosfolz amosfolz commented May 6, 2019

This will need more work but I feel is at a place to open up for some feedback.
The proposed feature adds two additional config values under site.password_security.

'password_security' => [
             'enforce_no_compromised'   => '5', // Set to false to turn off this feature. Otherwise, provide a numeric string, which sets the maximum number
                                       // of times that is "acceptable" for a password to have appeared in breaches. The recommended and most secure
                                       // option is '0' - meaning only passwords that are not on the list of compromised passwords will be allowed.

             'enforce_update_passwords' => true // Settings this to true will check user's passwords against list of known compromised passwords at each log on.
]

(I am open to suggestions on the naming convention for these).

@codecov
Copy link

codecov bot commented May 6, 2019

Codecov Report

Merging #974 (a5432d6) into develop (c92bf0b) will decrease coverage by 0.12%.
The diff coverage is 58.06%.

Impacted file tree graph

@@              Coverage Diff              @@
##             develop     #974      +/-   ##
=============================================
- Coverage      67.76%   67.64%   -0.13%     
- Complexity      1997     2021      +24     
=============================================
  Files            169      171       +2     
  Lines           6995     7086      +91     
=============================================
+ Hits            4740     4793      +53     
- Misses          2255     2293      +38     
Impacted Files Coverage Δ Complexity Δ
.../src/Database/Migrations/v430/UpdateUsersTable.php 100.00% <ø> (ø) 3.00 <0.00> (ø)
...nkles/account/src/Controller/AccountController.php 95.59% <25.92%> (-4.41%) 92.00 <2.00> (+6.00) ⬇️
.../account/src/ServicesProvider/ServicesProvider.php 77.86% <44.44%> (-1.96%) 11.00 <0.00> (+1.00) ⬇️
...kles/account/src/Authenticate/PasswordSecurity.php 70.21% <70.21%> (ø) 14.00 <14.00> (?)
...grations/v430/AddPasswordResetColumnUsersTable.php 100.00% <100.00%> (ø) 3.00 <3.00> (?)
...account/src/Repository/PasswordResetRepository.php 100.00% <100.00%> (ø) 1.00 <0.00> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c92bf0b...a5432d6. Read the comment docs.

Copy link
Member

@lcharette lcharette left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The controller is really long... While having private function in the controller is not wrong, I think it would make more sense to put both checkPassword and checkHash in a separate (static) class? Would make easier to use those methods elsewhere as well as writing proper tests.

@amosfolz
Copy link
Contributor Author

amosfolz commented May 7, 2019

The controller is really long... While having private function in the controller is not wrong, I think it would make more sense to put both checkPassword and checkHash in a separate (static) class? Would make easier to use those methods elsewhere as well as writing proper tests.

I thought they would probably need to be moved. I will need some guidance on where to move them to...

@Silic0nS0ldier
Copy link
Member

Looks like there are some changes on master that aren't on develop. Probably want to sync it up. Would make the diff clearer.

Copy link
Member

@Silic0nS0ldier Silic0nS0ldier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few things to be addressed here. There will likely need to be some discussion in most areas first though.

app/sprinkles/account/config/default.php Outdated Show resolved Hide resolved
app/sprinkles/account/locale/en_US/messages.php Outdated Show resolved Hide resolved
app/sprinkles/account/src/Controller/AccountController.php Outdated Show resolved Hide resolved
app/sprinkles/account/src/Controller/AccountController.php Outdated Show resolved Hide resolved
@lcharette lcharette added this to the 4.3.0 milestone May 14, 2019
@amosfolz
Copy link
Contributor Author

The experience around the (root) admin account being on a compromised passwords list could use some work. Namely, the admin is not forced to perform a password reset to login despite the recorded user activity stating that they have to. Probably not great that the account with the most access isn't covered by these additional protections.

Good catch! This is because the if that checks if flag_password_reset_required is set was using an instance of currentUser from prior to that field being set. (so it wasn't enforcing the change until the next login.) This should be fixed now.

@amosfolz
Copy link
Contributor Author

I believe this is ready for another review.

Copy link
Member

@Silic0nS0ldier Silic0nS0ldier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few issues, but the core logic looks good. I haven't tested it this time however.

*
* @return array Array of password hashes in the format c2d18a7d49b0d4260769eb03d027066d29a:181 - or <hash>:<number of breaches.
*/
private function getHashArrayFromAPI($hashPrefix)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this handle connectivity issues/outage of the HIBP Passwords API? It looks like there isn't any handling for it, potentially it would prevent login.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right.

I'll think about the best way to handle this. We probably want to try to query HIBP at least a few times before allowing user to login and bypass the check...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got to be careful with retry logic. At scale, it can translate to a massive spike in requests for an API.
Possible scenarios include;

  • API returns 500 error due to a fault, could be an edge case or everything, retry logic here would easily result in a larger number of requests (CloudFlare wouldn't cache these either, so it would fairly consistently hit the function itself until potentially DDoS protection gets triggered).
  • Response codes indicate that retries should not be attempted exist, we would be wasting our own time retrying these.
  • Connection timeouts could be attributed to failures anywhere along the connection (local misconfiguration, API hit its own response timeout and terminated).
  • Deliberate rate limiting by HIBP (if a UF site were to become heavily used, we could end up hitting this)

Ultimately, it may come down to not retrying at all and logging that the check failed. HIBP passwords should be supplemental to the security such that any UF site doesn't get busted by any issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to fix this in the recent commits. (Also switched over to Guzzle)

/**
* {@inheritdoc}
*/
public static $dependencies = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use reflection here? E.g;

use \UserFrosting\Sprinkle\Account\Database\Migrations\v400\GroupsTable`;

...

public static $dependencies = [
    GroupsTable::class,
    ...
];

I generally prefer to have source level references where possible as it makes tracking down references later, and performing refactors (with tooling assistance) much quicker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could. If this is the preference I should probably update the other two "modifying" migrations in 4.3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lcharette Should I change this and the other migrations that were already merged?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Both will work, but reflection is easier to work with in IDE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a leading \ is being added somewhere inside the migrator.

 [ERROR] Unfulfillable migrations found ::
         => \UserFrosting\Sprinkle\Account\Database\Migrations\v430\AddPasswordResetColumnUsersTable (Missing dependency
         : UserFrosting\Sprinkle\Account\Database\Migrations\v400\GroupsTable)

Some changes will likely need to be made in the migrator if we want to make this work. Unless we want to try to make those changes for 4.3, should probably just hold off on this for now.

@@ -33,7 +33,9 @@ class PasswordResetRepository extends TokenRepository
protected function updateUser(UserInterface $user, $args)
{
$user->password = Password::hash($args['password']);
$user->flag_password_reset_required = false;
// TODO: generate user activity? or do this in controller?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically outside scope of this, but does this have an answer yet? Are we logging updates to the user at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like it is happening in controller or here, so I would say no.

Copy link
Contributor Author

@amosfolz amosfolz Jul 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make sense to log some type of message "User set a new password".

Then the controller would be responsible for logging activity messages related to why a user needs to reset their password. (like HIBP, admin forcing reset, etc..)

@lcharette lcharette modified the milestones: 4.3.0, 4.4.0 Jul 27, 2019
@lcharette
Copy link
Member

FYI, I tagged this to 4.4.x as I think it will require more testing from both @Silic0nS0ldier and I.

@lcharette lcharette modified the milestones: 4.4.0, 4.5.0 Jan 11, 2020
@Silic0nS0ldier
Copy link
Member

As can be seen, I'm trying to resurrect this PR nearly 2 years on. Its been synced with master and had some issues resolved (mainly PHP crashes relating to recently completed deprecations and type violations).

@lcharette
Copy link
Member

Closing this, as it need to be updated for UF5. It should be implement as it's own sprinkle anyway.

@lcharette lcharette closed this Nov 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core feature request Feature request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants