-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
add support for handling avatar with SSO login #13917
Changes from 12 commits
6c67c5a
0fd47d1
546ea47
4d74943
d7285f7
f1767d2
670237c
879f8ad
c6de28d
93662ee
586170d
6915096
25d2594
b343a43
07cbe39
f4c1d39
1594a3f
3a18057
06bbd44
93ffe6a
e432b53
669c7b0
f5379ed
fb823f5
b51b4f8
adf0640
225be62
dfd26f0
11ff1b4
1592d43
c15e873
c0861f8
7ff3320
70d3cb5
5271480
e691a91
7101fc0
2d46136
b034ee3
6d370b7
e2cc4bf
966edfd
19a417d
ef9af82
0714752
ecdfe1a
465bade
2f1819c
006800f
4a78052
b1b0a91
b710023
f118e8b
751855c
1ef48dd
f815f3b
fe8e1ba
a973e75
7e165f4
2da03c2
7a29b06
4ebb1db
104eef8
1e87ac4
0526ec2
68eaef7
49937c8
5542a0f
d00385c
a81b0f7
9a89e9f
00af833
1a37d82
8746b66
5ac6092
81d834b
b32c2f7
a14eea5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Adds support for handling avatar in SSO login. Contributed by @ashfame. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| import abc | ||
| import io | ||
| import logging | ||
| from typing import ( | ||
| TYPE_CHECKING, | ||
|
|
@@ -31,6 +32,7 @@ | |
| import attr | ||
| from typing_extensions import NoReturn, Protocol | ||
|
|
||
| from twisted.web.client import readBody | ||
| from twisted.web.iweb import IRequest | ||
| from twisted.web.server import Request | ||
|
|
||
|
|
@@ -42,6 +44,7 @@ | |
| from synapse.http import get_request_user_agent | ||
| from synapse.http.server import respond_with_html, respond_with_redirect | ||
| from synapse.http.site import SynapseRequest | ||
| from synapse.logging.context import make_deferred_yieldable | ||
| from synapse.types import ( | ||
| JsonDict, | ||
| UserID, | ||
|
|
@@ -137,6 +140,7 @@ class UserAttributes: | |
| localpart: Optional[str] | ||
| confirm_localpart: bool = False | ||
| display_name: Optional[str] = None | ||
| picture: Optional[str] = None | ||
| emails: Collection[str] = attr.Factory(list) | ||
|
|
||
|
|
||
|
|
@@ -194,6 +198,8 @@ def __init__(self, hs: "HomeServer"): | |
| self._error_template = hs.config.sso.sso_error_template | ||
| self._bad_user_template = hs.config.sso.sso_auth_bad_user_template | ||
| self._profile_handler = hs.get_profile_handler() | ||
| self.media_repo = hs.get_media_repository() | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self._http_client = hs.get_proxied_http_client() | ||
ashfame marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # The following template is shown after a successful user interactive | ||
| # authentication session. It tells the user they can close the window. | ||
|
|
@@ -701,8 +707,69 @@ async def _register_mapped_user( | |
| await self._store.record_user_external_id( | ||
| auth_provider_id, remote_user_id, registered_user_id | ||
| ) | ||
|
|
||
| # Set avatar, if available | ||
| if attributes.picture: | ||
| await self.set_avatar(registered_user_id, attributes.picture) | ||
|
|
||
| return registered_user_id | ||
|
|
||
| async def set_avatar(self, user_id: str, picture_https_url: str) -> None: | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try: | ||
| uid = UserID.from_string(user_id) | ||
|
|
||
| # HEAD request to find image size & mime type before download | ||
| response = await self._http_client.request("HEAD", picture_https_url) | ||
| if response.code != 200: | ||
| raise Exception("error sending HEAD request to get image size") | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| content_length = int( | ||
| response.headers.getRawHeaders(b"Content-Length")[0].decode("utf-8") | ||
| ) | ||
| content_type = response.headers.getRawHeaders(b"Content-Type")[0].decode( | ||
| "utf-8" | ||
| ) | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # ensure picture size respects max_avatar_size defined in config | ||
| if self._profile_handler.max_avatar_size is not None: | ||
| if content_length > self._profile_handler.max_avatar_size: | ||
| raise Exception("sso avatar too big in size") | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # ensure picture is of allowed mime type | ||
| if ( | ||
| self._profile_handler.allowed_avatar_mimetypes | ||
| and content_type not in self._profile_handler.allowed_avatar_mimetypes | ||
| ): | ||
| raise Exception("mime type not allowed for sso avatar") | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # download picture | ||
| response = await self._http_client.request("GET", picture_https_url) | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if response.code != 200: | ||
| raise Exception( | ||
| "error sending GET request to provided sso avatar image" | ||
| ) | ||
DMRobertson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| image = await make_deferred_yieldable(readBody(response)) | ||
|
|
||
| # store it in media repository | ||
| avatar_mxc_url = await self.media_repo.create_content( | ||
|
||
| str(content_type), | ||
ashfame marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| None, | ||
| io.BytesIO(image), # convert image into BytesIO | ||
| content_length, | ||
| uid, | ||
| ) | ||
|
|
||
| # save it as user avatar | ||
| await self._profile_handler.set_avatar_url( | ||
| uid, | ||
| create_requester(uid), | ||
| str(avatar_mxc_url), | ||
| ) | ||
|
|
||
| logger.info("successfully saved the user avatar") | ||
| except Exception: | ||
| logger.exception("failed to save the user avatar") | ||
DMRobertson marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| async def complete_sso_ui_auth_request( | ||
| self, | ||
| auth_provider_id: str, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.