Setup Regal to lint policies and clean them up
This commit is contained in:
6
policies/.regal/config.yaml
Normal file
6
policies/.regal/config.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
rules:
|
||||
style:
|
||||
external-reference:
|
||||
level: ignore
|
||||
line-length:
|
||||
level: ignore
|
||||
@@ -1,24 +1,28 @@
|
||||
# Set to 1 to run OPA through Docker
|
||||
DOCKER := 0
|
||||
PODMAN := 0
|
||||
OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:0.64.1-debug
|
||||
OPA_DOCKER_IMAGE := docker.io/openpolicyagent/opa:0.70.0-debug
|
||||
REGAL_DOCKER_IMAGE := ghcr.io/styrainc/regal:0.29.2
|
||||
|
||||
INPUTS := \
|
||||
client_registration.rego \
|
||||
register.rego \
|
||||
authorization_grant.rego \
|
||||
email.rego
|
||||
client_registration/client_registration.rego \
|
||||
register/register.rego \
|
||||
authorization_grant/authorization_grant.rego \
|
||||
email/email.rego
|
||||
|
||||
ifeq ($(DOCKER), 1)
|
||||
OPA := docker run -i -v $(shell pwd):/policies:ro -w /policies --rm $(OPA_DOCKER_IMAGE)
|
||||
OPA_RW := docker run -i -v $(shell pwd):/policies -w /policies --rm $(OPA_DOCKER_IMAGE)
|
||||
REGAL := docker run -i -v $(shell pwd):/policies:ro -w /policies --rm $(REGAL_DOCKER_IMAGE)
|
||||
else ifeq ($(PODMAN), 1)
|
||||
# When running rootless, the volume directory may need to be given global write permissions on the host
|
||||
OPA := podman run -i -v $(shell pwd):/policies:ro:Z -w /policies --rm $(OPA_DOCKER_IMAGE)
|
||||
OPA_RW := podman run -i -v $(shell pwd):/policies:Z -w /policies --rm $(OPA_DOCKER_IMAGE)
|
||||
REGAL := podman run -i -v $(shell pwd):/policies:ro:Z -w /policies --rm $(REGAL_DOCKER_IMAGE)
|
||||
else
|
||||
OPA := opa
|
||||
OPA_RW := opa
|
||||
REGAL := regal
|
||||
endif
|
||||
|
||||
policy.wasm: $(INPUTS)
|
||||
@@ -38,16 +42,17 @@ fmt:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
$(OPA) test --schema ./schema/ -v ./*.rego
|
||||
$(OPA) test --schema ./schema/ --ignore schema -v ./
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
$(OPA) test --coverage ./*.rego | $(OPA) eval --format pretty \
|
||||
$(OPA) test --coverage --schema ./schema/ --ignore schema ./ | $(OPA) eval --format pretty \
|
||||
--stdin-input \
|
||||
--data util/coveralls.rego \
|
||||
data.coveralls.from_opa > coverage.json
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(OPA) fmt -d --fail ./*.rego util/*.rego
|
||||
$(OPA) check --strict --schema schema/ ./*.rego util/*.rego
|
||||
$(OPA) fmt -d --fail .
|
||||
$(OPA) check --strict --schema schema/ --ignore schema .
|
||||
$(REGAL) lint .
|
||||
|
||||
@@ -3,39 +3,39 @@
|
||||
# - input: schema["authorization_grant_input"]
|
||||
package authorization_grant
|
||||
|
||||
import future.keywords.in
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow {
|
||||
allow if {
|
||||
count(violation) == 0
|
||||
}
|
||||
|
||||
# Users can request admin scopes if either:
|
||||
# 1. They are in the admin_users list
|
||||
can_request_admin(user) {
|
||||
can_request_admin(user) if {
|
||||
some admin_user in data.admin_users
|
||||
user.username == admin_user
|
||||
}
|
||||
|
||||
# 2. They have the can_request_admin flag set to true
|
||||
can_request_admin(user) {
|
||||
can_request_admin(user) if {
|
||||
user.can_request_admin
|
||||
}
|
||||
|
||||
interactive_grant_type("authorization_code") = true
|
||||
interactive_grant_type("authorization_code") := true
|
||||
|
||||
interactive_grant_type("urn:ietf:params:oauth:grant-type:device_code") = true
|
||||
interactive_grant_type("urn:ietf:params:oauth:grant-type:device_code") := true
|
||||
|
||||
# Special case to make empty scope work
|
||||
allowed_scope("") = true
|
||||
allowed_scope("") := true
|
||||
|
||||
allowed_scope("openid") = true
|
||||
allowed_scope("openid") := true
|
||||
|
||||
allowed_scope("email") = true
|
||||
allowed_scope("email") := true
|
||||
|
||||
# This grants access to Synapse's admin API endpoints
|
||||
allowed_scope("urn:synapse:admin:*") {
|
||||
allowed_scope("urn:synapse:admin:*") if {
|
||||
# Synapse doesn't support user-less tokens yet, so access to the admin API
|
||||
# can only be used with an authorization_code grant or a device code grant
|
||||
# as the user is present
|
||||
@@ -44,39 +44,41 @@ allowed_scope("urn:synapse:admin:*") {
|
||||
}
|
||||
|
||||
# This grants access to the /graphql API endpoint
|
||||
allowed_scope("urn:mas:graphql:*") = true
|
||||
allowed_scope("urn:mas:graphql:*") := true
|
||||
|
||||
# This makes it possible to query and do anything in the GraphQL API as an admin
|
||||
allowed_scope("urn:mas:admin") {
|
||||
allowed_scope("urn:mas:admin") if {
|
||||
interactive_grant_type(input.grant_type)
|
||||
can_request_admin(input.user)
|
||||
}
|
||||
|
||||
# This makes it possible to get the admin scope for clients that are allowed
|
||||
allowed_scope("urn:mas:admin") {
|
||||
allowed_scope("urn:mas:admin") if {
|
||||
input.grant_type == "client_credentials"
|
||||
some client in data.admin_clients
|
||||
input.client.id == client
|
||||
}
|
||||
|
||||
allowed_scope(scope) {
|
||||
allowed_scope(scope) if {
|
||||
# Grant access to the C-S API only if there is a user
|
||||
interactive_grant_type(input.grant_type)
|
||||
regex.match("^urn:matrix:org.matrix.msc2967.client:device:[A-Za-z0-9._~!$&'()*+,;=:@/-]{10,}$", scope)
|
||||
regex.match(`^urn:matrix:org.matrix.msc2967.client:device:[A-Za-z0-9._~!$&'()*+,;=:@/-]{10,}$`, scope)
|
||||
}
|
||||
|
||||
allowed_scope("urn:matrix:org.matrix.msc2967.client:api:*") {
|
||||
allowed_scope("urn:matrix:org.matrix.msc2967.client:api:*") if {
|
||||
# Grant access to the C-S API only if there is a user
|
||||
interactive_grant_type(input.grant_type)
|
||||
}
|
||||
|
||||
violation[{"msg": msg}] {
|
||||
# METADATA
|
||||
# entrypoint: true
|
||||
violation contains {"msg": msg} if {
|
||||
some scope in split(input.scope, " ")
|
||||
not allowed_scope(scope)
|
||||
msg := sprintf("scope '%s' not allowed", [scope])
|
||||
}
|
||||
|
||||
violation[{"msg": "only one device scope is allowed at a time"}] {
|
||||
violation contains {"msg": "only one device scope is allowed at a time"} if {
|
||||
scope_list := split(input.scope, " ")
|
||||
count({key | scope_list[key]; startswith(scope_list[key], "urn:matrix:org.matrix.msc2967.client:device:")}) > 1
|
||||
count({scope | some scope in scope_list; startswith(scope, "urn:matrix:org.matrix.msc2967.client:device:")}) > 1
|
||||
}
|
||||
@@ -1,155 +1,134 @@
|
||||
package authorization_grant
|
||||
package authorization_grant_test
|
||||
|
||||
import data.authorization_grant
|
||||
import rego.v1
|
||||
|
||||
user := {"username": "john"}
|
||||
|
||||
client := {"client_id": "client"}
|
||||
|
||||
test_standard_scopes {
|
||||
allow with input.user as user
|
||||
test_standard_scopes if {
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as ""
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "openid"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "email"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "openid email"
|
||||
|
||||
# Not supported yet
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "phone"
|
||||
|
||||
# Not supported yet
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "profile"
|
||||
}
|
||||
|
||||
test_matrix_scopes {
|
||||
allow with input.user as user
|
||||
test_matrix_scopes if {
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:api:*"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:api:*"
|
||||
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "client_credentials"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:api:*"
|
||||
}
|
||||
|
||||
test_device_scopes {
|
||||
allow with input.user as user
|
||||
test_device_scopes if {
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01-asdasdsa1-2313"
|
||||
|
||||
# Too short
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:abcd"
|
||||
|
||||
# Multiple device scope
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01 urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd02"
|
||||
|
||||
# Allowed with the device code grant
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"
|
||||
|
||||
# Not allowed for the client credentials grant
|
||||
not allow with input.client as client
|
||||
# Not authorization_grant.allowed for the client credentials grant
|
||||
not authorization_grant.allow with input.client as client
|
||||
with input.grant_type as "client_credentials"
|
||||
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"
|
||||
}
|
||||
|
||||
test_synapse_admin_scopes {
|
||||
allow with input.user as user
|
||||
test_synapse_admin_scopes if {
|
||||
some grant_type in ["authorization_code", "urn:ietf:params:oauth:grant-type:device_code"]
|
||||
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as ["john"]
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.grant_type as grant_type
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as ["john"]
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.grant_type as grant_type
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
not allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.user.can_request_admin as true
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.grant_type as grant_type
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
allow with input.user as user
|
||||
with input.user.can_request_admin as true
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.user.can_request_admin as false
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
|
||||
not allow with input.user as user
|
||||
with input.user.can_request_admin as false
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "urn:ietf:params:oauth:grant-type:device_code"
|
||||
with input.grant_type as grant_type
|
||||
with input.scope as "urn:synapse:admin:*"
|
||||
}
|
||||
|
||||
test_mas_scopes {
|
||||
allow with input.user as user
|
||||
test_mas_scopes if {
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with input.scope as "urn:mas:graphql:*"
|
||||
|
||||
allow with input.user as user
|
||||
authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as ["john"]
|
||||
with input.grant_type as "authorization_code"
|
||||
with input.scope as "urn:mas:admin"
|
||||
|
||||
not allow with input.user as user
|
||||
not authorization_grant.allow with input.user as user
|
||||
with input.client as client
|
||||
with data.admin_users as []
|
||||
with input.grant_type as "authorization_code"
|
||||
@@ -3,26 +3,27 @@
|
||||
# - input: schema["client_registration_input"]
|
||||
package client_registration
|
||||
|
||||
import future.keywords.in
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow {
|
||||
allow if {
|
||||
count(violation) == 0
|
||||
}
|
||||
|
||||
parse_uri(url) = obj {
|
||||
parse_uri(url) := obj if {
|
||||
is_string(url)
|
||||
[matches] := regex.find_all_string_submatch_n("^(?P<scheme>[a-z][a-z0-9+.-]*):(?://(?P<host>((?:(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\\.)*(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])|127.0.0.1|0.0.0.0|\\[::1\\])(?::(?P<port>[0-9]+))?))?(?P<path>/[A-Za-z0-9/.-]*)$", url, 1)
|
||||
url_regex := `^(?P<scheme>[a-z][a-z0-9+.-]*):(?://(?P<host>((?:(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\.)*(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])|127.0.0.1|0.0.0.0|\[::1\])(?::(?P<port>[0-9]+))?))?(?P<path>/[A-Za-z0-9/.-]*)$`
|
||||
[matches] := regex.find_all_string_submatch_n(url_regex, url, 1)
|
||||
obj := {"scheme": matches[1], "authority": matches[2], "host": matches[3], "port": matches[4], "path": matches[5]}
|
||||
}
|
||||
|
||||
secure_url(x) {
|
||||
secure_url(x) if {
|
||||
x
|
||||
data.client_registration.allow_insecure_uris
|
||||
}
|
||||
|
||||
secure_url(x) {
|
||||
secure_url(x) if {
|
||||
url := parse_uri(x)
|
||||
url.scheme == "https"
|
||||
|
||||
@@ -36,14 +37,14 @@ secure_url(x) {
|
||||
url.port == ""
|
||||
}
|
||||
|
||||
host_matches_client_uri(x) {
|
||||
host_matches_client_uri(x) if {
|
||||
x
|
||||
|
||||
# Do not check we allow host mismatch
|
||||
data.client_registration.allow_host_mismatch
|
||||
}
|
||||
|
||||
host_matches_client_uri(x) {
|
||||
host_matches_client_uri(x) if {
|
||||
x
|
||||
|
||||
# Do not check if the client_uri is missing and we allow that
|
||||
@@ -51,99 +52,41 @@ host_matches_client_uri(x) {
|
||||
not data.client_metadata.client_uri
|
||||
}
|
||||
|
||||
host_matches_client_uri(x) {
|
||||
host_matches_client_uri(x) if {
|
||||
client_uri := parse_uri(input.client_metadata.client_uri)
|
||||
uri := parse_uri(x)
|
||||
is_subdomain(client_uri.host, uri.host)
|
||||
}
|
||||
|
||||
violation[{"msg": "missing client_uri"}] {
|
||||
not data.client_registration.allow_missing_client_uri
|
||||
not input.client_metadata.client_uri
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid client_uri"}] {
|
||||
not secure_url(input.client_metadata.client_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid tos_uri"}] {
|
||||
input.client_metadata.tos_uri
|
||||
not secure_url(input.client_metadata.tos_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "tos_uri not on the same host as the client_uri"}] {
|
||||
input.client_metadata.tos_uri
|
||||
not host_matches_client_uri(input.client_metadata.tos_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid policy_uri"}] {
|
||||
input.client_metadata.policy_uri
|
||||
not secure_url(input.client_metadata.policy_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "policy_uri not on the same host as the client_uri"}] {
|
||||
input.client_metadata.policy_uri
|
||||
not host_matches_client_uri(input.client_metadata.policy_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid logo_uri"}] {
|
||||
input.client_metadata.logo_uri
|
||||
not secure_url(input.client_metadata.logo_uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "logo_uri not on the same host as the client_uri"}] {
|
||||
input.client_metadata.logo_uri
|
||||
not host_matches_client_uri(input.client_metadata.logo_uri)
|
||||
}
|
||||
|
||||
# If the grant_types is missing, we assume it is authorization_code
|
||||
uses_grant_type("authorization_code") {
|
||||
not input.client_metadata.grant_types
|
||||
uses_grant_type("authorization_code", client_metadata) if {
|
||||
not client_metadata.grant_types
|
||||
}
|
||||
|
||||
# Else, we check that the grant_types contains the given grant_type
|
||||
uses_grant_type(grant_type) {
|
||||
some gt in input.client_metadata.grant_types
|
||||
gt == grant_type
|
||||
uses_grant_type(grant_type, client_metadata) if {
|
||||
some grant in client_metadata.grant_types
|
||||
grant == grant_type
|
||||
}
|
||||
|
||||
# Consider a client public if the authentication method is none
|
||||
is_public_client {
|
||||
is_public_client if {
|
||||
input.client_metadata.token_endpoint_auth_method == "none"
|
||||
}
|
||||
|
||||
requires_redirect_uris {
|
||||
uses_grant_type("authorization_code")
|
||||
requires_redirect_uris if {
|
||||
uses_grant_type("authorization_code", input.client_metadata)
|
||||
}
|
||||
|
||||
requires_redirect_uris {
|
||||
uses_grant_type("implicit")
|
||||
}
|
||||
|
||||
violation[{"msg": "client_credentials grant_type requires some form of client authentication"}] {
|
||||
uses_grant_type("client_credentials")
|
||||
is_public_client
|
||||
}
|
||||
|
||||
violation[{"msg": "missing redirect_uris"}] {
|
||||
requires_redirect_uris
|
||||
not input.client_metadata.redirect_uris
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid redirect_uris: it must be an array"}] {
|
||||
not is_array(input.client_metadata.redirect_uris)
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid redirect_uris: it must have at least one redirect_uri"}] {
|
||||
requires_redirect_uris
|
||||
count(input.client_metadata.redirect_uris) == 0
|
||||
requires_redirect_uris if {
|
||||
uses_grant_type("implicit", input.client_metadata)
|
||||
}
|
||||
|
||||
# Used to verify that a reverse-dns formatted scheme is a strict subdomain of
|
||||
# another host.
|
||||
# This is used so a redirect_uri like 'com.example.app:/' works for
|
||||
# a 'client_uri' of 'https://example.com/'
|
||||
reverse_dns_match(host, reverse_dns) {
|
||||
reverse_dns_match(host, reverse_dns) if {
|
||||
is_string(host)
|
||||
is_string(reverse_dns)
|
||||
|
||||
@@ -158,7 +101,7 @@ reverse_dns_match(host, reverse_dns) {
|
||||
}
|
||||
|
||||
# Used to verify that all the various URIs are subdomains of the client_uri
|
||||
is_subdomain(host, subdomain) {
|
||||
is_subdomain(host, subdomain) if {
|
||||
is_string(host)
|
||||
is_string(subdomain)
|
||||
|
||||
@@ -172,27 +115,21 @@ is_subdomain(host, subdomain) {
|
||||
array.slice(subdomain_parts, 0, count(host_parts)) == host_parts
|
||||
}
|
||||
|
||||
valid_native_redirector(x) {
|
||||
is_localhost("localhost")
|
||||
|
||||
is_localhost("127.0.0.1")
|
||||
|
||||
is_localhost("[::1]")
|
||||
|
||||
valid_native_redirector(x) if {
|
||||
url := parse_uri(x)
|
||||
is_localhost(url.host)
|
||||
url.scheme == "http"
|
||||
}
|
||||
|
||||
is_localhost(host) {
|
||||
host == "localhost"
|
||||
}
|
||||
|
||||
is_localhost(host) {
|
||||
host == "127.0.0.1"
|
||||
}
|
||||
|
||||
is_localhost(host) {
|
||||
host == "[::1]"
|
||||
}
|
||||
|
||||
# Custom schemes should match the client_uri, reverse-dns style
|
||||
# e.g. io.element.app:/ matches https://app.element.io/
|
||||
valid_native_redirector(x) {
|
||||
valid_native_redirector(x) if {
|
||||
url := parse_uri(x)
|
||||
url.scheme != "http"
|
||||
url.scheme != "https"
|
||||
@@ -203,17 +140,71 @@ valid_native_redirector(x) {
|
||||
reverse_dns_match(client_uri.host, url.scheme)
|
||||
}
|
||||
|
||||
valid_redirect_uri(uri) {
|
||||
valid_redirect_uri(uri) if {
|
||||
input.client_metadata.application_type == "native"
|
||||
valid_native_redirector(uri)
|
||||
}
|
||||
|
||||
valid_redirect_uri(uri) {
|
||||
valid_redirect_uri(uri) if {
|
||||
secure_url(uri)
|
||||
host_matches_client_uri(uri)
|
||||
}
|
||||
|
||||
violation[{"msg": "invalid redirect_uri", "redirect_uri": redirect_uri}] {
|
||||
# METADATA
|
||||
# entrypoint: true
|
||||
violation contains {"msg": "missing client_uri"} if {
|
||||
not data.client_registration.allow_missing_client_uri
|
||||
not input.client_metadata.client_uri
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid client_uri"} if {
|
||||
not secure_url(input.client_metadata.client_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid tos_uri"} if {
|
||||
not secure_url(input.client_metadata.tos_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "tos_uri not on the same host as the client_uri"} if {
|
||||
not host_matches_client_uri(input.client_metadata.tos_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid policy_uri"} if {
|
||||
not secure_url(input.client_metadata.policy_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "policy_uri not on the same host as the client_uri"} if {
|
||||
not host_matches_client_uri(input.client_metadata.policy_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid logo_uri"} if {
|
||||
not secure_url(input.client_metadata.logo_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "logo_uri not on the same host as the client_uri"} if {
|
||||
not host_matches_client_uri(input.client_metadata.logo_uri)
|
||||
}
|
||||
|
||||
violation contains {"msg": "client_credentials grant_type requires some form of client authentication"} if {
|
||||
uses_grant_type("client_credentials", input.client_metadata)
|
||||
is_public_client
|
||||
}
|
||||
|
||||
violation contains {"msg": "missing redirect_uris"} if {
|
||||
requires_redirect_uris
|
||||
not input.client_metadata.redirect_uris
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid redirect_uris: it must be an array"} if {
|
||||
not is_array(input.client_metadata.redirect_uris)
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid redirect_uris: it must have at least one redirect_uri"} if {
|
||||
requires_redirect_uris
|
||||
count(input.client_metadata.redirect_uris) == 0
|
||||
}
|
||||
|
||||
violation contains {"msg": "invalid redirect_uri", "redirect_uri": redirect_uri} if {
|
||||
some redirect_uri in input.client_metadata.redirect_uris
|
||||
not valid_redirect_uri(redirect_uri)
|
||||
}
|
||||
@@ -1,273 +1,297 @@
|
||||
package client_registration
|
||||
package client_registration_test
|
||||
|
||||
test_valid {
|
||||
allow with input.client_metadata as {
|
||||
import rego.v1
|
||||
|
||||
import data.client_registration
|
||||
|
||||
test_valid if {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["authorization_code"],
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_missing_client_uri {
|
||||
not allow with input.client_metadata as {"grant_types": []}
|
||||
test_missing_client_uri if {
|
||||
not client_registration.allow with input.client_metadata as {"grant_types": []}
|
||||
|
||||
allow with input.client_metadata as {"grant_types": []}
|
||||
with data.client_registration.allow_missing_client_uri as true
|
||||
client_registration.allow with input.client_metadata as {"grant_types": []}
|
||||
with client_registration.allow_missing_client_uri as true
|
||||
}
|
||||
|
||||
test_insecure_client_uri {
|
||||
not allow with input.client_metadata as {
|
||||
test_insecure_client_uri if {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "http://example.com/",
|
||||
}
|
||||
}
|
||||
|
||||
test_tos_uri {
|
||||
allow with input.client_metadata as {
|
||||
test_tos_uri if {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.com/tos",
|
||||
}
|
||||
}
|
||||
|
||||
test_tos_uri_insecure if {
|
||||
# Insecure
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "http://example.com/tos",
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "http://example.com/tos",
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
with client_registration.allow_insecure_uris as true
|
||||
}
|
||||
|
||||
test_tos_uri_host_mismatch if {
|
||||
# Host mistmatch
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.org/tos",
|
||||
}
|
||||
|
||||
# TOS on a subdomain of the client_uri host is allowed
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://tos.example.com/",
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"tos_uri": "https://example.org/tos",
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
with client_registration.allow_host_mismatch as true
|
||||
}
|
||||
|
||||
test_logo_uri {
|
||||
allow with input.client_metadata as {
|
||||
test_logo_uri if {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.com/logo.png",
|
||||
}
|
||||
}
|
||||
|
||||
test_logo_uri_insecure if {
|
||||
# Insecure
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "http://example.com/logo.png",
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "http://example.com/logo.png",
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
with client_registration.allow_insecure_uris as true
|
||||
}
|
||||
|
||||
test_logo_uri_host_mismatch if {
|
||||
# Host mistmatch
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.org/logo.png",
|
||||
}
|
||||
|
||||
# Logo on a subdomain of the client_uri host is allowed
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://static.example.com/logo.png",
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"logo_uri": "https://example.org/logo.png",
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
with client_registration.allow_host_mismatch as true
|
||||
}
|
||||
|
||||
test_policy_uri {
|
||||
allow with input.client_metadata as {
|
||||
test_policy_uri if {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.com/policy",
|
||||
}
|
||||
}
|
||||
|
||||
test_policy_uri_insecure if {
|
||||
# Insecure
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "http://example.com/policy",
|
||||
}
|
||||
|
||||
# Insecure, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "http://example.com/policy",
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
with client_registration.allow_insecure_uris as true
|
||||
}
|
||||
|
||||
test_policy_uri_host_mismatch if {
|
||||
# Host mistmatch
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.org/policy",
|
||||
}
|
||||
|
||||
# Policy on a subdomain of the client_uri host is allowed
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://policy.example.com/",
|
||||
}
|
||||
|
||||
# Host mistmatch, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": [],
|
||||
"client_uri": "https://example.com/",
|
||||
"policy_uri": "https://example.org/policy",
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
with client_registration.allow_host_mismatch as true
|
||||
}
|
||||
|
||||
test_redirect_uris {
|
||||
test_redirect_uris if {
|
||||
# Missing redirect_uris
|
||||
not allow with input.client_metadata as {"client_uri": "https://example.com/"}
|
||||
not client_registration.allow with input.client_metadata as {"client_uri": "https://example.com/"}
|
||||
|
||||
# redirect_uris is not an array
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": "https://example.com/callback",
|
||||
}
|
||||
|
||||
# Empty redirect_uris
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": [],
|
||||
}
|
||||
|
||||
# Not required for the client_credentials grant
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials"],
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
|
||||
# Required for the authorization_code grant
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials", "refresh_token", "authorization_code"],
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
|
||||
# Required for the implicit grant
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials", "implicit"],
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
}
|
||||
|
||||
test_web_redirect_uri {
|
||||
allow with input.client_metadata as {
|
||||
test_web_redirect_uri if {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.com/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_web_redirect_uri_insecure if {
|
||||
# Insecure URL
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/callback", "https://example.com/callback"],
|
||||
}
|
||||
|
||||
# Insecure URL, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/callback", "https://example.com/callback"],
|
||||
}
|
||||
with data.client_registration.allow_insecure_uris as true
|
||||
with client_registration.allow_insecure_uris as true
|
||||
}
|
||||
|
||||
test_web_redirect_uri_host_mismatch if {
|
||||
# Host mismatch
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"],
|
||||
}
|
||||
|
||||
# Host mismatch, but allowed by the config
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/second/callback", "https://example.org/callback"],
|
||||
}
|
||||
with data.client_registration.allow_host_mismatch as true
|
||||
with client_registration.allow_host_mismatch as true
|
||||
|
||||
# Redirect URI on a subdomain of the client_uri host is allowed
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://app.example.com/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_web_redirect_uri_no_custom_scheme if {
|
||||
# No custom scheme allowed
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["com.example.app:/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_web_redirect_uri_localhost_not_allowed if {
|
||||
# localhost not allowed
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://locahost:1234/callback"],
|
||||
}
|
||||
|
||||
# localhost not allowed
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://127.0.0.1:1234/callback"],
|
||||
}
|
||||
|
||||
# localhost not allowed
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "web",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://[::1]:1234/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_native_redirect_uri {
|
||||
test_native_redirect_uri_allowed if {
|
||||
# This has all the redirect URIs types we're supporting for native apps
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": [
|
||||
@@ -278,89 +302,89 @@ test_native_redirect_uri {
|
||||
"http://127.0.0.1:1234/callback",
|
||||
"http://[::1]/callback",
|
||||
"http://[::1]:1234/callback",
|
||||
"https://example.com/callback",
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
# We still allow matching URLs for native apps
|
||||
allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://example.com/"],
|
||||
}
|
||||
|
||||
test_native_redirect_uri_denied_domain if {
|
||||
# But not insecure
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://example.com/"],
|
||||
}
|
||||
|
||||
# And not a mismatch
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://bad.com/"],
|
||||
}
|
||||
}
|
||||
|
||||
test_native_redirect_uri_denied_on_localhost if {
|
||||
# We don't allow HTTPS on localhost
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["https://localhost:1234/"],
|
||||
}
|
||||
|
||||
# Ensure we're not allowing localhost as a prefix
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["http://localhost.com/"],
|
||||
}
|
||||
}
|
||||
|
||||
test_native_redirect_uri_custom_scheme if {
|
||||
# For custom schemes, it should match the client_uri hostname
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"application_type": "native",
|
||||
"client_uri": "https://example.com/",
|
||||
"redirect_uris": ["org.example.app:/callback"],
|
||||
}
|
||||
}
|
||||
|
||||
test_reverse_dns_match {
|
||||
client_uri := parse_uri("https://element.io/")
|
||||
redirect_uri := parse_uri("io.element.app:/callback")
|
||||
reverse_dns_match(client_uri.host, redirect_uri.scheme)
|
||||
test_reverse_dns_match_parse if {
|
||||
client_uri := client_registration.parse_uri("https://element.io/")
|
||||
redirect_uri := client_registration.parse_uri("io.element.app:/callback")
|
||||
client_registration.reverse_dns_match(client_uri.host, redirect_uri.scheme)
|
||||
}
|
||||
|
||||
test_client_credentials_grant {
|
||||
test_client_credentials_grant if {
|
||||
# Allowed for confidential clients
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials"],
|
||||
"token_endpoint_auth_method": "client_secret_basic",
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
allow with input.client_metadata as {
|
||||
client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials"],
|
||||
# If omitted, defaults to "client_secret_basic"
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
|
||||
# Disallowed for public clients
|
||||
not allow with input.client_metadata as {
|
||||
not client_registration.allow with input.client_metadata as {
|
||||
"grant_types": ["client_credentials"],
|
||||
"token_endpoint_auth_method": "none",
|
||||
"client_uri": "https://example.com/",
|
||||
}
|
||||
}
|
||||
|
||||
test_is_subdomain {
|
||||
is_subdomain("example.com", "example.com")
|
||||
is_subdomain("example.com", "app.example.com")
|
||||
not is_subdomain("example.com", "example.org")
|
||||
not is_subdomain("test.com", "example.com")
|
||||
test_is_subdomain if {
|
||||
client_registration.is_subdomain("example.com", "example.com")
|
||||
client_registration.is_subdomain("example.com", "app.example.com")
|
||||
not client_registration.is_subdomain("example.com", "example.org")
|
||||
not client_registration.is_subdomain("test.com", "example.com")
|
||||
}
|
||||
|
||||
test_reverse_dns_match {
|
||||
reverse_dns_match("example.com", "com.example")
|
||||
reverse_dns_match("example.com", "com.example.app")
|
||||
not reverse_dns_match("example.com", "org.example")
|
||||
not reverse_dns_match("test.com", "com.example")
|
||||
test_reverse_dns_match if {
|
||||
client_registration.reverse_dns_match("example.com", "com.example")
|
||||
client_registration.reverse_dns_match("example.com", "com.example.app")
|
||||
not client_registration.reverse_dns_match("example.com", "org.example")
|
||||
not client_registration.reverse_dns_match("test.com", "com.example")
|
||||
}
|
||||
@@ -3,32 +3,34 @@
|
||||
# - input: schema["email_input"]
|
||||
package email
|
||||
|
||||
import future.keywords.in
|
||||
import rego.v1
|
||||
|
||||
default allow := false
|
||||
|
||||
allow {
|
||||
allow if {
|
||||
count(violation) == 0
|
||||
}
|
||||
|
||||
# Allow any domains if the data.allowed_domains array is not set
|
||||
email_domain_allowed {
|
||||
domain_allowed if {
|
||||
not data.allowed_domains
|
||||
}
|
||||
|
||||
# Allow an email only if its domain is in the list of allowed domains
|
||||
email_domain_allowed {
|
||||
domain_allowed if {
|
||||
[_, domain] := split(input.email, "@")
|
||||
some allowed_domain in data.allowed_domains
|
||||
glob.match(allowed_domain, ["."], domain)
|
||||
}
|
||||
|
||||
violation[{"msg": "email domain is not allowed"}] {
|
||||
not email_domain_allowed
|
||||
# METADATA
|
||||
# entrypoint: true
|
||||
violation contains {"msg": "email domain is not allowed"} if {
|
||||
not domain_allowed
|
||||
}
|
||||
|
||||
# Deny emails with their domain in the domains banlist
|
||||
violation[{"msg": "email domain is banned"}] {
|
||||
violation contains {"msg": "email domain is banned"} if {
|
||||
[_, domain] := split(input.email, "@")
|
||||
some banned_domain in data.banned_domains
|
||||
glob.match(banned_domain, ["."], domain)
|
||||
29
policies/email/email_test.rego
Normal file
29
policies/email/email_test.rego
Normal file
@@ -0,0 +1,29 @@
|
||||
package email_test
|
||||
|
||||
import data.email
|
||||
import rego.v1
|
||||
|
||||
test_allow_all_domains if {
|
||||
email.allow with input.email as "hello@staging.element.io"
|
||||
}
|
||||
|
||||
test_allowed_domain if {
|
||||
email.allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_not_allowed_domain if {
|
||||
not email.allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["example.com"]
|
||||
}
|
||||
|
||||
test_banned_domain if {
|
||||
not email.allow with input.email as "hello@staging.element.io"
|
||||
with data.banned_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_banned_subdomain if {
|
||||
not email.allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
with data.banned_domains as ["staging.element.io"]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package email
|
||||
|
||||
test_allow_all_domains {
|
||||
allow with input.email as "hello@staging.element.io"
|
||||
}
|
||||
|
||||
test_allowed_domain {
|
||||
allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_not_allowed_domain {
|
||||
not allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["example.com"]
|
||||
}
|
||||
|
||||
test_banned_domain {
|
||||
not allow with input.email as "hello@staging.element.io"
|
||||
with data.banned_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_banned_subdomain {
|
||||
not allow with input.email as "hello@staging.element.io"
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
with data.banned_domains as ["staging.element.io"]
|
||||
}
|
||||
@@ -3,38 +3,40 @@
|
||||
# - input: schema["register_input"]
|
||||
package register
|
||||
|
||||
import data.email as email_policy
|
||||
import rego.v1
|
||||
|
||||
import future.keywords.in
|
||||
import data.email as email_policy
|
||||
|
||||
default allow := false
|
||||
|
||||
allow {
|
||||
allow if {
|
||||
count(violation) == 0
|
||||
}
|
||||
|
||||
violation[{"field": "username", "msg": "username too short"}] {
|
||||
# METADATA
|
||||
# entrypoint: true
|
||||
violation contains {"field": "username", "msg": "username too short"} if {
|
||||
count(input.username) <= 2
|
||||
}
|
||||
|
||||
violation[{"field": "username", "msg": "username too long"}] {
|
||||
violation contains {"field": "username", "msg": "username too long"} if {
|
||||
count(input.username) > 64
|
||||
}
|
||||
|
||||
violation[{"field": "username", "msg": "username contains invalid characters"}] {
|
||||
not regex.match("^[a-z0-9.=_/-]+$", input.username)
|
||||
violation contains {"field": "username", "msg": "username contains invalid characters"} if {
|
||||
not regex.match(`^[a-z0-9.=_/-]+$`, input.username)
|
||||
}
|
||||
|
||||
violation[{"msg": "unspecified registration method"}] {
|
||||
violation contains {"msg": "unspecified registration method"} if {
|
||||
not input.registration_method
|
||||
}
|
||||
|
||||
violation[{"msg": "unknown registration method"}] {
|
||||
violation contains {"msg": "unknown registration method"} if {
|
||||
not input.registration_method in ["password", "upstream-oauth2"]
|
||||
}
|
||||
|
||||
# Check that we supplied an email for password registration
|
||||
violation[{"field": "email", "msg": "email required for password-based registration"}] {
|
||||
violation contains {"field": "email", "msg": "email required for password-based registration"} if {
|
||||
input.registration_method == "password"
|
||||
|
||||
not input.email
|
||||
@@ -42,7 +44,7 @@ violation[{"field": "email", "msg": "email required for password-based registrat
|
||||
|
||||
# Check if the email is valid using the email policy
|
||||
# and add the email field to the violation object
|
||||
violation[object.union({"field": "email"}, v)] {
|
||||
violation contains object.union({"field": "email"}, v) if {
|
||||
# Check if we have an email set in the input
|
||||
input.email
|
||||
|
||||
58
policies/register/register_test.rego
Normal file
58
policies/register/register_test.rego
Normal file
@@ -0,0 +1,58 @@
|
||||
package register_test
|
||||
|
||||
import data.register
|
||||
import rego.v1
|
||||
|
||||
mock_registration := {
|
||||
"registration_method": "password",
|
||||
"username": "hello",
|
||||
"email": "hello@staging.element.io",
|
||||
}
|
||||
|
||||
test_allow_all_domains if {
|
||||
register.allow with input as mock_registration
|
||||
}
|
||||
|
||||
test_allowed_domain if {
|
||||
register.allow with input as mock_registration
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_not_allowed_domain if {
|
||||
not register.allow with input as mock_registration
|
||||
with data.allowed_domains as ["example.com"]
|
||||
}
|
||||
|
||||
test_banned_domain if {
|
||||
not register.allow with input as mock_registration
|
||||
with data.banned_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_banned_subdomain if {
|
||||
not register.allow with input as mock_registration
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
with data.banned_domains as ["staging.element.io"]
|
||||
}
|
||||
|
||||
test_email_required if {
|
||||
not register.allow with input as {"username": "hello", "registration_method": "password"}
|
||||
}
|
||||
|
||||
test_no_email if {
|
||||
register.allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
|
||||
test_short_username if {
|
||||
not register.allow with input as {"username": "a", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
|
||||
test_long_username if {
|
||||
not register.allow with input as {
|
||||
"username": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"registration_method": "upstream-oauth2",
|
||||
}
|
||||
}
|
||||
|
||||
test_invalid_username if {
|
||||
not register.allow with input as {"username": "hello world", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package register
|
||||
|
||||
mock_registration := {
|
||||
"registration_method": "password",
|
||||
"username": "hello",
|
||||
"email": "hello@staging.element.io",
|
||||
}
|
||||
|
||||
test_allow_all_domains {
|
||||
allow with input as mock_registration
|
||||
}
|
||||
|
||||
test_allowed_domain {
|
||||
allow with input as mock_registration
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_not_allowed_domain {
|
||||
not allow with input as mock_registration
|
||||
with data.allowed_domains as ["example.com"]
|
||||
}
|
||||
|
||||
test_banned_domain {
|
||||
not allow with input as mock_registration
|
||||
with data.banned_domains as ["*.element.io"]
|
||||
}
|
||||
|
||||
test_banned_subdomain {
|
||||
not allow with input as mock_registration
|
||||
with data.allowed_domains as ["*.element.io"]
|
||||
with data.banned_domains as ["staging.element.io"]
|
||||
}
|
||||
|
||||
test_email_required {
|
||||
not allow with input as {"username": "hello", "registration_method": "password"}
|
||||
}
|
||||
|
||||
test_no_email {
|
||||
allow with input as {"username": "hello", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
|
||||
test_short_username {
|
||||
not allow with input as {"username": "a", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
|
||||
test_long_username {
|
||||
not allow with input as {"username": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
|
||||
test_invalid_username {
|
||||
not allow with input as {"username": "hello world", "registration_method": "upstream-oauth2"}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package coveralls
|
||||
package util
|
||||
|
||||
import future.keywords
|
||||
import rego.v1
|
||||
|
||||
from_opa := {"source_files": coverage}
|
||||
|
||||
@@ -9,7 +9,7 @@ coverage contains obj if {
|
||||
obj := {"name": file, "coverage": to_lines(report)}
|
||||
}
|
||||
|
||||
covered_map(report) = cm if {
|
||||
covered_map(report) := cm if {
|
||||
covered := object.get(report, "covered", [])
|
||||
cm := {line: 1 |
|
||||
some item in covered
|
||||
@@ -17,7 +17,7 @@ covered_map(report) = cm if {
|
||||
}
|
||||
}
|
||||
|
||||
not_covered_map(report) = ncm if {
|
||||
not_covered_map(report) := ncm if {
|
||||
not_covered := object.get(report, "not_covered", [])
|
||||
ncm := {line: 0 |
|
||||
some item in not_covered
|
||||
@@ -25,7 +25,7 @@ not_covered_map(report) = ncm if {
|
||||
}
|
||||
}
|
||||
|
||||
to_lines(report) = lines if {
|
||||
to_lines(report) := lines if {
|
||||
cm := covered_map(report)
|
||||
ncm := not_covered_map(report)
|
||||
keys := sort([line | some line, _ in object.union(cm, ncm)])
|
||||
@@ -37,15 +37,15 @@ to_lines(report) = lines if {
|
||||
]
|
||||
}
|
||||
|
||||
to_value(cm, _, line) = 1 if {
|
||||
to_value(cm, _, line) := 1 if {
|
||||
cm[line]
|
||||
}
|
||||
|
||||
to_value(_, ncm, line) = 0 if {
|
||||
to_value(_, ncm, line) := 0 if {
|
||||
ncm[line]
|
||||
}
|
||||
|
||||
to_value(cm, ncm, line) = null if {
|
||||
to_value(cm, ncm, line) := null if {
|
||||
not cm[line]
|
||||
not ncm[line]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user