Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Support icons for Identity Providers (#9154)
Browse files Browse the repository at this point in the history
  • Loading branch information
richvdh authored Jan 20, 2021
1 parent 6c0dfd2 commit 0cd2938
Show file tree
Hide file tree
Showing 19 changed files with 146 additions and 91 deletions.
1 change: 1 addition & 0 deletions changelog.d/9154.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for multiple SSO Identity Providers.
4 changes: 4 additions & 0 deletions docs/sample_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,10 @@ saml2_config:
# idp_name: A user-facing name for this identity provider, which is used to
# offer the user a choice of login mechanisms.
#
# idp_icon: An optional icon for this identity provider, which is presented
# by identity picker pages. If given, must be an MXC URI of the format
# mxc://<server-name>/<media-id>
#
# discover: set to 'false' to disable the use of the OIDC discovery mechanism
# to discover endpoints. Defaults to true.
#
Expand Down
1 change: 1 addition & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ files =
synapse/util/async_helpers.py,
synapse/util/caches,
synapse/util/metrics.py,
synapse/util/stringutils.py,
tests/replication,
tests/test_utils,
tests/handlers/test_password_providers.py,
Expand Down
20 changes: 20 additions & 0 deletions synapse/config/oidc_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from synapse.python_dependencies import DependencyException, check_requirements
from synapse.types import Collection, JsonDict
from synapse.util.module_loader import load_module
from synapse.util.stringutils import parse_and_validate_mxc_uri

from ._base import Config, ConfigError

Expand Down Expand Up @@ -66,6 +67,10 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
# idp_name: A user-facing name for this identity provider, which is used to
# offer the user a choice of login mechanisms.
#
# idp_icon: An optional icon for this identity provider, which is presented
# by identity picker pages. If given, must be an MXC URI of the format
# mxc://<server-name>/<media-id>
#
# discover: set to 'false' to disable the use of the OIDC discovery mechanism
# to discover endpoints. Defaults to true.
#
Expand Down Expand Up @@ -207,6 +212,7 @@ def generate_config_section(self, config_dir_path, server_name, **kwargs):
"properties": {
"idp_id": {"type": "string", "minLength": 1, "maxLength": 128},
"idp_name": {"type": "string"},
"idp_icon": {"type": "string"},
"discover": {"type": "boolean"},
"issuer": {"type": "string"},
"client_id": {"type": "string"},
Expand Down Expand Up @@ -336,9 +342,20 @@ def _parse_oidc_config_dict(
config_path + ("idp_id",),
)

# MSC2858 also specifies that the idp_icon must be a valid MXC uri
idp_icon = oidc_config.get("idp_icon")
if idp_icon is not None:
try:
parse_and_validate_mxc_uri(idp_icon)
except ValueError as e:
raise ConfigError(
"idp_icon must be a valid MXC URI", config_path + ("idp_icon",)
) from e

return OidcProviderConfig(
idp_id=idp_id,
idp_name=oidc_config.get("idp_name", "OIDC"),
idp_icon=idp_icon,
discover=oidc_config.get("discover", True),
issuer=oidc_config["issuer"],
client_id=oidc_config["client_id"],
Expand Down Expand Up @@ -366,6 +383,9 @@ class OidcProviderConfig:
# user-facing name for this identity provider.
idp_name = attr.ib(type=str)

# Optional MXC URI for icon for this IdP.
idp_icon = attr.ib(type=Optional[str])

# whether the OIDC discovery mechanism is used to discover endpoints
discover = attr.ib(type=bool)

Expand Down
2 changes: 1 addition & 1 deletion synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from netaddr import IPSet

from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.util.stringutils import parse_and_validate_server_name

from ._base import Config, ConfigError

Expand Down
2 changes: 1 addition & 1 deletion synapse/federation/federation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
from synapse.federation.federation_base import FederationBase, event_from_pdu_json
from synapse.federation.persistence import TransactionActions
from synapse.federation.units import Edu, Transaction
from synapse.http.endpoint import parse_server_name
from synapse.http.servlet import assert_params_in_dict
from synapse.logging.context import (
make_deferred_yieldable,
Expand All @@ -66,6 +65,7 @@
from synapse.util import glob_to_regex, json_decoder, unwrapFirstError
from synapse.util.async_helpers import Linearizer, concurrently_execute
from synapse.util.caches.response_cache import ResponseCache
from synapse.util.stringutils import parse_server_name

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand Down
2 changes: 1 addition & 1 deletion synapse/federation/transport/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
FEDERATION_V1_PREFIX,
FEDERATION_V2_PREFIX,
)
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.http.server import JsonResource
from synapse.http.servlet import (
parse_boolean_from_args,
Expand All @@ -45,6 +44,7 @@
)
from synapse.server import HomeServer
from synapse.types import ThirdPartyInstanceID, get_domain_from_id
from synapse.util.stringutils import parse_and_validate_server_name
from synapse.util.versionstring import get_version_string

logger = logging.getLogger(__name__)
Expand Down
4 changes: 4 additions & 0 deletions synapse/handlers/cas_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def __init__(self, hs: "HomeServer"):
# user-facing name of this auth provider
self.idp_name = "CAS"

# we do not currently support icons for CAS auth, but this is required by
# the SsoIdentityProvider protocol type.
self.idp_icon = None

self._sso_handler = hs.get_sso_handler()

self._sso_handler.register_identity_provider(self)
Expand Down
3 changes: 3 additions & 0 deletions synapse/handlers/oidc_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ def __init__(
# user-facing name of this auth provider
self.idp_name = provider.idp_name

# MXC URI for icon for this auth provider
self.idp_icon = provider.idp_icon

self._sso_handler = hs.get_sso_handler()

self._sso_handler.register_identity_provider(self)
Expand Down
2 changes: 1 addition & 1 deletion synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
from synapse.events import EventBase
from synapse.events.utils import copy_power_levels_contents
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.storage.state import StateFilter
from synapse.types import (
JsonDict,
Expand All @@ -55,6 +54,7 @@
from synapse.util import stringutils
from synapse.util.async_helpers import Linearizer
from synapse.util.caches.response_cache import ResponseCache
from synapse.util.stringutils import parse_and_validate_server_name
from synapse.visibility import filter_events_for_client

from ._base import BaseHandler
Expand Down
4 changes: 4 additions & 0 deletions synapse/handlers/saml_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def __init__(self, hs: "HomeServer"):
# user-facing name of this auth provider
self.idp_name = "SAML"

# we do not currently support icons for SAML auth, but this is required by
# the SsoIdentityProvider protocol type.
self.idp_icon = None

# a map from saml session id to Saml2SessionData object
self._outstanding_requests_dict = {} # type: Dict[str, Saml2SessionData]

Expand Down
5 changes: 5 additions & 0 deletions synapse/handlers/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ def idp_id(self) -> str:
def idp_name(self) -> str:
"""User-facing name for this provider"""

@property
def idp_icon(self) -> Optional[str]:
"""Optional MXC URI for user-facing icon"""
return None

@abc.abstractmethod
async def handle_redirect_request(
self,
Expand Down
79 changes: 0 additions & 79 deletions synapse/http/endpoint.py

This file was deleted.

3 changes: 3 additions & 0 deletions synapse/res/templates/sso_login_idp_picker.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ <h1 id="title">{{server_name | e}} Login</h1>
<li>
<input type="radio" name="idp" id="prov{{loop.index}}" value="{{p.idp_id}}">
<label for="prov{{loop.index}}">{{p.idp_name | e}}</label>
{% if p.idp_icon %}
<img src="{{p.idp_icon | mxc_to_http(32, 32)}}"/>
{% endif %}
</li>
{% endfor %}
</ul>
Expand Down
3 changes: 1 addition & 2 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
)
from synapse.api.filtering import Filter
from synapse.events.utils import format_event_for_client_v2
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.http.servlet import (
RestServlet,
assert_params_in_dict,
Expand All @@ -47,7 +46,7 @@
from synapse.streams.config import PaginationConfig
from synapse.types import RoomAlias, RoomID, StreamToken, ThirdPartyInstanceID, UserID
from synapse.util import json_decoder
from synapse.util.stringutils import random_string
from synapse.util.stringutils import parse_and_validate_server_name, random_string

if TYPE_CHECKING:
import synapse.server
Expand Down
6 changes: 2 additions & 4 deletions synapse/storage/databases/main/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import collections
import logging
import re
from abc import abstractmethod
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple
Expand All @@ -30,6 +29,7 @@
from synapse.types import JsonDict, ThirdPartyInstanceID
from synapse.util import json_encoder
from synapse.util.caches.descriptors import cached
from synapse.util.stringutils import MXC_REGEX

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -660,8 +660,6 @@ def _get_media_mxcs_in_room_txn(self, txn, room_id):
The local and remote media as a lists of tuples where the key is
the hostname and the value is the media ID.
"""
mxc_re = re.compile("^mxc://([^/]+)/([^/#?]+)")

sql = """
SELECT stream_ordering, json FROM events
JOIN event_json USING (room_id, event_id)
Expand All @@ -688,7 +686,7 @@ def _get_media_mxcs_in_room_txn(self, txn, room_id):
for url in (content_url, thumbnail_url):
if not url:
continue
matches = mxc_re.match(url)
matches = MXC_REGEX.match(url)
if matches:
hostname = matches.group(1)
media_id = matches.group(2)
Expand Down
2 changes: 1 addition & 1 deletion synapse/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from unpaddedbase64 import decode_base64

from synapse.api.errors import Codes, SynapseError
from synapse.http.endpoint import parse_and_validate_server_name
from synapse.util.stringutils import parse_and_validate_server_name

if TYPE_CHECKING:
from synapse.appservice.api import ApplicationService
Expand Down
Loading

0 comments on commit 0cd2938

Please sign in to comment.