Skip to content

Commit

Permalink
add oauth interface, add requirer implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanhphan1147 committed Dec 13, 2024
1 parent db9e9fe commit 74248fc
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 3 deletions.
4 changes: 3 additions & 1 deletion metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ requires:
interface: smtp
limit: 1
optional: true

oauth:
interface: oauth
limit: 1
peers:
synapse-peers:
interface: synapse-instance
7 changes: 6 additions & 1 deletion src/auth/mas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
"""Helper module used to manage MAS-related workloads."""
import logging
import secrets
import typing

import ops
from charms.hydra.v0.oauth import ClientConfig, OauthProviderConfig
from jinja2 import Environment, FileSystemLoader, select_autoescape

from state.charm_state import SynapseConfig
Expand Down Expand Up @@ -36,6 +38,10 @@
}
)

MAS_AUTHORIZATION_GRANT = ["authorization_code"]
MAS_TOKEN_ENDPOINT_AUTH_METHOD = "client_secret_basic"
MAS_OIDC_SCOPE = "openid profile email"


class MASConfigInvalidError(Exception):
"""Exception raised when validation of the MAS config failed."""
Expand Down Expand Up @@ -210,7 +216,6 @@ def generate_synapse_msc3861_config(
str: The rendered msc3861 configuration.
"""
mas_context = mas_configuration.mas_context

mas_prefix = mas_configuration.mas_prefix
# We explicitly set the oauth2 endpoints using MAS local address
# This is to avoid problems with TLS self-signed certificates
Expand Down
24 changes: 23 additions & 1 deletion src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
import typing

import ops
from charms.hydra.v0.oauth import OAuthRequirer
from charms.nginx_ingress_integrator.v0.nginx_route import require_nginx_route
from charms.redis_k8s.v0.redis import RedisRelationCharmEvents
from charms.traefik_k8s.v2.ingress import IngressPerAppRequirer
from charms.hydra.v0.oauth import OAuthRequirer
from ops import main
from ops.charm import ActionEvent, RelationDepartedEvent

Expand Down Expand Up @@ -113,6 +113,9 @@ def __init__(self, *args: typing.Any) -> None:
self.on.promote_user_admin_action, self._on_promote_user_admin_action
)
self.framework.observe(self.on.anonymize_user_action, self._on_anonymize_user_action)
self.framework.observe(self._oauth.on.oauth_info_changed, self._on_config_changed)
self.framework.observe(self._oauth.on.oauth_info_removed, self._on_config_changed)
self.framework.observe(self._oauth.on.invalid_client_config, self._on_config_changed)

def build_charm_state(self) -> CharmState:
"""Build charm state.
Expand Down Expand Up @@ -218,6 +221,25 @@ def reconcile(self, charm_state: CharmState, mas_configuration: MASConfiguration
self.unit.status = ops.MaintenanceStatus("Waiting for Synapse pebble")
return
self.model.unit.status = ops.MaintenanceStatus("Configuring Synapse")

oauth_client_config = self._mas.generate_oauth_client_config(
mas_configuration, charm_state.synapse_config
)
self._oauth.update_client_config(oauth_client_config)
oauth_provider_info = None
if self._oauth.is_client_created():
oauth_provider_info = self._oauth.get_provider_info()

rendered_mas_configuration = self._mas.generate_mas_config(
mas_configuration,
charm_state.synapse_config,
oauth_provider_info,
self.get_main_unit_address(),
)
synapse_msc3861_configuration = self._mas.generate_synapse_msc3861_config(
mas_configuration, charm_state.synapse_config
)

try:
# check signing key
signing_key_path = f"/data/{charm_state.synapse_config.server_name}.signing.key"
Expand Down
4 changes: 4 additions & 0 deletions src/state/mas.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class MASContext(BaseModel):
synapse_shared_secret: Used to authenticate MAS to the homeserver
synapse_oidc_client_id: OIDC client ID used by synapse
synapse_oidc_client_secret: OIDC client secret used by synapse
upstream_oidc_provider_id: ULID to identify the upstream oidc provider
"""

encryption_key: str = Field(min_length=64, max_length=64)
Expand All @@ -55,6 +56,7 @@ class MASContext(BaseModel):
synapse_shared_secret: str = Field(min_length=32, max_length=32)
synapse_oidc_client_id: str = Field()
synapse_oidc_client_secret: str = Field(min_length=32, max_length=32)
upstream_oidc_provider_id: str = Field()


class SigningKey(typing.NamedTuple):
Expand Down Expand Up @@ -161,6 +163,7 @@ def from_charm(cls, charm: ops.CharmBase) -> "MASConfiguration":
"synapse-shared-secret": secrets.token_hex(16),
"synapse-oidc-client-id": str(ULID()),
"synapse-oidc-client-secret": secrets.token_hex(16),
"upstream-oidc-provider-id": str(ULID()),
}
secret = charm.app.add_secret(content=mas_context_secret, label=MAS_CONTEXT_LABEL)

Expand All @@ -172,6 +175,7 @@ def from_charm(cls, charm: ops.CharmBase) -> "MASConfiguration":
synapse_shared_secret=mas_context_secret["synapse-shared-secret"],
synapse_oidc_client_id=mas_context_secret["synapse-oidc-client-id"],
synapse_oidc_client_secret=mas_context_secret["synapse-oidc-client-secret"],
upstream_oidc_provider_id=mas_context_secret["upstream-oidc-provider-id"],
)
except ValidationError as exc:
logger.exception("Error validating MAS context.")
Expand Down
23 changes: 23 additions & 0 deletions templates/mas_config.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,26 @@ templates:
translations_path: /mas/share/translations
policy:
wasm_module: /mas/share/policy.wasm
{% if oauth_provider_config is not None %}
providers:
- id: {{ upstream_oidc_provider_id }}
human_name: SSO
issuer: {{ oauth_provider_config.issuer_url }}
client_id: {{ oauth_provider_config.client_id }}
client_secret: {{ oauth_provider_config.client_secret }}
scope: "openid profile email"
token_endpoint_auth_method: client_secret_basic
discovery_mode: oidc
claims_imports:
subject:
action: require
template: {{ '"{{ user.sub }}' }}
displayname:
action: suggest
template: {{ '"{{ user.name }}"' }}
localpart:
action: ignore
email:
action: suggest
template: {{ '"{{ user.email }}"' }}
{% endif %}
3 changes: 3 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ def harness_fixture(request, monkeypatch) -> typing.Generator[Harness, None, Non
monkeypatch.setattr(
"state.mas.MASConfiguration.from_charm", MagicMock(return_value=MagicMock())
)
monkeypatch.setattr(
"charm.generate_oauth_client_config", MagicMock(return_value=None)
)
monkeypatch.setattr("pebble._push_mas_config", MagicMock())
monkeypatch.setattr("charm.generate_mas_config", MagicMock(return_value=""))
monkeypatch.setattr("charm.generate_synapse_msc3861_config", MagicMock(return_value={}))
Expand Down

0 comments on commit 74248fc

Please sign in to comment.