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

Account validity: allow defining HTML templates to serve the user on account renewal attempt #5807

Merged
merged 5 commits into from
Aug 1, 2019
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
1 change: 1 addition & 0 deletions changelog.d/5807.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow defining HTML templates to serve the user on account renewal attempt when using the account validity feature.
10 changes: 10 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,16 @@ uploads_path: "DATADIR/uploads"
# period: 6w
# renew_at: 1w
# renew_email_subject: "Renew your %(app)s account"
# # Directory in which Synapse will try to find the HTML files to serve to the
# # user when trying to renew an account. Optional, defaults to
# # synapse/res/templates.
# template_dir: "res/templates"
# # HTML to be displayed to the user after they successfully renewed their
# # account. Optional.
# account_renewed_html_path: "account_renewed.html"
# # HTML to be displayed when the user tries to renew an account with an invalid
# # renewal token. Optional.
# invalid_token_html_path: "invalid_token.html"

# Time that a user's session remains valid for, after they log in.
#
Expand Down
45 changes: 43 additions & 2 deletions synapse/config/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
from distutils.util import strtobool

import pkg_resources

from synapse.config._base import Config, ConfigError
from synapse.types import RoomAlias
from synapse.util.stringutils import random_string_with_symbols
Expand All @@ -41,8 +44,36 @@ def __init__(self, config, synapse_config):

self.startup_job_max_delta = self.period * 10.0 / 100.0

if self.renew_by_email_enabled and "public_baseurl" not in synapse_config:
raise ConfigError("Can't send renewal emails without 'public_baseurl'")
if self.renew_by_email_enabled:
if "public_baseurl" not in synapse_config:
raise ConfigError("Can't send renewal emails without 'public_baseurl'")

template_dir = config.get("template_dir")

if not template_dir:
template_dir = pkg_resources.resource_filename("synapse", "res/templates")

if "account_renewed_html_path" in config:
file_path = os.path.join(template_dir, config["account_renewed_html_path"])

self.account_renewed_html_content = self.read_file(
file_path, "account_validity.account_renewed_html_path"
)
else:
self.account_renewed_html_content = (
"<html><body>Your account has been successfully renewed.</body><html>"
)

if "invalid_token_html_path" in config:
file_path = os.path.join(template_dir, config["invalid_token_html_path"])

self.invalid_token_html_content = self.read_file(
file_path, "account_validity.invalid_token_html_path"
)
else:
self.invalid_token_html_content = (
"<html><body>Invalid renewal token.</body><html>"
)


class RegistrationConfig(Config):
Expand Down Expand Up @@ -145,6 +176,16 @@ def generate_config_section(self, generate_secrets=False, **kwargs):
# period: 6w
# renew_at: 1w
# renew_email_subject: "Renew your %%(app)s account"
# # Directory in which Synapse will try to find the HTML files to serve to the
# # user when trying to renew an account. Optional, defaults to
# # synapse/res/templates.
# template_dir: "res/templates"
# # HTML to be displayed to the user after they successfully renewed their
# # account. Optional.
# account_renewed_html_path: "account_renewed.html"
# # HTML to be displayed when the user tries to renew an account with an invalid
# # renewal token. Optional.
# invalid_token_html_path: "invalid_token.html"

# Time that a user's session remains valid for, after they log in.
#
Expand Down
10 changes: 9 additions & 1 deletion synapse/handlers/account_validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,19 @@ def renew_account(self, renewal_token):

Args:
renewal_token (str): Token sent with the renewal request.
Returns:
bool: Whether the provided token is valid.
"""
user_id = yield self.store.get_user_from_renewal_token(renewal_token)
try:
user_id = yield self.store.get_user_from_renewal_token(renewal_token)
except StoreError:
defer.returnValue(False)

logger.debug("Renewing an account for user %s", user_id)
yield self.renew_account_for_user(user_id)

defer.returnValue(True)

@defer.inlineCallbacks
def renew_account_for_user(self, user_id, expiration_ts=None, email_sent=False):
"""Renews the account attached to a given user by pushing back the
Expand Down
1 change: 1 addition & 0 deletions synapse/res/templates/account_renewed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><body>Your account has been successfully renewed.</body><html>
1 change: 1 addition & 0 deletions synapse/res/templates/invalid_token.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html><body>Invalid renewal token.</body><html>
25 changes: 17 additions & 8 deletions synapse/rest/client/v2_alpha/account_validity.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,32 @@ def __init__(self, hs):
self.hs = hs
self.account_activity_handler = hs.get_account_validity_handler()
self.auth = hs.get_auth()
self.success_html = hs.config.account_validity.account_renewed_html_content
self.failure_html = hs.config.account_validity.invalid_token_html_content

