mirror of
https://github.com/lldap/lldap.git
synced 2024-10-23 20:29:50 +00:00
server: enforce email and uuid unicity
This commit is contained in:
parent
d1f1eb8e80
commit
4e85a4718f
3 changed files with 131 additions and 22 deletions
|
@ -86,7 +86,7 @@ pub mod tests {
|
|||
handler
|
||||
.create_user(CreateUserRequest {
|
||||
user_id: UserId::new(name),
|
||||
email: "bob@bob.bob".to_string(),
|
||||
email: format!("{}@bob.bob", name),
|
||||
display_name: Some("display ".to_string() + name),
|
||||
first_name: Some("first ".to_string() + name),
|
||||
last_name: Some("last ".to_string() + name),
|
||||
|
|
|
@ -2,15 +2,14 @@ use crate::domain::{
|
|||
sql_tables::{DbConnection, SchemaVersion},
|
||||
types::{GroupId, UserId, Uuid},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use sea_orm::{
|
||||
sea_query::{self, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Query, Table, Value},
|
||||
sea_query::{self, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Index, Query, Table, Value},
|
||||
ConnectionTrait, FromQueryResult, Iden, Statement, TransactionTrait,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use super::sql_tables::LAST_SCHEMA_VERSION;
|
||||
|
||||
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum Users {
|
||||
Table,
|
||||
|
@ -460,30 +459,81 @@ async fn migrate_to_v3(pool: &DbConnection) -> anyhow::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_to_v4(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
let builder = pool.get_database_backend();
|
||||
// Make emails and UUIDs unique.
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.name("unique-user-email")
|
||||
.table(Users::Table)
|
||||
.col(Users::Email)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("while enforcing unicity on emails (2 users have the same email)")?;
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.name("unique-user-uuid")
|
||||
.table(Users::Table)
|
||||
.col(Users::Uuid)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("while enforcing unicity on user UUIDs (2 users have the same UUID)")?;
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.name("unique-group-uuid")
|
||||
.table(Groups::Table)
|
||||
.col(Groups::Uuid)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("while enforcing unicity on group UUIDs (2 groups have the same UUID)")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is needed to make an array of async functions.
|
||||
macro_rules! to_sync {
|
||||
($l:ident) => {
|
||||
|pool| -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>>>> {
|
||||
Box::pin($l(pool))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn migrate_from_version(
|
||||
pool: &DbConnection,
|
||||
version: SchemaVersion,
|
||||
last_version: SchemaVersion,
|
||||
) -> anyhow::Result<()> {
|
||||
match version.cmp(&LAST_SCHEMA_VERSION) {
|
||||
std::cmp::Ordering::Less => info!(
|
||||
"Upgrading DB schema from {} to {}",
|
||||
version.0, LAST_SCHEMA_VERSION.0
|
||||
),
|
||||
match version.cmp(&last_version) {
|
||||
std::cmp::Ordering::Less => (),
|
||||
std::cmp::Ordering::Equal => return Ok(()),
|
||||
std::cmp::Ordering::Greater => anyhow::bail!("DB version downgrading is not supported"),
|
||||
}
|
||||
if version < SchemaVersion(2) {
|
||||
migrate_to_v2(pool).await?;
|
||||
}
|
||||
if version < SchemaVersion(3) {
|
||||
migrate_to_v3(pool).await?;
|
||||
let migrations = [
|
||||
to_sync!(migrate_to_v2),
|
||||
to_sync!(migrate_to_v3),
|
||||
to_sync!(migrate_to_v4),
|
||||
];
|
||||
for migration in 2..=4 {
|
||||
if version < SchemaVersion(migration) && SchemaVersion(migration) <= last_version {
|
||||
info!("Upgrading DB schema from {} to {}", version.0, migration);
|
||||
migrations[(migration - 2) as usize](pool).await?;
|
||||
}
|
||||
}
|
||||
let builder = pool.get_database_backend();
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Query::update()
|
||||
.table(Metadata::Table)
|
||||
.value(Metadata::Version, Value::from(LAST_SCHEMA_VERSION)),
|
||||
.value(Metadata::Version, Value::from(last_version)),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -21,7 +21,7 @@ impl From<SchemaVersion> for Value {
|
|||
}
|
||||
}
|
||||
|
||||
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(3);
|
||||
const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(4);
|
||||
|
||||
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
let version = {
|
||||
|
@ -32,7 +32,7 @@ pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
|||
SchemaVersion(1)
|
||||
}
|
||||
};
|
||||
migrate_from_version(pool, version).await?;
|
||||
migrate_from_version(pool, version, LAST_SCHEMA_VERSION).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -100,21 +100,21 @@ mod tests {
|
|||
let sql_pool = get_in_memory_db().await;
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, first_name TEXT NOT NULL, last_name TEXT, avatar BLOB, creation_date TEXT);"#,
|
||||
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, first_name TEXT NOT NULL, last_name TEXT, avatar BLOB, creation_date TEXT, email TEXT);"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||
VALUES ("bôb", "", "", "1970-01-01 00:00:00")"#,
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date, email)
|
||||
VALUES ("bôb", "", "", "1970-01-01 00:00:00", "bob@bob.com")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||
VALUES ("john", "John Doe", "John", "1971-01-01 00:00:00")"#,
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date, email)
|
||||
VALUES ("john", "John Doe", "John", "1971-01-01 00:00:00", "bob2@bob.com")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -206,6 +206,65 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_migration_to_v4() {
|
||||
let sql_pool = get_in_memory_db().await;
|
||||
upgrade_to_v1(&sql_pool).await.unwrap();
|
||||
migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(3))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, email, display_name, first_name, creation_date, uuid)
|
||||
VALUES ("bob", "bob@bob.com", "", "", "1970-01-01 00:00:00", "a02eaf13-48a7-30f6-a3d4-040ff7c52b04")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, email, display_name, first_name, creation_date, uuid)
|
||||
VALUES ("bob2", "bob@bob.com", "", "", "1970-01-01 00:00:00", "986765a5-3f03-389e-b47b-536b2d6e1bec")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
migrate_from_version(&sql_pool, SchemaVersion(3), SchemaVersion(4))
|
||||
.await
|
||||
.expect_err("migration should fail");
|
||||
assert_eq!(
|
||||
sql_migrations::JustSchemaVersion::find_by_statement(raw_statement(
|
||||
r#"SELECT version FROM metadata"#
|
||||
))
|
||||
.one(&sql_pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
sql_migrations::JustSchemaVersion {
|
||||
version: SchemaVersion(3)
|
||||
}
|
||||
);
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"UPDATE users SET email = "new@bob.com" WHERE user_id = "bob2""#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
migrate_from_version(&sql_pool, SchemaVersion(3), SchemaVersion(4))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
sql_migrations::JustSchemaVersion::find_by_statement(raw_statement(
|
||||
r#"SELECT version FROM metadata"#
|
||||
))
|
||||
.one(&sql_pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
sql_migrations::JustSchemaVersion {
|
||||
version: SchemaVersion(4)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_too_high_version() {
|
||||
let sql_pool = get_in_memory_db().await;
|
||||
|
|
Loading…
Reference in a new issue