mirror of
https://git.anonymousland.org/anonymousland/synapse-product.git
synced 2024-10-01 08:25:44 -04:00
Support 3PID login in password providers (#4931)
Adds a new method, check_3pid_auth, which gives password providers the chance to allow authentication with third-party identifiers such as email or msisdn.
This commit is contained in:
parent
903f04c21f
commit
bbd244c7b2
1
changelog.d/4931.feature
Normal file
1
changelog.d/4931.feature
Normal file
@ -0,0 +1 @@
|
||||
Add ability for password providers to login/register a user via 3PID (email, phone).
|
@ -75,6 +75,20 @@ Password auth provider classes may optionally provide the following methods.
|
||||
result from the ``/login`` call (including ``access_token``, ``device_id``,
|
||||
etc.)
|
||||
|
||||
``someprovider.check_3pid_auth``\(*medium*, *address*, *password*)
|
||||
|
||||
This method, if implemented, is called when a user attempts to register or
|
||||
log in with a third party identifier, such as email. It is passed the
|
||||
medium (ex. "email"), an address (ex. "jdoe@example.com") and the user's
|
||||
password.
|
||||
|
||||
The method should return a Twisted ``Deferred`` object, which resolves to
|
||||
a ``str`` containing the user's (canonical) User ID if authentication was
|
||||
successful, and ``None`` if not.
|
||||
|
||||
As with ``check_auth``, the ``Deferred`` may alternatively resolve to a
|
||||
``(user_id, callback)`` tuple.
|
||||
|
||||
``someprovider.check_password``\(*user_id*, *password*)
|
||||
|
||||
This method provides a simpler interface than ``get_supported_login_types``
|
||||
|
@ -621,13 +621,13 @@ class Auth(object):
|
||||
|
||||
Returns:
|
||||
True if the the sender is allowed to redact the target event if the
|
||||
target event was created by them.
|
||||
target event was created by them.
|
||||
False if the sender is allowed to redact the target event with no
|
||||
further checks.
|
||||
further checks.
|
||||
|
||||
Raises:
|
||||
AuthError if the event sender is definitely not allowed to redact
|
||||
the target event.
|
||||
the target event.
|
||||
"""
|
||||
return event_auth.check_redaction(room_version, event, auth_events)
|
||||
|
||||
@ -743,9 +743,9 @@ class Auth(object):
|
||||
|
||||
Returns:
|
||||
Deferred[tuple[str, str|None]]: Resolves to the current membership of
|
||||
the user in the room and the membership event ID of the user. If
|
||||
the user is not in the room and never has been, then
|
||||
`(Membership.JOIN, None)` is returned.
|
||||
the user in the room and the membership event ID of the user. If
|
||||
the user is not in the room and never has been, then
|
||||
`(Membership.JOIN, None)` is returned.
|
||||
"""
|
||||
|
||||
try:
|
||||
@ -777,13 +777,13 @@ class Auth(object):
|
||||
|
||||
Args:
|
||||
user_id(str|None): If present, checks for presence against existing
|
||||
MAU cohort
|
||||
MAU cohort
|
||||
|
||||
threepid(dict|None): If present, checks for presence against configured
|
||||
reserved threepid. Used in cases where the user is trying register
|
||||
with a MAU blocked server, normally they would be rejected but their
|
||||
threepid is on the reserved list. user_id and
|
||||
threepid should never be set at the same time.
|
||||
reserved threepid. Used in cases where the user is trying register
|
||||
with a MAU blocked server, normally they would be rejected but their
|
||||
threepid is on the reserved list. user_id and
|
||||
threepid should never be set at the same time.
|
||||
"""
|
||||
|
||||
# Never fail an auth check for the server notices users or support user
|
||||
|
@ -745,6 +745,42 @@ class AuthHandler(BaseHandler):
|
||||
errcode=Codes.FORBIDDEN
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def check_password_provider_3pid(self, medium, address, password):
|
||||
"""Check if a password provider is able to validate a thirdparty login
|
||||
|
||||
Args:
|
||||
medium (str): The medium of the 3pid (ex. email).
|
||||
address (str): The address of the 3pid (ex. jdoe@example.com).
|
||||
password (str): The password of the user.
|
||||
|
||||
Returns:
|
||||
Deferred[(str|None, func|None)]: A tuple of `(user_id,
|
||||
callback)`. If authentication is successful, `user_id` is a `str`
|
||||
containing the authenticated, canonical user ID. `callback` is
|
||||
then either a function to be later run after the server has
|
||||
completed login/registration, or `None`. If authentication was
|
||||
unsuccessful, `user_id` and `callback` are both `None`.
|
||||
"""
|
||||
for provider in self.password_providers:
|
||||
if hasattr(provider, "check_3pid_auth"):
|
||||
# This function is able to return a deferred that either
|
||||
# resolves None, meaning authentication failure, or upon
|
||||
# success, to a str (which is the user_id) or a tuple of
|
||||
# (user_id, callback_func), where callback_func should be run
|
||||
# after we've finished everything else
|
||||
result = yield provider.check_3pid_auth(
|
||||
medium, address, password,
|
||||
)
|
||||
if result:
|
||||
# Check if the return value is a str or a tuple
|
||||
if isinstance(result, str):
|
||||
# If it's a str, set callback function to None
|
||||
result = (result, None)
|
||||
defer.returnValue(result)
|
||||
|
||||
defer.returnValue((None, None))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _check_local_password(self, user_id, password):
|
||||
"""Authenticate a user against the local password database.
|
||||
@ -756,7 +792,8 @@ class AuthHandler(BaseHandler):
|
||||
user_id (unicode): complete @user:id
|
||||
password (unicode): the provided password
|
||||
Returns:
|
||||
(unicode) the canonical_user_id, or None if unknown user / bad password
|
||||
Deferred[unicode] the canonical_user_id, or Deferred[None] if
|
||||
unknown user/bad password
|
||||
|
||||
Raises:
|
||||
LimitExceededError if the ratelimiter's login requests count for this
|
||||
|
@ -147,8 +147,14 @@ class BaseProfileHandler(BaseHandler):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def set_displayname(self, target_user, requester, new_displayname, by_admin=False):
|
||||
"""target_user is the user whose displayname is to be changed;
|
||||
auth_user is the user attempting to make this change."""
|
||||
"""Set the displayname of a user
|
||||
|
||||
Args:
|
||||
target_user (UserID): the user whose displayname is to be changed.
|
||||
requester (Requester): The user attempting to make this change.
|
||||
new_displayname (str): The displayname to give this user.
|
||||
by_admin (bool): Whether this change was made by an administrator.
|
||||
"""
|
||||
if not self.hs.is_mine(target_user):
|
||||
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||
|
||||
|
@ -171,7 +171,7 @@ class RegistrationHandler(BaseHandler):
|
||||
api.constants.UserTypes, or None for a normal user.
|
||||
default_display_name (unicode|None): if set, the new user's displayname
|
||||
will be set to this. Defaults to 'localpart'.
|
||||
address (str|None): the IP address used to perform the regitration.
|
||||
address (str|None): the IP address used to perform the registration.
|
||||
Returns:
|
||||
A tuple of (user_id, access_token).
|
||||
Raises:
|
||||
@ -623,7 +623,7 @@ class RegistrationHandler(BaseHandler):
|
||||
admin (boolean): is an admin user?
|
||||
user_type (str|None): type of user. One of the values from
|
||||
api.constants.UserTypes, or None for a normal user.
|
||||
address (str|None): the IP address used to perform the regitration.
|
||||
address (str|None): the IP address used to perform the registration.
|
||||
|
||||
Returns:
|
||||
Deferred
|
||||
@ -721,9 +721,9 @@ class RegistrationHandler(BaseHandler):
|
||||
access_token (str|None): The access token of the newly logged in
|
||||
device, or None if `inhibit_login` enabled.
|
||||
bind_email (bool): Whether to bind the email with the identity
|
||||
server
|
||||
server.
|
||||
bind_msisdn (bool): Whether to bind the msisdn with the identity
|
||||
server
|
||||
server.
|
||||
"""
|
||||
if self.hs.config.worker_app:
|
||||
yield self._post_registration_client(
|
||||
@ -765,7 +765,7 @@ class RegistrationHandler(BaseHandler):
|
||||
"""A user consented to the terms on registration
|
||||
|
||||
Args:
|
||||
user_id (str): The user ID that consented
|
||||
user_id (str): The user ID that consented.
|
||||
consent_version (str): version of the policy the user has
|
||||
consented to.
|
||||
"""
|
||||
|
@ -73,14 +73,26 @@ class ModuleApi(object):
|
||||
"""
|
||||
return self._auth_handler.check_user_exists(user_id)
|
||||
|
||||
def register(self, localpart):
|
||||
"""Registers a new user with given localpart
|
||||
@defer.inlineCallbacks
|
||||
def register(self, localpart, displayname=None):
|
||||
"""Registers a new user with given localpart and optional
|
||||
displayname.
|
||||
|
||||
Args:
|
||||
localpart (str): The localpart of the new user.
|
||||
displayname (str|None): The displayname of the new user. If None,
|
||||
the user's displayname will default to `localpart`.
|
||||
|
||||
Returns:
|
||||
Deferred: a 2-tuple of (user_id, access_token)
|
||||
"""
|
||||
# Register the user
|
||||
reg = self.hs.get_registration_handler()
|
||||
return reg.register(localpart=localpart)
|
||||
user_id, access_token = yield reg.register(
|
||||
localpart=localpart, default_display_name=displayname,
|
||||
)
|
||||
|
||||
defer.returnValue((user_id, access_token))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def invalidate_access_token(self, access_token):
|
||||
|
@ -201,6 +201,24 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
# We store all email addreses as lowercase in the DB.
|
||||
# (See add_threepid in synapse/handlers/auth.py)
|
||||
address = address.lower()
|
||||
|
||||
# Check for login providers that support 3pid login types
|
||||
canonical_user_id, callback_3pid = (
|
||||
yield self.auth_handler.check_password_provider_3pid(
|
||||
medium,
|
||||
address,
|
||||
login_submission["password"],
|
||||
)
|
||||
)
|
||||
if canonical_user_id:
|
||||
# Authentication through password provider and 3pid succeeded
|
||||
result = yield self._register_device_with_callback(
|
||||
canonical_user_id, login_submission, callback_3pid,
|
||||
)
|
||||
defer.returnValue(result)
|
||||
|
||||
# No password providers were able to handle this 3pid
|
||||
# Check local store
|
||||
user_id = yield self.hs.get_datastore().get_user_id_by_threepid(
|
||||
medium, address,
|
||||
)
|
||||
@ -223,20 +241,43 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
if "user" not in identifier:
|
||||
raise SynapseError(400, "User identifier is missing 'user' key")
|
||||
|
||||
auth_handler = self.auth_handler
|
||||
canonical_user_id, callback = yield auth_handler.validate_login(
|
||||
canonical_user_id, callback = yield self.auth_handler.validate_login(
|
||||
identifier["user"],
|
||||
login_submission,
|
||||
)
|
||||
|
||||
result = yield self._register_device_with_callback(
|
||||
canonical_user_id, login_submission, callback,
|
||||
)
|
||||
defer.returnValue(result)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _register_device_with_callback(
|
||||
self,
|
||||
user_id,
|
||||
login_submission,
|
||||
callback=None,
|
||||
):
|
||||
""" Registers a device with a given user_id. Optionally run a callback
|
||||
function after registration has completed.
|
||||
|
||||
Args:
|
||||
user_id (str): ID of the user to register.
|
||||
login_submission (dict): Dictionary of login information.
|
||||
callback (func|None): Callback function to run after registration.
|
||||
|
||||
Returns:
|
||||
result (Dict[str,str]): Dictionary of account information after
|
||||
successful registration.
|
||||
"""
|
||||
device_id = login_submission.get("device_id")
|
||||
initial_display_name = login_submission.get("initial_device_display_name")
|
||||
device_id, access_token = yield self.registration_handler.register_device(
|
||||
canonical_user_id, device_id, initial_display_name,
|
||||
user_id, device_id, initial_display_name,
|
||||
)
|
||||
|
||||
result = {
|
||||
"user_id": canonical_user_id,
|
||||
"user_id": user_id,
|
||||
"access_token": access_token,
|
||||
"home_server": self.hs.hostname,
|
||||
"device_id": device_id,
|
||||
|
Loading…
Reference in New Issue
Block a user