Repository: matrix-org/synapse

Issue:
SSO login via CAS create user with registration disabled
### Description

If a user logs in with SSO (CAS) but is not registered in Matrix's user database, he should get an error warning that he is not authorized to access the service (401).

However, Matrix will automatically register the user regardless of the registration disabled parameter, then let him connect to the server and use the service.

### Steps to reproduce

Matrix config homeserver.yaml :

```
enable_registration: false
cas_config:
  enabled: true
  server_url: "https://cas.mydomain.com"
```

### Homeserver

Private server

### Synapse Version

1.85.0

### Installation Method

Debian packages from packages.matrix.org

### Database

PostgreSQL

### Workers

Single process

### Platform

KVM / Debian 11

### Configuration

_No response_

### Relevant log output

```shell
Not relevant ?
```


### Anything else that would be useful to know?

_No response_


Discussion:
Usually the whole point using SSO is to have SSO handling the users so Synapse "doesn't have to". I suppose there could be an way within SSO to allow or disallow matrix per uaer basis, but I don't know.
> Usually the whole point using SSO is to have SSO handling the users so Synapse "doesn't have to". I suppose there could be an way within SSO to allow or disallow matrix per uaer basis, but I don't know.

The advantage of using SSO is that users authenticate only once, whatever the application: Matrix, webmail, Nextcloud, etc. This means that applications don't have to manage authentication, but SSO doesn't have to replace the user database of these applications.

I've tried several configurations to avoid the problem of Matrix creating a user when it doesn't exist.

- The best way would be to use the `required_attributes` parameter in the `cas_config` but for some reason Synapse doesn't get the CAS attributes : https://github.com/matrix-org/synapse/issues/15807.
- I can imagine creating an intermediary between SSO and Matrix but it's very hacky.
- If I don't find it, I'll pre-register all my users and disable those who aren't authorized to use the service.

Ideally, of course, Matrix wouldn't register users on its own with `enable_registration: false` parameter ...
This is on purpose, cf [this comment](https://github.com/matrix-org/synapse/pull/14978#pullrequestreview-1297952284).

An `enable_registration` could be added at the CAS configuration level, as it was done for OIDC in the linked PR.

PR welcome :) 

Resolving patch:
diff --git a/synapse/config/cas.py b/synapse/config/cas.py
--- a/synapse/config/cas.py
+++ b/synapse/config/cas.py
@@ -57,6 +57,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
                 required_attributes
             )
 
+            self.cas_enable_registration = cas_config.get("enable_registration", True)
+
             self.idp_name = cas_config.get("idp_name", "CAS")
             self.idp_icon = cas_config.get("idp_icon")
             self.idp_brand = cas_config.get("idp_brand")
@@ -67,6 +69,7 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
             self.cas_protocol_version = None
             self.cas_displayname_attribute = None
             self.cas_required_attributes = []
+            self.cas_enable_registration = False
 
 
 # CAS uses a legacy required attributes mapping, not the one provided by
diff --git a/synapse/handlers/cas.py b/synapse/handlers/cas.py
--- a/synapse/handlers/cas.py
+++ b/synapse/handlers/cas.py
@@ -70,6 +70,7 @@ def __init__(self, hs: "HomeServer"):
         self._cas_protocol_version = hs.config.cas.cas_protocol_version
         self._cas_displayname_attribute = hs.config.cas.cas_displayname_attribute
         self._cas_required_attributes = hs.config.cas.cas_required_attributes
+        self._cas_enable_registration = hs.config.cas.cas_enable_registration
 
         self._http_client = hs.get_proxied_http_client()
 
@@ -395,4 +396,5 @@ async def grandfather_existing_users() -> Optional[str]:
             client_redirect_url,
             cas_response_to_user_attributes,
             grandfather_existing_users,
+            registration_enabled=self._cas_enable_registration,
         )


Test patch:
diff --git a/tests/handlers/test_cas.py b/tests/handlers/test_cas.py
--- a/tests/handlers/test_cas.py
+++ b/tests/handlers/test_cas.py
@@ -197,6 +197,23 @@ def test_required_attributes(self) -> None:
             auth_provider_session_id=None,
         )
 
+    @override_config({"cas_config": {"enable_registration": False}})
+    def test_map_cas_user_does_not_register_new_user(self) -> None:
+        """Ensures new users are not registered if the enabled registration flag is disabled."""
+
+        # stub out the auth handler
+        auth_handler = self.hs.get_auth_handler()
+        auth_handler.complete_sso_login = AsyncMock()  # type: ignore[method-assign]
+
+        cas_response = CasResponse("test_user", {})
+        request = _mock_request()
+        self.get_success(
+            self.handler._handle_cas_response(request, cas_response, "redirect_uri", "")
+        )
+
+        # check that the auth handler was not called as expected
+        auth_handler.complete_sso_login.assert_not_called()
+
 
 def _mock_request() -> Mock:
     """Returns a mock which will stand in as a SynapseRequest"""