Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Version 0.7
- Support multiple id fields in SAML identity provider
- Include ``client_id`` in authlib logout URL since some OIDC providers mayrequire this
- Allow setting timeout for authlib token requests (default: 10 seconds)
- Add new ``MULTIPASS_HIDE_NO_SUCH_USER`` config setting to convert ``NoSuchUser``
exceptions to ``InvalidCredentials`` to avoid disclosing whether a username is valid
- Include the username in the ``identifier`` attribute of the ``InvalidCredentials``
exception so applications can apply e.g. per-username rate limiting

Version 0.6
-----------
Expand Down
1 change: 1 addition & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ The following configuration values exist for Flask-Multipass:
``MULTIPASS_FAILURE_CATEGORY`` Category of message when flashing after unsuccessful login
``MULTIPASS_ALL_MATCHING_IDENTITIES`` If true, all matching identities are passed after successful authentication
``MULTIPASS_REQUIRE_IDENTITY`` If true, ``IdentityRetrievalFailed`` is raised when no matching identities are found, otherwise empty list is passed
``MULTIPASS_HIDE_NO_SUCH_USER`` If true, ``InvalidCredentials`` instead of ``NoSuchUser`` is raised when no user is found in the system
====================================== =========================================

A configuration example can be found here: :ref:`config_example`
Expand Down
11 changes: 10 additions & 1 deletion flask_multipass/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
from werkzeug.exceptions import NotFound

from flask_multipass.auth import AuthProvider
from flask_multipass.exceptions import GroupRetrievalFailed, IdentityRetrievalFailed, MultipassException
from flask_multipass.exceptions import (
GroupRetrievalFailed,
IdentityRetrievalFailed,
InvalidCredentials,
MultipassException,
NoSuchUser,
)
from flask_multipass.identity import IdentityProvider
from flask_multipass.util import (
get_canonical_provider_map,
Expand Down Expand Up @@ -72,6 +78,7 @@ def init_app(self, app):
app.config.setdefault('MULTIPASS_FAILURE_CATEGORY', 'error')
app.config.setdefault('MULTIPASS_ALL_MATCHING_IDENTITIES', False)
app.config.setdefault('MULTIPASS_REQUIRE_IDENTITY', True)
app.config.setdefault('MULTIPASS_HIDE_NO_SUCH_USER', False)
with app.app_context():
self._create_login_rule()
state.auth_providers = ImmutableDict(self._create_providers('AUTH', AuthProvider))
Expand Down Expand Up @@ -528,6 +535,8 @@ def handle_login_form(self, provider, data):
try:
response = provider.process_local_login(data)
except MultipassException as e:
if isinstance(e, NoSuchUser) and current_app.config['MULTIPASS_HIDE_NO_SUCH_USER']:
e = InvalidCredentials(e.provider)
self.handle_auth_error(e)
else:
return response
Expand Down
3 changes: 2 additions & 1 deletion flask_multipass/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ def __init__(self, details=None, provider=None):
class InvalidCredentials(AuthenticationFailed):
"""Indicates a failure to authenticate using the given credentials."""

def __init__(self, details=None, provider=None):
def __init__(self, details=None, provider=None, identifier=None):
AuthenticationFailed.__init__(self, 'Invalid credentials', details=details, provider=provider)
self.identifier = identifier


class IdentityRetrievalFailed(MultipassException):
Expand Down
2 changes: 1 addition & 1 deletion flask_multipass/providers/ldap/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def process_local_login(self, data):
raise NoSuchUser(provider=self)
current_ldap.connection.simple_bind_s(user_dn, password)
except INVALID_CREDENTIALS:
raise InvalidCredentials(provider=self)
raise InvalidCredentials(provider=self, identifier=data['username'])
auth_info = AuthInfo(self, identifier=user_data[self.ldap_settings['uid']][0])
return self.multipass.handle_auth_success(auth_info)

Expand Down
2 changes: 1 addition & 1 deletion flask_multipass/providers/sqlalchemy.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def process_local_login(self, data):
if not identity:
raise NoSuchUser(provider=self)
if not self.check_password(identity, data['password']):
raise InvalidCredentials(provider=self)
raise InvalidCredentials(provider=self, identifier=data['identifier'])
auth_info = AuthInfo(self, identity=identity)
return self.multipass.handle_auth_success(auth_info)

Expand Down
2 changes: 1 addition & 1 deletion flask_multipass/providers/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def process_local_login(self, data):
if password is None:
raise NoSuchUser(provider=self)
if password != data['password']:
raise InvalidCredentials(provider=self)
raise InvalidCredentials(provider=self, identifier=data['username'])
auth_info = AuthInfo(self, username=data['username'])
return self.multipass.handle_auth_success(auth_info)

Expand Down