1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-09 04:22:45 +03:00

Remove OPA-based password policy enforcement (#2875)

Co-authored-by: Quentin Gliech <quenting@element.io>
This commit is contained in:
reivilibre
2024-07-16 14:33:04 +01:00
committed by GitHub
parent e47f26fde6
commit 1afd2a2906
13 changed files with 9 additions and 168 deletions

View File

@@ -112,7 +112,6 @@ pub async fn policy_factory_from_config(
client_registration: config.client_registration_entrypoint.clone(),
authorization_grant: config.authorization_grant_entrypoint.clone(),
email: config.email_entrypoint.clone(),
password: config.password_entrypoint.clone(),
};
PolicyFactory::load(policy_file, config.data.clone(), entrypoints)

View File

@@ -546,11 +546,7 @@ impl UserMutations {
return Err(async_graphql::Error::new("Unauthorized"));
}
let mut policy = state.policy().await?;
let res = policy.evaluate_password(&input.new_password).await?;
if !res.valid() {
if input.new_password.is_empty() {
// TODO Expose the reason for the policy violation
// This involves redesigning the error handling
// Idea would be to expose an errors array in the response,

View File

@@ -85,7 +85,6 @@ pub(crate) async fn policy_factory(
client_registration: "client_registration/violation".to_owned(),
authorization_grant: "authorization_grant/violation".to_owned(),
email: "email/violation".to_owned(),
password: "password/violation".to_owned(),
};
let policy_factory = PolicyFactory::load(file, data, entrypoints).await?;

View File

@@ -24,7 +24,6 @@ use mas_axum_utils::{
FancyError,
};
use mas_data_model::SiteConfig;
use mas_policy::Policy;
use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRepository, BoxRng};
use mas_templates::{
@@ -129,7 +128,6 @@ pub(crate) async fn post(
mut rng: BoxRng,
clock: BoxClock,
mut repo: BoxRepository,
mut policy: Policy,
State(site_config): State<SiteConfig>,
State(password_manager): State<PasswordManager>,
State(templates): State<Templates>,
@@ -238,17 +236,6 @@ pub(crate) async fn post(
);
}
let res = policy.evaluate_password(&form.new_password).await?;
if !res.valid() {
form_state = form_state.with_error_on_field(
RecoveryFinishFormField::NewPassword,
FieldError::Policy {
message: res.to_string(),
},
);
}
if !form_state.is_valid() {
let context = RecoveryFinishContext::new(user)
.with_form_state(form_state)

View File

@@ -214,7 +214,7 @@ pub(crate) async fn post(
}
let res = policy
.evaluate_register(&form.username, &form.password, &form.email)
.evaluate_register(&form.username, &form.email)
.await?;
for violation in res.violations {

View File

@@ -23,9 +23,7 @@ use opa_wasm::{
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt};
use self::model::{
AuthorizationGrantInput, ClientRegistrationInput, EmailInput, PasswordInput, RegisterInput,
};
use self::model::{AuthorizationGrantInput, ClientRegistrationInput, EmailInput, RegisterInput};
pub use self::model::{EvaluationResult, Violation};
use crate::model::GrantType;
@@ -66,17 +64,15 @@ pub struct Entrypoints {
pub client_registration: String,
pub authorization_grant: String,
pub email: String,
pub password: String,
}
impl Entrypoints {
fn all(&self) -> [&str; 5] {
fn all(&self) -> [&str; 4] {
[
self.register.as_str(),
self.client_registration.as_str(),
self.authorization_grant.as_str(),
self.email.as_str(),
self.password.as_str(),
]
}
}
@@ -195,21 +191,6 @@ impl Policy {
Ok(res)
}
#[tracing::instrument(name = "policy.evaluate_password", skip_all, err)]
pub async fn evaluate_password(
&mut self,
password: &str,
) -> Result<EvaluationResult, EvaluationError> {
let input = PasswordInput { password };
let [res]: [EvaluationResult; 1] = self
.instance
.evaluate(&mut self.store, &self.entrypoints.password, &input)
.await?;
Ok(res)
}
#[tracing::instrument(
name = "policy.evaluate.register",
skip_all,
@@ -223,14 +204,9 @@ impl Policy {
pub async fn evaluate_register(
&mut self,
username: &str,
password: &str,
email: &str,
) -> Result<EvaluationResult, EvaluationError> {
let input = RegisterInput::Password {
username,
password,
email,
};
let input = RegisterInput::Password { username, email };
let [res]: [EvaluationResult; 1] = self
.instance
@@ -415,7 +391,6 @@ mod tests {
client_registration: "client_registration/violation".to_owned(),
authorization_grant: "authorization_grant/violation".to_owned(),
email: "email/violation".to_owned(),
password: "password/violation".to_owned(),
};
let factory = PolicyFactory::load(file, data, entrypoints).await.unwrap();
@@ -423,19 +398,19 @@ mod tests {
let mut policy = factory.instantiate().await.unwrap();
let res = policy
.evaluate_register("hello", "hunter2", "hello@example.com")
.evaluate_register("hello", "hello@example.com")
.await
.unwrap();
assert!(!res.valid());
let res = policy
.evaluate_register("hello", "hunter2", "hello@foo.element.io")
.evaluate_register("hello", "hello@foo.element.io")
.await
.unwrap();
assert!(res.valid());
let res = policy
.evaluate_register("hello", "hunter2", "hello@staging.element.io")
.evaluate_register("hello", "hello@staging.element.io")
.await
.unwrap();
assert!(!res.valid());

View File

@@ -65,11 +65,7 @@ impl EvaluationResult {
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum RegisterInput<'a> {
#[serde(rename = "password")]
Password {
username: &'a str,
password: &'a str,
email: &'a str,
},
Password { username: &'a str, email: &'a str },
#[serde(rename = "upstream-oauth2")]
UpstreamOAuth2 {

View File

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

View File

@@ -1,30 +0,0 @@
# METADATA
# schemas:
# - input: schema["password_input"]
package password
default allow := false
allow {
count(violation) == 0
}
violation[{"msg": msg}] {
count(input.password) < data.passwords.min_length
msg := sprintf("needs to be at least %d characters", [data.passwords.min_length])
}
violation[{"msg": "requires at least one number"}] {
data.passwords.require_number
not regex.match("[0-9]", input.password)
}
violation[{"msg": "requires at least one lowercase letter"}] {
data.passwords.require_lowercase
not regex.match("[a-z]", input.password)
}
violation[{"msg": "requires at least one uppercase letter"}] {
data.passwords.require_uppercase
not regex.match("[A-Z]", input.password)
}

View File

@@ -1,29 +0,0 @@
package password
test_password_require_number {
allow with data.passwords.require_number as true
not allow with input.password as "hunter"
with data.passwords.require_number as true
}
test_password_require_lowercase {
allow with data.passwords.require_lowercase as true
not allow with input.password as "HUNTER2"
with data.passwords.require_lowercase as true
}
test_password_require_uppercase {
allow with data.passwords.require_uppercase as true
not allow with input.password as "hunter2"
with data.passwords.require_uppercase as true
}
test_password_min_length {
allow with data.passwords.min_length as 6
not allow with input.password as "short"
with data.passwords.min_length as 6
}

View File

@@ -4,7 +4,6 @@
package register
import data.email as email_policy
import data.password as password_policy
import future.keywords.in
@@ -34,14 +33,6 @@ violation[{"msg": "unknown registration method"}] {
not input.registration_method in ["password", "upstream-oauth2"]
}
violation[object.union({"field": "password"}, v)] {
# Check if the registration method is password
input.registration_method == "password"
# Get the violation object from the password policy
some v in password_policy.violation
}
# Check that we supplied an email for password registration
violation[{"field": "email", "msg": "email required for password-based registration"}] {
input.registration_method == "password"

View File

@@ -3,7 +3,6 @@ package register
mock_registration := {
"registration_method": "password",
"username": "hello",
"password": "Hunter2",
"email": "hello@staging.element.io",
}
@@ -51,39 +50,3 @@ test_long_username {
test_invalid_username {
not allow with input as {"username": "hello world", "registration_method": "upstream-oauth2"}
}
test_password_require_number {
allow with input as mock_registration
with data.passwords.require_number as true
not allow with input as mock_registration
with input.password as "hunter"
with data.passwords.require_number as true
}
test_password_require_lowercase {
allow with input as mock_registration
with data.passwords.require_lowercase as true
not allow with input as mock_registration
with input.password as "HUNTER2"
with data.passwords.require_lowercase as true
}
test_password_require_uppercase {
allow with input as mock_registration
with data.passwords.require_uppercase as true
not allow with input as mock_registration
with input.password as "hunter2"
with data.passwords.require_uppercase as true
}
test_password_min_length {
allow with input as mock_registration
with data.passwords.min_length as 6
not allow with input as mock_registration
with input.password as "short"
with data.passwords.min_length as 6
}

View File

@@ -7,7 +7,6 @@
"type": "object",
"required": [
"email",
"password",
"registration_method",
"username"
],
@@ -21,9 +20,6 @@
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"email": {
"type": "string"
}