|
48 | 48 |
|
49 | 49 | logger = logging.getLogger(__name__) |
50 | 50 |
|
51 | | -SESSION_COOKIE_NAME = b"oidc_session" |
| 51 | +# we want the cookie to be returned to us even when the request is the POSTed |
| 52 | +# result of a form on another domain, as is used with `response_mode=form_post`. |
| 53 | +# |
| 54 | +# Modern browsers will not do so unless we set SameSite=None; however *older* |
| 55 | +# browsers (including all versions of Safari on iOS 12?) don't support |
| 56 | +# SameSite=None, and interpret it as SameSite=Strict: |
| 57 | +# https://bugs.webkit.org/show_bug.cgi?id=198181 |
| 58 | +# |
| 59 | +# As a rather painful workaround, we set *two* cookies, one with SameSite=None |
| 60 | +# and one with no SameSite, in the hope that at least one of them will get |
| 61 | +# back to us. |
| 62 | +# |
| 63 | +# Secure is necessary for SameSite=None (and, empirically, also breaks things |
| 64 | +# on iOS 12.) |
| 65 | +# |
| 66 | +# Here we have the names of the cookies, and the options we use to set them. |
| 67 | +_SESSION_COOKIES = [ |
| 68 | + (b"oidc_session", b"Path=/_synapse/client/oidc; HttpOnly; Secure; SameSite=None"), |
| 69 | + (b"oidc_session_no_samesite", b"Path=/_synapse/client/oidc; HttpOnly"), |
| 70 | +] |
52 | 71 |
|
53 | 72 | #: A token exchanged from the token endpoint, as per RFC6749 sec 5.1. and |
54 | 73 | #: OpenID.Core sec 3.1.3.3. |
@@ -149,26 +168,33 @@ async def handle_oidc_callback(self, request: SynapseRequest) -> None: |
149 | 168 | # otherwise, it is presumably a successful response. see: |
150 | 169 | # https://tools.ietf.org/html/rfc6749#section-4.1.2 |
151 | 170 |
|
152 | | - # Fetch the session cookie |
153 | | - session = request.getCookie(SESSION_COOKIE_NAME) # type: Optional[bytes] |
154 | | - if session is None: |
| 171 | + # Fetch the session cookie. See the comments on SESSION_COOKIES for why there |
| 172 | + # are two. |
| 173 | + |
| 174 | + for cookie_name, _ in _SESSION_COOKIES: |
| 175 | + session = request.getCookie(cookie_name) # type: Optional[bytes] |
| 176 | + if session is not None: |
| 177 | + break |
| 178 | + else: |
155 | 179 | logger.info("Received OIDC callback, with no session cookie") |
156 | 180 | self._sso_handler.render_error( |
157 | 181 | request, "missing_session", "No session cookie found" |
158 | 182 | ) |
159 | 183 | return |
160 | 184 |
|
161 | | - # Remove the cookie. There is a good chance that if the callback failed |
| 185 | + # Remove the cookies. There is a good chance that if the callback failed |
162 | 186 | # once, it will fail next time and the code will already be exchanged. |
163 | | - # Removing it early avoids spamming the provider with token requests. |
164 | | - request.addCookie( |
165 | | - SESSION_COOKIE_NAME, |
166 | | - b"", |
167 | | - path="/_synapse/oidc", |
168 | | - expires="Thu, Jan 01 1970 00:00:00 UTC", |
169 | | - httpOnly=True, |
170 | | - sameSite="lax", |
171 | | - ) |
| 187 | + # Removing the cookies early avoids spamming the provider with token requests. |
| 188 | + # |
| 189 | + # we have to build the header by hand rather than calling request.addCookie |
| 190 | + # because the latter does not support SameSite=None |
| 191 | + # (https://twistedmatrix.com/trac/ticket/10088) |
| 192 | + |
| 193 | + for cookie_name, options in _SESSION_COOKIES: |
| 194 | + request.cookies.append( |
| 195 | + b"%s=; Expires=Thu, Jan 01 1970 00:00:00 UTC; %s" |
| 196 | + % (cookie_name, options) |
| 197 | + ) |
172 | 198 |
|
173 | 199 | # Check for the state query parameter |
174 | 200 | if b"state" not in request.args: |
@@ -722,14 +748,18 @@ async def handle_redirect_request( |
722 | 748 | ui_auth_session_id=ui_auth_session_id, |
723 | 749 | ), |
724 | 750 | ) |
725 | | - request.addCookie( |
726 | | - SESSION_COOKIE_NAME, |
727 | | - cookie, |
728 | | - path="/_synapse/client/oidc", |
729 | | - max_age="3600", |
730 | | - httpOnly=True, |
731 | | - sameSite="lax", |
732 | | - ) |
| 751 | + |
| 752 | + # Set the cookies. See the comments on _SESSION_COOKIES for why there are two. |
| 753 | + # |
| 754 | + # we have to build the header by hand rather than calling request.addCookie |
| 755 | + # because the latter does not support SameSite=None |
| 756 | + # (https://twistedmatrix.com/trac/ticket/10088) |
| 757 | + |
| 758 | + for cookie_name, options in _SESSION_COOKIES: |
| 759 | + request.cookies.append( |
| 760 | + b"%s=%s; Max-Age=3600; %s" |
| 761 | + % (cookie_name, cookie.encode("utf-8"), options) |
| 762 | + ) |
733 | 763 |
|
734 | 764 | metadata = await self.load_metadata() |
735 | 765 | authorization_endpoint = metadata.get("authorization_endpoint") |
|
0 commit comments