summary refs log tree commit diff
path: root/synapse
diff options
context:
space:
mode:
Diffstat (limited to 'synapse')
-rw-r--r--synapse/api/auth.py2
-rw-r--r--synapse/handlers/account_validity.py33
-rw-r--r--synapse/rest/client/v1/admin.py39
-rw-r--r--synapse/rest/client/v2_alpha/account_validity.py31
-rw-r--r--synapse/storage/registration.py29
5 files changed, 116 insertions, 18 deletions
diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 4482962510..960e66dbdc 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -232,7 +232,7 @@ class Auth(object):
             if self._account_validity.enabled:
                 user_id = user.to_string()
                 expiration_ts = yield self.store.get_expiration_ts_for_user(user_id)
-                if expiration_ts and self.clock.time_msec() >= expiration_ts:
+                if expiration_ts is not None and self.clock.time_msec() >= expiration_ts:
                     raise AuthError(
                         403,
                         "User account has expired",
diff --git a/synapse/handlers/account_validity.py b/synapse/handlers/account_validity.py
index e82049e42d..261446517d 100644
--- a/synapse/handlers/account_validity.py
+++ b/synapse/handlers/account_validity.py
@@ -91,6 +91,11 @@ class AccountValidityHandler(object):
                 )
 
     @defer.inlineCallbacks
+    def send_renewal_email_to_user(self, user_id):
+        expiration_ts = yield self.store.get_expiration_ts_for_user(user_id)
+        yield self._send_renewal_email(user_id, expiration_ts)
+
+    @defer.inlineCallbacks
     def _send_renewal_email(self, user_id, expiration_ts):
         """Sends out a renewal email to every email address attached to the given user
         with a unique link allowing them to renew their account.
@@ -217,12 +222,32 @@ class AccountValidityHandler(object):
             renewal_token (str): Token sent with the renewal request.
         """
         user_id = yield self.store.get_user_from_renewal_token(renewal_token)
-
         logger.debug("Renewing an account for user %s", user_id)
+        yield self.renew_account_for_user(user_id)
 
-        new_expiration_date = self.clock.time_msec() + self._account_validity.period
+    @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
+        expiration date by the current validity period in the server's
+        configuration.
 
-        yield self.store.renew_account_for_user(
+        Args:
+            renewal_token (str): Token sent with the renewal request.
+            expiration_ts (int): New expiration date. Defaults to now + validity period.
+            email_sent (bool): Whether an email has been sent for this validity period.
+                Defaults to False.
+
+        Returns:
+            defer.Deferred[int]: New expiration date for this account, as a timestamp
+                in milliseconds since epoch.
+        """
+        if expiration_ts is None:
+            expiration_ts = self.clock.time_msec() + self._account_validity.period
+
+        yield self.store.set_account_validity_for_user(
             user_id=user_id,
-            new_expiration_ts=new_expiration_date,
+            expiration_ts=expiration_ts,
+            email_sent=email_sent,
         )
+
+        defer.returnValue(expiration_ts)
diff --git a/synapse/rest/client/v1/admin.py b/synapse/rest/client/v1/admin.py
index e788769639..d27472c538 100644
--- a/synapse/rest/client/v1/admin.py
+++ b/synapse/rest/client/v1/admin.py
@@ -786,6 +786,44 @@ class SearchUsersRestServlet(ClientV1RestServlet):
         defer.returnValue((200, ret))
 
 
+class AccountValidityRenewServlet(ClientV1RestServlet):
+    PATTERNS = client_path_patterns("/admin/account_validity/validity$")
+
+    def __init__(self, hs):
+        """
+        Args:
+            hs (synapse.server.HomeServer): server
+        """
+        super(AccountValidityRenewServlet, self).__init__(hs)
+
+        self.hs = hs
+        self.account_activity_handler = hs.get_account_validity_handler()
+        self.auth = hs.get_auth()
+
+    @defer.inlineCallbacks
+    def on_POST(self, request):
+        requester = yield self.auth.get_user_by_req(request)
+        is_admin = yield self.auth.is_server_admin(requester.user)
+
+        if not is_admin:
+            raise AuthError(403, "You are not a server admin")
+
+        body = parse_json_object_from_request(request)
+
+        if "user_id" not in body:
+            raise SynapseError(400, "Missing property 'user_id' in the request body")
+
+        expiration_ts = yield self.account_activity_handler.renew_account_for_user(
+            body["user_id"], body.get("expiration_ts"),
+            not body.get("enable_renewal_emails", True),
+        )
+
+        res = {
+            "expiration_ts": expiration_ts,
+        }
+        defer.returnValue((200, res))
+
+
 def register_servlets(hs, http_server):
     WhoisRestServlet(hs).register(http_server)
     PurgeMediaCacheRestServlet(hs).register(http_server)
@@ -801,3 +839,4 @@ def register_servlets(hs, http_server):
     ListMediaInRoom(hs).register(http_server)
     UserRegisterServlet(hs).register(http_server)
     VersionServlet(hs).register(http_server)
+    AccountValidityRenewServlet(hs).register(http_server)
diff --git a/synapse/rest/client/v2_alpha/account_validity.py b/synapse/rest/client/v2_alpha/account_validity.py
index 1ff6a6b638..fc8dbeb617 100644
--- a/synapse/rest/client/v2_alpha/account_validity.py
+++ b/synapse/rest/client/v2_alpha/account_validity.py
@@ -17,7 +17,7 @@ import logging
 
 from twisted.internet import defer
 
-from synapse.api.errors import SynapseError
+from synapse.api.errors import AuthError, SynapseError
 from synapse.http.server import finish_request
 from synapse.http.servlet import RestServlet
 
@@ -39,6 +39,7 @@ class AccountValidityRenewServlet(RestServlet):
 
         self.hs = hs
         self.account_activity_handler = hs.get_account_validity_handler()
+        self.auth = hs.get_auth()
 
     @defer.inlineCallbacks
     def on_GET(self, request):
@@ -58,5 +59,33 @@ class AccountValidityRenewServlet(RestServlet):
         defer.returnValue(None)
 
 
+class AccountValiditySendMailServlet(RestServlet):
+    PATTERNS = client_v2_patterns("/account_validity/send_mail$")
+
+    def __init__(self, hs):
+        """
+        Args:
+            hs (synapse.server.HomeServer): server
+        """
+        super(AccountValiditySendMailServlet, self).__init__()
+
+        self.hs = hs
+        self.account_activity_handler = hs.get_account_validity_handler()
+        self.auth = hs.get_auth()
+        self.account_validity = self.hs.config.account_validity
+
+    @defer.inlineCallbacks
+    def on_POST(self, request):
+        if not self.account_validity.renew_by_email_enabled:
+            raise AuthError(403, "Account renewal via email is disabled on this server.")
+
+        requester = yield self.auth.get_user_by_req(request)
+        user_id = requester.user.to_string()
+        yield self.account_activity_handler.send_renewal_email_to_user(user_id)
+
+        defer.returnValue((200, {}))
+
+
 def register_servlets(hs, http_server):
     AccountValidityRenewServlet(hs).register(http_server)
+    AccountValiditySendMailServlet(hs).register(http_server)
diff --git a/synapse/storage/registration.py b/synapse/storage/registration.py
index a78850259f..dfdb4e7e34 100644
--- a/synapse/storage/registration.py
+++ b/synapse/storage/registration.py
@@ -108,25 +108,30 @@ class RegistrationWorkerStore(SQLBaseStore):
         defer.returnValue(res)
 
     @defer.inlineCallbacks
-    def renew_account_for_user(self, user_id, new_expiration_ts):
-        """Updates the account validity table with a new timestamp for a given
-        user, removes the existing renewal token from this user, and unsets the
-        flag indicating that an email has been sent for renewing this account.
+    def set_account_validity_for_user(self, user_id, expiration_ts, email_sent,
+                                      renewal_token=None):
+        """Updates the account validity properties of the given account, with the
+        given values.
 
         Args:
-            user_id (str): ID of the user whose account validity to renew.
-            new_expiration_ts: New expiration date, as a timestamp in milliseconds
+            user_id (str): ID of the account to update properties for.
+            expiration_ts (int): New expiration date, as a timestamp in milliseconds
                 since epoch.
+            email_sent (bool): True means a renewal email has been sent for this
+                account and there's no need to send another one for the current validity
+                period.
+            renewal_token (str): Renewal token the user can use to extend the validity
+                of their account. Defaults to no token.
         """
-        def renew_account_for_user_txn(txn):
+        def set_account_validity_for_user_txn(txn):
             self._simple_update_txn(
                 txn=txn,
                 table="account_validity",
                 keyvalues={"user_id": user_id},
                 updatevalues={
-                    "expiration_ts_ms": new_expiration_ts,
-                    "email_sent": False,
-                    "renewal_token": None,
+                    "expiration_ts_ms": expiration_ts,
+                    "email_sent": email_sent,
+                    "renewal_token": renewal_token,
                 },
             )
             self._invalidate_cache_and_stream(
@@ -134,8 +139,8 @@ class RegistrationWorkerStore(SQLBaseStore):
             )
 
         yield self.runInteraction(
-            "renew_account_for_user",
-            renew_account_for_user_txn,
+            "set_account_validity_for_user",
+            set_account_validity_for_user_txn,
         )
 
     @defer.inlineCallbacks