@defer.inlineCallbacks
def on_GET(self, request):
if b"token" not in request.args:
raise SynapseError(400, "Missing renewal token")
renewal_token = request.args[b"token"][0]

yield self.account_activity_handler.renew_account(renewal_token.decode("utf8"))
token_valid = yield self.account_activity_handler.renew_account(
renewal_token.decode("utf8")
)

if token_valid:
status_code = 200
response = self.success_html
else:
status_code = 404
response = self.failure_html

request.setResponseCode(200)
request.setResponseCode(status_code)
request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
request.setHeader(
b"Content-Length", b"%d" % (len(AccountValidityRenewServlet.SUCCESS_HTML),)
)
request.write(AccountValidityRenewServlet.SUCCESS_HTML)
request.setHeader(b"Content-Length", b"%d" % (len(response),))
request.write(response.encode("utf8"))
finish_request(request)
return None
defer.returnValue(None)


class AccountValiditySendMailServlet(RestServlet):
Expand Down Expand Up @@ -87,7 +96,7 @@ def on_POST(self, request):
user_id = requester.user.to_string()
yield self.account_activity_handler.send_renewal_email_to_user(user_id)

return (200, {})
defer.returnValue((200, {}))


def register_servlets(hs, http_server):
Expand Down
37 changes: 37 additions & 0 deletions tests/rest/client/v2_alpha/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ def make_homeserver(self, reactor, clock):
"renew_at": 172800000, # Time in ms for 2 days
"renew_by_email_enabled": True,
"renew_email_subject": "Renew your account",
"account_renewed_html_path": "account_renewed.html",
"invalid_token_html_path": "invalid_token.html",
}

# Email config.
Expand Down Expand Up @@ -373,6 +375,19 @@ def test_renewal_email(self):
self.render(request)
self.assertEquals(channel.result["code"], b"200", channel.result)

# Check that we're getting HTML back.
content_type = None
for header in channel.result.get("headers", []):
if header[0] == b"Content-Type":
content_type = header[1]
self.assertEqual(content_type, b"text/html; charset=utf-8", channel.result)

# Check that the HTML we're getting is the one we expect on a successful renewal.
expected_html = self.hs.config.account_validity.account_renewed_html_content
self.assertEqual(
channel.result["body"], expected_html.encode("utf8"), channel.result
)

# Move 3 days forward. If the renewal failed, every authed request with
# our access token should be denied from now, otherwise they should
# succeed.
Expand All @@ -381,6 +396,28 @@ def test_renewal_email(self):
self.render(request)
self.assertEquals(channel.result["code"], b"200", channel.result)

def test_renewal_invalid_token(self):
# Hit the renewal endpoint with an invalid token and check that it behaves as
# expected, i.e. that it responds with 404 Not Found and the correct HTML.
url = "/_matrix/client/unstable/account_validity/renew?token=123"
request, channel = self.make_request(b"GET", url)
self.render(request)
self.assertEquals(channel.result["code"], b"404", channel.result)

# Check that we're getting HTML back.
content_type = None
for header in channel.result.get("headers", []):
if header[0] == b"Content-Type":
content_type = header[1]
self.assertEqual(content_type, b"text/html; charset=utf-8", channel.result)

# Check that the HTML we're getting is the one we expect when using an
# invalid/unknown token.
expected_html = self.hs.config.account_validity.invalid_token_html_content
self.assertEqual(
channel.result["body"], expected_html.encode("utf8"), channel.result
)

def test_manual_email_send(self):
self.email_attempts = []

Expand Down