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

Factor out a validate_user_via_ui_auth method #2728

Merged
merged 1 commit into from
Dec 5, 2017
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
43 changes: 43 additions & 0 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,49 @@ def __init__(self, hs):
)
self._supported_login_types = frozenset(login_types)

@defer.inlineCallbacks
def validate_user_via_ui_auth(self, requester, request_body, clientip):
"""
Checks that the user is who they claim to be, via a UI auth.

This is used for things like device deletion and password reset where
the user already has a valid access token, but we want to double-check
that it isn't stolen by re-authenticating them.

Args:
requester (Requester): The user, as given by the access token

request_body (dict): The body of the request sent by the client

clientip (str): The IP address of the client.

Returns:
defer.Deferred[dict]: the parameters for this request (which may
have been given only in a previous call).

Raises:
InteractiveAuthIncompleteError if the client has not yet completed
any of the permitted login flows

AuthError if the client has completed a login flow, and it gives
a different user to `requester`
"""

# we only support password login here
flows = [[LoginType.PASSWORD]]

result, params, _ = yield self.check_auth(
flows, request_body, clientip,
)

user_id = result[LoginType.PASSWORD]

# check that the UI auth matched the access token
if user_id != requester.user.to_string():
raise AuthError(403, "Invalid auth")

defer.returnValue(params)

