1# Copyright 2020 The Matrix.org Foundation C.I.C.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14import logging
15from typing import TYPE_CHECKING, Tuple
16
17from twisted.web.server import Request
18
19from synapse.api.errors import ThreepidValidationError
20from synapse.config.emailconfig import ThreepidBehaviour
21from synapse.http.server import DirectServeHtmlResource
22from synapse.http.servlet import parse_string
23from synapse.util.stringutils import assert_valid_client_secret
24
25if TYPE_CHECKING:
26    from synapse.server import HomeServer
27
28logger = logging.getLogger(__name__)
29
30
31class PasswordResetSubmitTokenResource(DirectServeHtmlResource):
32    """Handles 3PID validation token submission
33
34    This resource gets mounted under /_synapse/client/password_reset/email/submit_token
35    """
36
37    isLeaf = 1
38
39    def __init__(self, hs: "HomeServer"):
40        """
41        Args:
42            hs: server
43        """
44        super().__init__()
45
46        self.clock = hs.get_clock()
47        self.store = hs.get_datastore()
48
49        self._local_threepid_handling_disabled_due_to_email_config = (
50            hs.config.email.local_threepid_handling_disabled_due_to_email_config
51        )
52        self._confirmation_email_template = (
53            hs.config.email.email_password_reset_template_confirmation_html
54        )
55        self._email_password_reset_template_success_html = (
56            hs.config.email.email_password_reset_template_success_html_content
57        )
58        self._failure_email_template = (
59            hs.config.email.email_password_reset_template_failure_html
60        )
61
62        # This resource should not be mounted if threepid behaviour is not LOCAL
63        assert hs.config.email.threepid_behaviour_email == ThreepidBehaviour.LOCAL
64
65    async def _async_render_GET(self, request: Request) -> Tuple[int, bytes]:
66        sid = parse_string(request, "sid", required=True)
67        token = parse_string(request, "token", required=True)
68        client_secret = parse_string(request, "client_secret", required=True)
69        assert_valid_client_secret(client_secret)
70
71        # Show a confirmation page, just in case someone accidentally clicked this link when
72        # they didn't mean to
73        template_vars = {
74            "sid": sid,
75            "token": token,
76            "client_secret": client_secret,
77        }
78        return (
79            200,
80            self._confirmation_email_template.render(**template_vars).encode("utf-8"),
81        )
82
83    async def _async_render_POST(self, request: Request) -> Tuple[int, bytes]:
84        sid = parse_string(request, "sid", required=True)
85        token = parse_string(request, "token", required=True)
86        client_secret = parse_string(request, "client_secret", required=True)
87
88        # Attempt to validate a 3PID session
89        try:
90            # Mark the session as valid
91            next_link = await self.store.validate_threepid_session(
92                sid, client_secret, token, self.clock.time_msec()
93            )
94
95            # Perform a 302 redirect if next_link is set
96            if next_link:
97                if next_link.startswith("file:///"):
98                    logger.warning(
99                        "Not redirecting to next_link as it is a local file: address"
100                    )
101                else:
102                    next_link_bytes = next_link.encode("utf-8")
103                    request.setHeader("Location", next_link_bytes)
104                    return (
105                        302,
106                        (
107                            b'You are being redirected to <a src="%s">%s</a>.'
108                            % (next_link_bytes, next_link_bytes)
109                        ),
110                    )
111
112            # Otherwise show the success template
113            html_bytes = self._email_password_reset_template_success_html.encode(
114                "utf-8"
115            )
116            status_code = 200
117        except ThreepidValidationError as e:
118            status_code = e.code
119
120            # Show a failure page with a reason
121            template_vars = {"failure_reason": e.msg}
122            html_bytes = self._failure_email_template.render(**template_vars).encode(
123                "utf-8"
124            )
125
126        return status_code, html_bytes
127