Skip to content
This repository has been archived by the owner on Feb 6, 2024. It is now read-only.

Lock user after a configurable amount of failed login attempts #169

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

neckhair
Copy link

This PR introduces a new configuration option max_failed_login_attempts with a default value of 5.
When a user unsuccessfully tries to login 5 times in a row he gets locked for 5 minutes. Technically this is done by setting the attribute locked_until with an offset of 5 minutes.

After a configurable amount of consecutive failed login attempts the new attribute `locked_until` will be set.
The default amount of attempts is 5 and the default time the user is locked is 5 minutes.
After this commit locked users will still be able to log in. Locking out locked users is part of the next
commit.
@coveralls
Copy link

coveralls commented Aug 12, 2016

Coverage Status

Coverage increased (+0.06%) to 97.26% when pulling a6bb27c on ninech:lock-user into 9ebf812 on rbCAS:master.

When a user is locked (locked_until is in the future) he can't log in anymore.
@coveralls
Copy link

coveralls commented Aug 12, 2016

Coverage Status

Coverage increased (+0.06%) to 97.26% when pulling d382925 on ninech:lock-user into 9ebf812 on rbCAS:master.

@calmyournerves
Copy link
Contributor

Looks good to me! Maybe it should be possible to deactivate this feature (set max_failed_login_attempts to 0)?

@pencil, @luxflux: what do you think?

@luxflux
Copy link
Member

luxflux commented Aug 15, 2016

Maybe it should be possible to deactivate this feature (set max_failed_login_attempts to 0)?

Yes, I agree. But IMHO we should use -1 to disable the feature.

I also would like to see the lock duration configurable, what do you think @calmyournerves, @pencil?

Philippe Hässig added 2 commits August 19, 2016 13:50
It is no possible to disable the feature by setting the configuration for max failed login attempts to -1.
@coveralls
Copy link

coveralls commented Aug 19, 2016

Coverage Status

Coverage increased (+0.2%) to 97.391% when pulling 77f73f1 on ninech:lock-user into 9ebf812 on rbCAS:master.

@neckhair
Copy link
Author

@calmyournerves @luxflux @pencil The feature can now be disabled by setting max_failed_login_attempts to -1. Also the timeout can be set with the config option failed_login_lock_timeout.

@calmyournerves
Copy link
Contributor

LGTM

@neckhair
Copy link
Author

@pencil Good to merge?

@rwtsoftware
Copy link

I believe calling validate_login_credentials before calling locked? allows an attacker to use brute force with a timing attack in order to determine the password regardless of the account being locked out.

@@ -4,6 +4,8 @@ module CASino::SessionsHelper
include CASino::TicketGrantingTicketProcessor
include CASino::ServiceTicketProcessor

LOCK_TIMEOUT = 5.minutes
Copy link
Member

Choose a reason for hiding this comment

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

This constant seems to be unused.

To prevent possible timing attacks, a concern raised in [1], this defers
the credentials check until it's validated that the user is not
currently locked.

[1] rbCAS#169 (comment)
@coveralls
Copy link

coveralls commented Nov 3, 2016

Coverage Status

Coverage decreased (-0.8%) to 96.393% when pulling 8ec6562 on ninech:lock-user into 9ebf812 on rbCAS:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 97.388% when pulling fc49a9a on ninech:lock-user into 9ebf812 on rbCAS:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 97.388% when pulling fc49a9a on ninech:lock-user into 9ebf812 on rbCAS:master.

Prevents timing attack concern by checking the user lock status first. The concern seemed very valid and was raised in rbCAS#169 (comment) .
@coveralls
Copy link

coveralls commented Nov 3, 2016

Coverage Status

Coverage increased (+0.2%) to 97.385% when pulling 5167b81 on ninech:lock-user into 9ebf812 on rbCAS:master.

def log_failed_login(username)
def user_locked?(username)
CASino::User.where(username: username).to_a.any? do |user|
user.locked?
Copy link
Contributor

Choose a reason for hiding this comment

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

Is .to_a.any? necessary? Also, I would return after the first locked one:

CASino::User.where(username: username).each do |user|
  return true if user.locked?
end

Copy link

Choose a reason for hiding this comment

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

to_a is necessary to use Enumerable.any?:

any? [{ |obj| block }] → true or false
Passes each element of the collection to the given block. The method returns true if the block ever returns a value other than false or nil.

vs. the ActiveRecord::Relation.any?:

any?()
Returns true if there are any records.

Copy link

Choose a reason for hiding this comment

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

It would be totally possible to do it through each if that's somehow more efficient.

Copy link
Member

Choose a reason for hiding this comment

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

Why is where used anyway? Isn't this sufficient?

!!CASino::User.find_by(username: username).try(:locked?)

Copy link

@cimnine cimnine Nov 3, 2016

Choose a reason for hiding this comment

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

The same username can exist within different adapters. That's why.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm... if there are the same usernames on several backends, all of them should be locked? So just fetching the first one would be okay for this check...?

Copy link
Contributor

Choose a reason for hiding this comment

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

:seemsgood:

Copy link
Author

Choose a reason for hiding this comment

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

We could also just add a scope for locked users:

CASino::User.locked.where(username: username).any?

@coveralls
Copy link

coveralls commented Nov 4, 2016

Coverage Status

Coverage increased (+0.2%) to 97.385% when pulling b81827b on ninech:lock-user into 9ebf812 on rbCAS:master.

@cimnine
Copy link

cimnine commented Nov 11, 2016

@pencil could you spend a few Minutes to review the latest changes?

Copy link

@cimnine cimnine left a comment

Choose a reason for hiding this comment

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

There is an error in the code, which I discovered when we deployed our fork. Don't merge this branch until further notice! Never mind. It's all good. Just the migrations where missing. Usually we run them automatically, which it didn't this time (because of reasons).

There should not be a separate message when the account is locked
due to too many login attempts, as this would be an information leak.
@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 97.385% when pulling e57aa73 on ninech:lock-user into 9ebf812 on rbCAS:master.

Before, the user was claimed to be locked when any CASino::User sharing
the same username was locked. But a user is actually only locked, if he
does not have any CASino::User left, which is unlocked. The reason
herefore is, that he could have any secondary/legacy authentication
providers, on which he can never login. Therefore, one of his 'alter
egos' may be getting locked on any regular (and otherwise successful)
login attempt and he would be locked out regardless. That is now
changed, so that all CASino::Users sharing the same username must be
first be in locked state before one is considered locked.
@coveralls
Copy link

coveralls commented Nov 25, 2016

Coverage Status

Coverage increased (+0.2%) to 97.391% when pulling 4177592 on ninech:lock-user into 9ebf812 on rbCAS:master.

@coveralls
Copy link

coveralls commented Nov 25, 2016

Coverage Status

Coverage increased (+0.2%) to 97.391% when pulling b8b305e on ninech:lock-user into 9ebf812 on rbCAS:master.

@coveralls
Copy link

coveralls commented Nov 25, 2016

Coverage Status

Coverage increased (+0.2%) to 97.391% when pulling 672d176 on ninech:lock-user into 9ebf812 on rbCAS:master.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants