Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

matrix-org / synapse / 4532

23 Sep 2019 - 19:39 coverage decreased (-49.7%) to 17.596%
4532

Pull #6079

buildkite

Richard van der Hoff
update changelog
Pull Request #6079: Add submit_url response parameter to msisdn /requestToken

359 of 12986 branches covered (2.76%)

Branch coverage included in aggregate %.

0 of 7 new or added lines in 1 file covered. (0.0%)

18869 existing lines in 281 files now uncovered.

8809 of 39116 relevant lines covered (22.52%)

0.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

19.05
/synapse/handlers/account_validity.py
1
# -*- coding: utf-8 -*-
2
# Copyright 2019 New Vector Ltd
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15

16
import email.mime.multipart
1×
17
import email.utils
1×
18
import logging
1×
19
from email.mime.multipart import MIMEMultipart
1×
20
from email.mime.text import MIMEText
1×
21

22
from twisted.internet import defer
1×
23

24
from synapse.api.errors import StoreError
1×
25
from synapse.logging.context import make_deferred_yieldable
1×
26
from synapse.metrics.background_process_metrics import run_as_background_process
1×
27
from synapse.types import UserID
1×
28
from synapse.util import stringutils
1×
29

30
try:
1×
31
    from synapse.push.mailer import load_jinja2_templates
1×
32
except ImportError:
!
33
    load_jinja2_templates = None
!
34

35
logger = logging.getLogger(__name__)
1×
36

37

38
class AccountValidityHandler(object):
1×
39
    def __init__(self, hs):
1×
UNCOV
40
        self.hs = hs
!
UNCOV
41
        self.config = hs.config
!
UNCOV
42
        self.store = self.hs.get_datastore()
!
UNCOV
43
        self.sendmail = self.hs.get_sendmail()
!
UNCOV
44
        self.clock = self.hs.get_clock()
!
45

UNCOV
46
        self._account_validity = self.hs.config.account_validity
!
47

UNCOV
48
        if self._account_validity.renew_by_email_enabled and load_jinja2_templates:
Branches [[0, 39], [0, 50]] missed. !
49
            # Don't do email-specific configuration if renewal by email is disabled.
50
            try:
!
51
                app_name = self.hs.config.email_app_name
!
52

53
                self._subject = self._account_validity.renew_email_subject % {
!
54
                    "app": app_name
55
                }
56

57
                self._from_string = self.hs.config.email_notif_from % {"app": app_name}
!
58
            except Exception:
!
59
                # If substitution failed, fall back to the bare strings.
60
                self._subject = self._account_validity.renew_email_subject
!
61
                self._from_string = self.hs.config.email_notif_from
!
62

63
            self._raw_from = email.utils.parseaddr(self._from_string)[1]
!
64

65
            self._template_html, self._template_text = load_jinja2_templates(
!
66
                self.config.email_template_dir,
67
                [
68
                    self.config.email_expiry_template_html,
69
                    self.config.email_expiry_template_text,
70
                ],
71
                apply_format_ts_filter=True,
72
                apply_mxc_to_http_filter=True,
73
                public_baseurl=self.config.public_baseurl,
74
            )
75

76
            # Check the renewal emails to send and send them every 30min.
77
            def send_emails():
!
78
                # run as a background process to make sure that the database transactions
79
                # have a logcontext to report to
80
                return run_as_background_process(
!
81
                    "send_renewals", self.send_renewal_emails
82
                )
83

84
            self.clock.looping_call(send_emails, 30 * 60 * 1000)
!
85

86
    @defer.inlineCallbacks
1×
87
    def send_renewal_emails(self):
88
        """Gets the list of users whose account is expiring in the amount of time
89
        configured in the ``renew_at`` parameter from the ``account_validity``
90
        configuration, and sends renewal emails to all of these users as long as they
91
        have an email 3PID attached to their account.
92
        """
93
        expiring_users = yield self.store.get_users_expiring_soon()
!
94

95
        if expiring_users:
Branches [[0, 86], [0, 96]] missed. !
96
            for user in expiring_users:
Branches [[0, 86], [0, 97]] missed. !
97
                yield self._send_renewal_email(
!
98
                    user_id=user["user_id"], expiration_ts=user["expiration_ts_ms"]
99
                )
100

101
    @defer.inlineCallbacks
1×
102
    def send_renewal_email_to_user(self, user_id):
103
        expiration_ts = yield self.store.get_expiration_ts_for_user(user_id)
!
104
        yield self._send_renewal_email(user_id, expiration_ts)
!
105

106
    @defer.inlineCallbacks
1×
107
    def _send_renewal_email(self, user_id, expiration_ts):
108
        """Sends out a renewal email to every email address attached to the given user
109
        with a unique link allowing them to renew their account.
110

111
        Args:
112
            user_id (str): ID of the user to send email(s) to.
113
            expiration_ts (int): Timestamp in milliseconds for the expiration date of
114
                this user's account (used in the email templates).
115
        """
116
        addresses = yield self._get_email_addresses_for_user(user_id)
!
117

118
        # Stop right here if the user doesn't have at least one email address.
119
        # In this case, they will have to ask their server admin to renew their
120
        # account manually.
121
        # We don't need to do a specific check to make sure the account isn't
122
        # deactivated, as a deactivated account isn't supposed to have any
123
        # email address attached to it.
124
        if not addresses:
Branches [[0, 125], [0, 127]] missed. !
125
            return
!
126

127
        try:
!
128
            user_display_name = yield self.store.get_profile_displayname(
!
129
                UserID.from_string(user_id).localpart
130
            )
131
            if user_display_name is None:
