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

Commit

Permalink
Refactor the user-interactive auth handling (#6105)
Browse files Browse the repository at this point in the history
Pull the checkers out to their own classes, rather than having them lost in a
massive 1000-line class which does everything.

This is also preparation for some more intelligent advertising of flows, as per #6100
  • Loading branch information
richvdh authored Sep 25, 2019
1 parent 8004d6c commit 2cd9881
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 141 deletions.
1 change: 1 addition & 0 deletions changelog.d/6105.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Refactor the user-interactive auth handling.
141 changes: 10 additions & 131 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@
import attr
import bcrypt
import pymacaroons
from canonicaljson import json

from twisted.internet import defer
from twisted.web.client import PartialDownloadError

import synapse.util.stringutils as stringutils
from synapse.api.constants import LoginType
Expand All @@ -38,7 +36,8 @@
UserDeactivatedError,
)
from synapse.api.ratelimiting import Ratelimiter
from synapse.config.emailconfig import ThreepidBehaviour
from synapse.handlers.ui_auth import INTERACTIVE_AUTH_CHECKERS
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
from synapse.logging.context import defer_to_thread
from synapse.module_api import ModuleApi
from synapse.types import UserID
Expand All @@ -58,13 +57,12 @@ def __init__(self, hs):
hs (synapse.server.HomeServer):
"""
super(AuthHandler, self).__init__(hs)
self.checkers = {
LoginType.RECAPTCHA: self._check_recaptcha,
LoginType.EMAIL_IDENTITY: self._check_email_identity,
LoginType.MSISDN: self._check_msisdn,
LoginType.DUMMY: self._check_dummy_auth,
LoginType.TERMS: self._check_terms_auth,
}

self.checkers = {} # type: dict[str, UserInteractiveAuthChecker]
for auth_checker_class in INTERACTIVE_AUTH_CHECKERS:
inst = auth_checker_class(hs)
self.checkers[inst.AUTH_TYPE] = inst

self.bcrypt_rounds = hs.config.bcrypt_rounds

# This is not a cache per se, but a store of all current sessions that
Expand Down Expand Up @@ -292,7 +290,7 @@ def add_oob_auth(self, stagetype, authdict, clientip):
sess["creds"] = {}
creds = sess["creds"]

result = yield self.checkers[stagetype](authdict, clientip)
result = yield self.checkers[stagetype].check_auth(authdict, clientip)
if result:
creds[stagetype] = result
self._save_session(sess)
Expand Down Expand Up @@ -363,7 +361,7 @@ def _check_auth_dict(self, authdict, clientip):
login_type = authdict["type"]
checker = self.checkers.get(login_type)
if checker is not None:
res = yield checker(authdict, clientip=clientip)
res = yield checker.check_auth(authdict, clientip=clientip)
return res

# build a v1-login-style dict out of the authdict and fall back to the
Expand All @@ -376,125 +374,6 @@ def _check_auth_dict(self, authdict, clientip):
(canonical_id, callback) = yield self.validate_login(user_id, authdict)
return canonical_id

@defer.inlineCallbacks
def _check_recaptcha(self, authdict, clientip, **kwargs):
try:
user_response = authdict["response"]
except KeyError:
# Client tried to provide captcha but didn't give the parameter:
# bad request.
raise LoginError(
400, "Captcha response is required", errcode=Codes.CAPTCHA_NEEDED
)

logger.info(
"Submitting recaptcha response %s with remoteip %s", user_response, clientip
)

# TODO: get this from the homeserver rather than creating a new one for
# each request
try:
client = self.hs.get_simple_http_client()
resp_body = yield client.post_urlencoded_get_json(
self.hs.config.recaptcha_siteverify_api,
args={
"secret": self.hs.config.recaptcha_private_key,
"response": user_response,
"remoteip": clientip,
},
)
except PartialDownloadError as pde:
# Twisted is silly
data = pde.response
resp_body = json.loads(data)

if "success" in resp_body:
# Note that we do NOT check the hostname here: we explicitly
# intend the CAPTCHA to be presented by whatever client the
# user is using, we just care that they have completed a CAPTCHA.
logger.info(
"%s reCAPTCHA from hostname %s",
"Successful" if resp_body["success"] else "Failed",
resp_body.get("hostname"),
)
if resp_body["success"]:
return True
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)

def _check_email_identity(self, authdict, **kwargs):
return self._check_threepid("email", authdict, **kwargs)

def _check_msisdn(self, authdict, **kwargs):
return self._check_threepid("msisdn", authdict)

def _check_dummy_auth(self, authdict, **kwargs):
return defer.succeed(True)

def _check_terms_auth(self, authdict, **kwargs):
return defer.succeed(True)

@defer.inlineCallbacks
def _check_threepid(self, medium, authdict, **kwargs):
if "threepid_creds" not in authdict:
raise LoginError(400, "Missing threepid_creds", Codes.MISSING_PARAM)

threepid_creds = authdict["threepid_creds"]

identity_handler = self.hs.get_handlers().identity_handler

logger.info("Getting validated threepid. threepidcreds: %r", (threepid_creds,))
if self.hs.config.threepid_behaviour_email == ThreepidBehaviour.REMOTE:
if medium == "email":
threepid = yield identity_handler.threepid_from_creds(
self.hs.config.account_threepid_delegate_email, threepid_creds
)
elif medium == "msisdn":
threepid = yield identity_handler.threepid_from_creds(
self.hs.config.account_threepid_delegate_msisdn, threepid_creds
)
else:
raise SynapseError(400, "Unrecognized threepid medium: %s" % (medium,))
elif self.hs.config.threepid_behaviour_email == ThreepidBehaviour.LOCAL:
row = yield self.store.get_threepid_validation_session(
medium,
threepid_creds["client_secret"],
sid=threepid_creds["sid"],
validated=True,
)

threepid = (
{
"medium": row["medium"],
"address": row["address"],
"validated_at": row["validated_at"],
}
if row
else None
)

if row:
# Valid threepid returned, delete from the db
yield self.store.delete_threepid_session(threepid_creds["sid"])
else:
raise SynapseError(
400, "Password resets are not enabled on this homeserver"
)

if not threepid:
raise LoginError(401, "", errcode=Codes.UNAUTHORIZED)

if threepid["medium"] != medium:
raise LoginError(
401,
"Expecting threepid of type '%s', got '%s'"
% (medium, threepid["medium"]),
errcode=Codes.UNAUTHORIZED,
)

threepid["threepid_creds"] = authdict["threepid_creds"]

return threepid

def _get_params_recaptcha(self):
return {"public_key": self.hs.config.recaptcha_public_key}

Expand Down
22 changes: 22 additions & 0 deletions synapse/handlers/ui_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2019 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This module implements user-interactive auth verification.
TODO: move more stuff out of AuthHandler in here.
"""

from synapse.handlers.ui_auth.checkers import INTERACTIVE_AUTH_CHECKERS # noqa: F401
Loading

0 comments on commit 2cd9881

Please sign in to comment.