@defer.inlineCallbacks
def check_auth(self, flows, clientdict, clientip):
"""
Expand Down
107 changes: 47 additions & 60 deletions synapse/rest/client/v2_alpha/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from synapse.api.auth import has_access_token
from synapse.api.constants import LoginType
from synapse.api.errors import Codes, LoginError, SynapseError
from synapse.api.errors import Codes, SynapseError
from synapse.http.servlet import (
RestServlet, assert_params_in_request,
parse_json_object_from_request,
Expand Down Expand Up @@ -103,44 +103,50 @@ def __init__(self, hs):
@interactive_auth_handler
@defer.inlineCallbacks
def on_POST(self, request):
yield run_on_reactor()

body = parse_json_object_from_request(request)

result, params, _ = yield self.auth_handler.check_auth([
[LoginType.PASSWORD],
[LoginType.EMAIL_IDENTITY],
[LoginType.MSISDN],
], body, self.hs.get_ip_from_request(request))
# there are two possibilities here. Either the user does not have an
# access token, and needs to do a password reset; or they have one and
# need to validate their identity.
#
# In the first case, we offer a couple of means of identifying
# themselves (email and msisdn, though it's unclear if msisdn actually
# works).
#
# In the second case, we require a password to confirm their identity.

user_id = None
requester = None

if LoginType.PASSWORD in result:
# if using password, they should also be logged in
if has_access_token(request):
requester = yield self.auth.get_user_by_req(request)
user_id = requester.user.to_string()
if user_id != result[LoginType.PASSWORD]:
raise LoginError(400, "", Codes.UNKNOWN)
elif LoginType.EMAIL_IDENTITY in result:
threepid = result[LoginType.EMAIL_IDENTITY]
if 'medium' not in threepid or 'address' not in threepid:
raise SynapseError(500, "Malformed threepid")
if threepid['medium'] == 'email':
# For emails, transform the address to lowercase.
# We store all email addreses as lowercase in the DB.
# (See add_threepid in synapse/handlers/auth.py)
threepid['address'] = threepid['address'].lower()
# if using email, we must know about the email they're authing with!
threepid_user_id = yield self.datastore.get_user_id_by_threepid(
threepid['medium'], threepid['address']
params = yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)
if not threepid_user_id:
raise SynapseError(404, "Email address not found", Codes.NOT_FOUND)
user_id = threepid_user_id
user_id = requester.user.to_string()
else:
logger.error("Auth succeeded but no known type!", result.keys())
raise SynapseError(500, "", Codes.UNKNOWN)
requester = None
result, params, _ = yield self.auth_handler.check_auth(
[[LoginType.EMAIL_IDENTITY], [LoginType.MSISDN]],
body, self.hs.get_ip_from_request(request),
)

if LoginType.EMAIL_IDENTITY in result:
threepid = result[LoginType.EMAIL_IDENTITY]
if 'medium' not in threepid or 'address' not in threepid:
raise SynapseError(500, "Malformed threepid")
if threepid['medium'] == 'email':
# For emails, transform the address to lowercase.
# We store all email addreses as lowercase in the DB.
# (See add_threepid in synapse/handlers/auth.py)
threepid['address'] = threepid['address'].lower()
# if using email, we must know about the email they're authing with!
threepid_user_id = yield self.datastore.get_user_id_by_threepid(
threepid['medium'], threepid['address']
)
if not threepid_user_id:
raise SynapseError(404, "Email address not found", Codes.NOT_FOUND)
user_id = threepid_user_id
else:
logger.error("Auth succeeded but no known type!", result.keys())
raise SynapseError(500, "", Codes.UNKNOWN)

if 'new_password' not in params:
raise SynapseError(400, "", Codes.MISSING_PARAM)
Expand Down Expand Up @@ -171,40 +177,21 @@ def __init__(self, hs):
def on_POST(self, request):
body = parse_json_object_from_request(request)

# if the caller provides an access token, it ought to be valid.
requester = None
if has_access_token(request):
requester = yield self.auth.get_user_by_req(
request,
) # type: synapse.types.Requester
requester = yield self.auth.get_user_by_req(request)

# allow ASes to dectivate their own users
if requester and requester.app_service:
if requester.app_service:
yield self._deactivate_account_handler.deactivate_account(
requester.user.to_string()
)
defer.returnValue((200, {}))

result, params, _ = yield self.auth_handler.check_auth([
[LoginType.PASSWORD],
], body, self.hs.get_ip_from_request(request))

if LoginType.PASSWORD in result:
user_id = result[LoginType.PASSWORD]
# if using password, they should also be logged in
if requester is None:
raise SynapseError(
400,
"Deactivate account requires an access_token",
errcode=Codes.MISSING_TOKEN
)
if requester.user.to_string() != user_id:
raise LoginError(400, "", Codes.UNKNOWN)
else:
logger.error("Auth succeeded but no known type!", result.keys())
raise SynapseError(500, "", Codes.UNKNOWN)

yield self._deactivate_account_handler.deactivate_account(user_id)
yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)
yield self._deactivate_account_handler.deactivate_account(
requester.user.to_string(),
)
defer.returnValue((200, {}))


Expand Down
26 changes: 12 additions & 14 deletions synapse/rest/client/v2_alpha/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

from twisted.internet import defer

from synapse.api import constants, errors
from synapse.api import errors
from synapse.http import servlet
from ._base import client_v2_patterns, interactive_auth_handler

Expand Down Expand Up @@ -63,6 +63,8 @@ def __init__(self, hs):
@interactive_auth_handler
@defer.inlineCallbacks
def on_POST(self, request):
requester = yield self.auth.get_user_by_req(request)

try:
body = servlet.parse_json_object_from_request(request)
except errors.SynapseError as e:
Expand All @@ -78,11 +80,10 @@ def on_POST(self, request):
400, "No devices supplied", errcode=errors.Codes.MISSING_PARAM
)

result, params, _ = yield self.auth_handler.check_auth([
[constants.LoginType.PASSWORD],
], body, self.hs.get_ip_from_request(request))
result, params, _ = yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)

requester = yield self.auth.get_user_by_req(request)
yield self.device_handler.delete_devices(
requester.user.to_string(),
body['devices'],
Expand Down Expand Up @@ -129,16 +130,13 @@ def on_DELETE(self, request, device_id):
else:
raise

result, params, _ = yield self.auth_handler.check_auth([
[constants.LoginType.PASSWORD],
], body, self.hs.get_ip_from_request(request))

# check that the UI auth matched the access token
user_id = result[constants.LoginType.PASSWORD]
if user_id != requester.user.to_string():
raise errors.AuthError(403, "Invalid auth")
yield self.auth_handler.validate_user_via_ui_auth(
requester, body, self.hs.get_ip_from_request(request),
)

yield self.device_handler.delete_device(user_id, device_id)
yield self.device_handler.delete_device(
requester.user.to_string(), device_id,
)
defer.returnValue((200, {}))

@defer.inlineCallbacks
Expand Down