Branches [[0, 132], [0, 136]] missed. !
132
                user_display_name = user_id
!
133
        except StoreError:
!
134
            user_display_name = user_id
!
135

136
        renewal_token = yield self._get_renewal_token(user_id)
!
137
        url = "%s_matrix/client/unstable/account_validity/renew?token=%s" % (
!
138
            self.hs.config.public_baseurl,
139
            renewal_token,
140
        )
141

142
        template_vars = {
!
143
            "display_name": user_display_name,
144
            "expiration_ts": expiration_ts,
145
            "url": url,
146
        }
147

148
        html_text = self._template_html.render(**template_vars)
!
149
        html_part = MIMEText(html_text, "html", "utf8")
!
150

151
        plain_text = self._template_text.render(**template_vars)
!
152
        text_part = MIMEText(plain_text, "plain", "utf8")
!
153

154
        for address in addresses:
Branches [[0, 155], [0, 183]] missed. !
155
            raw_to = email.utils.parseaddr(address)[1]
!
156

157
            multipart_msg = MIMEMultipart("alternative")
!
158
            multipart_msg["Subject"] = self._subject
!
159
            multipart_msg["From"] = self._from_string
!
160
            multipart_msg["To"] = address
!
161
            multipart_msg["Date"] = email.utils.formatdate()
!
162
            multipart_msg["Message-ID"] = email.utils.make_msgid()
!
163
            multipart_msg.attach(text_part)
!
164
            multipart_msg.attach(html_part)
!
165

166
            logger.info("Sending renewal email to %s", address)
!
167

168
            yield make_deferred_yieldable(
!
169
                self.sendmail(
170
                    self.hs.config.email_smtp_host,
171
                    self._raw_from,
172
                    raw_to,
173
                    multipart_msg.as_string().encode("utf8"),
174
                    reactor=self.hs.get_reactor(),
175
                    port=self.hs.config.email_smtp_port,
176
                    requireAuthentication=self.hs.config.email_smtp_user is not None,
177
                    username=self.hs.config.email_smtp_user,
178
                    password=self.hs.config.email_smtp_pass,
179
                    requireTransportSecurity=self.hs.config.require_transport_security,
180
                )
181
            )
182

183
        yield self.store.set_renewal_mail_status(user_id=user_id, email_sent=True)
!
184

185
    @defer.inlineCallbacks
1×
186
    def _get_email_addresses_for_user(self, user_id):
187
        """Retrieve the list of email addresses attached to a user's account.
188

189
        Args:
190
            user_id (str): ID of the user to lookup email addresses for.
191

192
        Returns:
193
            defer.Deferred[list[str]]: Email addresses for this account.
194
        """
195
        threepids = yield self.store.user_get_threepids(user_id)
!
196

197
        addresses = []
!
198
        for threepid in threepids:
Branches [[0, 199], [0, 202]] missed. !
199
            if threepid["medium"] == "email":
Branches [[0, 198], [0, 200]] missed. !
200
                addresses.append(threepid["address"])
!
201

202
        return addresses
!
203

204
    @defer.inlineCallbacks
1×
205
    def _get_renewal_token(self, user_id):
206
        """Generates a 32-byte long random string that will be inserted into the
207
        user's renewal email's unique link, then saves it into the database.
208

209
        Args:
210
            user_id (str): ID of the user to generate a string for.
211

212
        Returns:
213
            defer.Deferred[str]: The generated string.
214

215
        Raises:
216
            StoreError(500): Couldn't generate a unique string after 5 attempts.
217
        """
218
        attempts = 0
!
219
        while attempts < 5:
Branches [[0, 220], [0, 226]] missed. !
220
            try:
!
221
                renewal_token = stringutils.random_string(32)
!
222
                yield self.store.set_renewal_token_for_user(user_id, renewal_token)
!
223
                return renewal_token
!
224
            except StoreError:
!
225
                attempts += 1
!
226
        raise StoreError(500, "Couldn't generate a unique string as refresh string.")
!
227

228
    @defer.inlineCallbacks
1×
229
    def renew_account(self, renewal_token):
230
        """Renews the account attached to a given renewal token by pushing back the
231
        expiration date by the current validity period in the server's configuration.
232

233
        Args:
234
            renewal_token (str): Token sent with the renewal request.
235
        Returns:
236
            bool: Whether the provided token is valid.
237
        """
238
        try:
!
239
            user_id = yield self.store.get_user_from_renewal_token(renewal_token)
!
240
        except StoreError:
!
241
            defer.returnValue(False)
!
242

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

246
        defer.returnValue(True)
!
247

248
    @defer.inlineCallbacks
1×
249
    def renew_account_for_user(self, user_id, expiration_ts=None, email_sent=False):
1×
250
        """Renews the account attached to a given user by pushing back the
251
        expiration date by the current validity period in the server's
252
        configuration.
253

254
        Args:
255
            renewal_token (str): Token sent with the renewal request.
256
            expiration_ts (int): New expiration date. Defaults to now + validity period.
257
            email_sent (bool): Whether an email has been sent for this validity period.
258
                Defaults to False.
259

260
        Returns:
261
            defer.Deferred[int]: New expiration date for this account, as a timestamp
262
                in milliseconds since epoch.
263
        """
264
        if expiration_ts is None:
Branches [[0, 265], [0, 267]] missed. !
265
            expiration_ts = self.clock.time_msec() + self._account_validity.period
!
266

267
        yield self.store.set_account_validity_for_user(
!
268
            user_id=user_id, expiration_ts=expiration_ts, email_sent=email_sent
269
        )
270

271
        return expiration_ts
!
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC