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

Add an option allowing users to use their password to reauthenticate even though password authentication is disabled. #12883

Merged
merged 12 commits into from
May 27, 2022
Merged
1 change: 1 addition & 0 deletions changelog.d/12883.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add an option allowing users to use their password to log in or reauthenticate even though password authentication is disabled.
10 changes: 9 additions & 1 deletion synapse/config/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
if password_config is None:
password_config = {}

self.password_enabled = password_config.get("enabled", True)
passwords_enabled = password_config.get("enabled", True)
self.password_enabled = passwords_enabled
# 'only_for_reauth' allows users who have previously set a password to use it,
# even though passwords would otherwise be disabled.
self.password_enabled_for_reauth = passwords_enabled == "only_for_reauth"

if self.password_enabled_for_reauth:
self.password_enabled = False

self.password_localdb_enabled = password_config.get("localdb_enabled", True)
self.password_pepper = password_config.get("pepper", "")

Expand Down
7 changes: 5 additions & 2 deletions synapse/handlers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def __init__(self, hs: "HomeServer"):
self.hs = hs # FIXME better possibility to access registrationHandler later?
self.macaroon_gen = hs.get_macaroon_generator()
self._password_enabled = hs.config.auth.password_enabled
self._password_enabled_for_reauth = hs.config.auth.password_enabled_for_reauth
self._password_localdb_enabled = hs.config.auth.password_localdb_enabled
self._third_party_rules = hs.get_third_party_event_rules()

Expand Down Expand Up @@ -393,7 +394,9 @@ async def _get_available_ui_auth_types(self, user: UserID) -> Iterable[str]:

# if the HS supports password auth, and the user has a non-null password, we
# support password auth
if self._password_localdb_enabled and self._password_enabled:
if self._password_localdb_enabled and (
self._password_enabled or self._password_enabled_for_reauth
):
lookupres = await self._find_user_id_and_pwd_hash(user.to_string())
if lookupres:
_, password_hash = lookupres
Expand Down Expand Up @@ -1133,7 +1136,7 @@ async def validate_login(
# for the auth providers
password = login_submission.get("password")
if login_type == LoginType.PASSWORD:
if not self._password_enabled:
if not self._password_enabled and not self._password_enabled_for_reauth:
raise SynapseError(400, "Password login has been disabled.")
if not isinstance(password, str):
raise SynapseError(400, "Bad parameter: password", Codes.INVALID_PARAM)
Expand Down
29 changes: 28 additions & 1 deletion tests/rest/client/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# 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.
import json
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Tuple, Union

Expand All @@ -22,6 +23,7 @@
import synapse.rest.admin
from synapse.api.constants import LoginType
from synapse.handlers.ui_auth.checkers import UserInteractiveAuthChecker
from synapse.rest import admin
from synapse.rest.client import account, auth, devices, login, logout, register
from synapse.rest.synapse.client import build_synapse_client_resource_tree
from synapse.server import HomeServer
Expand All @@ -33,7 +35,7 @@
from tests.handlers.test_oidc import HAS_OIDC
from tests.rest.client.utils import TEST_OIDC_CONFIG
from tests.server import FakeChannel
from tests.unittest import override_config, skip_unless
from tests.unittest import HomeserverTestCase, override_config, skip_unless


class DummyRecaptchaChecker(UserInteractiveAuthChecker):
Expand Down Expand Up @@ -1079,3 +1081,28 @@ def _txn(txn: LoggingTransaction) -> int:
# and no refresh token
self.assertEqual(_table_length("access_tokens"), 0)
self.assertEqual(_table_length("refresh_tokens"), 0)


class PasswordReauthTestCase(HomeserverTestCase):
servlets = [admin.register_servlets, login.register_servlets]

@override_config({"password_config": {"enabled": "only_for_reauth"}})
def test_password_reauth_succeeds_with_setting(self) -> None:
"""
A user can re-authenticate using a previously-set password if
'only_for_reauth' is set.
"""
user_id = self.register_user("christina", "verysecret")
self.login(user_id, "verysecret")

@override_config({"password_config": {"enabled": False}})
def test_password_reauth_fails_if_disabled(self) -> None:
user_id = self.register_user("christina", "verysecret")

body = {"type": "m.login.password", "user": user_id, "password": "verysecret"}
channel = self.make_request(
"POST",
"/_matrix/client/r0/login",
json.dumps(body).encode("utf8"),
)
self.assertEqual(channel.code, 400, channel.result)