Introduce compat login policy

This commit is contained in:
Olivier 'reivilibre
2025-11-25 18:20:14 +00:00
parent 1d2f7fecf8
commit 069b57758b
6 changed files with 251 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ INPUTS := \
client_registration/client_registration.rego \
register/register.rego \
authorization_grant/authorization_grant.rego \
compat_login/compat_login.rego \
email/email.rego
ifeq ($(DOCKER), 1)
@@ -38,6 +39,7 @@ policy.wasm: $(INPUTS)
-e "client_registration/violation" \
-e "register/violation" \
-e "authorization_grant/violation" \
-e "compat_login/violation" \
-e "email/violation" \
$^
tar xzf bundle.tar.gz /policy.wasm

View File

@@ -0,0 +1,61 @@
# Copyright 2025 Element Creations Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.
# METADATA
# schemas:
# - input: schema["compat_login_input"]
package compat_login
import rego.v1
import data.common
default allow := false
allow if {
count(violation) == 0
}
violation contains {"msg": sprintf(
"Requester [%s] isn't allowed to do this action",
[common.format_requester(input.requester)],
)} if {
common.requester_banned(input.requester, data.requester)
}
violation contains {
"code": "too-many-sessions",
"msg": "user has too many active sessions (soft limit)",
} if {
# Only apply if session limits are enabled in the config
data.session_limit != null
# This is a web-based interactive login
# TODO not strictly correct...
input.login_type == "m.login.sso"
# For web-based 'compat SSO' login, a violation occurs when the soft limit has already been
# reached or exceeded.
# We use the soft limit because the user will be able to interactively remove
# sessions to return under the limit.
data.session_limit.soft_limit <= input.session_counts.total
}
violation contains {
"code": "too-many-sessions",
"msg": "user has too many active sessions (hard limit)",
} if {
# Only apply if session limits are enabled in the config
data.session_limit != null
# This is not a web-based interactive login
input.login_type == "m.login.password"
# For `m.login.password` login, a violation occurs when the hard limit has already been
# reached or exceeded.
# We don't use the soft limit because the user won't be able to interactively remove
# sessions to return under the limit.
data.session_limit.hard_limit <= input.session_counts.total
}

View File

@@ -0,0 +1,72 @@
# Copyright 2025 Element Creations Ltd.
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
# Please see LICENSE files in the repository root for full details.
package compat_login_test
import data.compat_login
import rego.v1
user := {"username": "john"}
test_session_limiting_sso if {
compat_login.allow with input.user as user
with input.session_counts as {"total": 1}
with input.login_type as "m.login.sso"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
compat_login.allow with input.user as user
with input.session_counts as {"total": 31}
with input.login_type as "m.login.sso"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
not compat_login.allow with input.user as user
with input.session_counts as {"total": 32}
with input.login_type as "m.login.sso"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
not compat_login.allow with input.user as user
with input.session_counts as {"total": 42}
with input.login_type as "m.login.sso"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
not compat_login.allow with input.user as user
with input.session_counts as {"total": 65}
with input.login_type as "m.login.sso"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
# No limit configured
compat_login.allow with input.user as user
with input.session_counts as {"total": 1}
with input.login_type as "m.login.sso"
with data.session_limit as null
}
test_session_limiting_password if {
compat_login.allow with input.user as user
with input.session_counts as {"total": 1}
with input.login_type as "m.login.password"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
compat_login.allow with input.user as user
with input.session_counts as {"total": 63}
with input.login_type as "m.login.password"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
not compat_login.allow with input.user as user
with input.session_counts as {"total": 64}
with input.login_type as "m.login.password"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
not compat_login.allow with input.user as user
with input.session_counts as {"total": 65}
with input.login_type as "m.login.password"
with data.session_limit as {"soft_limit": 32, "hard_limit": 64}
# No limit configured
compat_login.allow with input.user as user
with input.session_counts as {"total": 1}
with input.login_type as "m.login.password"
with data.session_limit as null
}

View File

@@ -0,0 +1,88 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "CompatLoginInput",
"description": "Input for the compatibility login policy.",
"type": "object",
"required": [
"login_type",
"requester",
"session_counts",
"user"
],
"properties": {
"user": {
"type": "object",
"additionalProperties": true
},
"session_counts": {
"description": "How many sessions the user has.",
"allOf": [
{
"$ref": "#/definitions/SessionCounts"
}
]
},
"login_type": {
"$ref": "#/definitions/CompatLoginType"
},
"requester": {
"$ref": "#/definitions/Requester"
}
},
"definitions": {
"SessionCounts": {
"description": "Information about how many sessions the user has",
"type": "object",
"required": [
"compat",
"oauth2",
"personal",
"total"
],
"properties": {
"total": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"oauth2": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"compat": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"personal": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
}
},
"CompatLoginType": {
"type": "string",
"enum": [
"m.login.sso",
"m.login.password"
]
},
"Requester": {
"description": "Identity of the requester",
"type": "object",
"properties": {
"ip_address": {
"description": "IP address of the entity making the request",
"type": "string",
"format": "ip"
},
"user_agent": {
"description": "User agent of the entity making the request",
"type": "string"
}
}
}
}
}