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(), client_registration: config.client_registration_entrypoint.clone(),
authorization_grant: config.authorization_grant_entrypoint.clone(), authorization_grant: config.authorization_grant_entrypoint.clone(),
email: config.email_entrypoint.clone(), email: config.email_entrypoint.clone(),
password: config.password_entrypoint.clone(),
}; };
PolicyFactory::load(policy_file, config.data.clone(), entrypoints) PolicyFactory::load(policy_file, config.data.clone(), entrypoints)

View File

@@ -546,11 +546,7 @@ impl UserMutations {
return Err(async_graphql::Error::new("Unauthorized")); return Err(async_graphql::Error::new("Unauthorized"));
} }
let mut policy = state.policy().await?; if input.new_password.is_empty() {
let res = policy.evaluate_password(&input.new_password).await?;
if !res.valid() {
// TODO Expose the reason for the policy violation // TODO Expose the reason for the policy violation
// This involves redesigning the error handling // This involves redesigning the error handling
// Idea would be to expose an errors array in the response, // 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(), client_registration: "client_registration/violation".to_owned(),
authorization_grant: "authorization_grant/violation".to_owned(), authorization_grant: "authorization_grant/violation".to_owned(),
email: "email/violation".to_owned(), email: "email/violation".to_owned(),
password: "password/violation".to_owned(),
}; };
let policy_factory = PolicyFactory::load(file, data, entrypoints).await?; let policy_factory = PolicyFactory::load(file, data, entrypoints).await?;

View File

@@ -24,7 +24,6 @@ use mas_axum_utils::{
FancyError, FancyError,
}; };
use mas_data_model::SiteConfig; use mas_data_model::SiteConfig;
use mas_policy::Policy;
use mas_router::UrlBuilder; use mas_router::UrlBuilder;
use mas_storage::{BoxClock, BoxRepository, BoxRng}; use mas_storage::{BoxClock, BoxRepository, BoxRng};
use mas_templates::{ use mas_templates::{
@@ -129,7 +128,6 @@ pub(crate) async fn post(
mut rng: BoxRng, mut rng: BoxRng,
clock: BoxClock, clock: BoxClock,
mut repo: BoxRepository, mut repo: BoxRepository,
mut policy: Policy,
State(site_config): State<SiteConfig>, State(site_config): State<SiteConfig>,
State(password_manager): State<PasswordManager>, State(password_manager): State<PasswordManager>,
State(templates): State<Templates>, 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() { if !form_state.is_valid() {
let context = RecoveryFinishContext::new(user) let context = RecoveryFinishContext::new(user)
.with_form_state(form_state) .with_form_state(form_state)

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,6 @@ INPUTS := \
client_registration.rego \ client_registration.rego \
register.rego \ register.rego \
authorization_grant.rego \ authorization_grant.rego \
password.rego \
email.rego email.rego
ifeq ($(DOCKER), 1) ifeq ($(DOCKER), 1)
@@ -27,7 +26,6 @@ policy.wasm: $(INPUTS)
-e "client_registration/violation" \ -e "client_registration/violation" \
-e "register/violation" \ -e "register/violation" \
-e "authorization_grant/violation" \ -e "authorization_grant/violation" \
-e "password/violation" \
-e "email/violation" \ -e "email/violation" \
$^ $^
tar xzf bundle.tar.gz /policy.wasm 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 package register
import data.email as email_policy import data.email as email_policy
import data.password as password_policy
import future.keywords.in import future.keywords.in
@@ -34,14 +33,6 @@ violation[{"msg": "unknown registration method"}] {
not input.registration_method in ["password", "upstream-oauth2"] 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 # Check that we supplied an email for password registration
violation[{"field": "email", "msg": "email required for password-based registration"}] { violation[{"field": "email", "msg": "email required for password-based registration"}] {
input.registration_method == "password" input.registration_method == "password"

View File

@@ -3,7 +3,6 @@ package register
mock_registration := { mock_registration := {
"registration_method": "password", "registration_method": "password",
"username": "hello", "username": "hello",
"password": "Hunter2",
"email": "hello@staging.element.io", "email": "hello@staging.element.io",
} }
@@ -51,39 +50,3 @@ test_long_username {
test_invalid_username { test_invalid_username {
not allow with input as {"username": "hello world", "registration_method": "upstream-oauth2"} 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", "type": "object",
"required": [ "required": [
"email", "email",
"password",
"registration_method", "registration_method",
"username" "username"
], ],
@@ -21,9 +20,6 @@
"username": { "username": {
"type": "string" "type": "string"
}, },
"password": {
"type": "string"
},
"email": { "email": {
"type": "string" "type": "string"
} }