From e458aca3e39048e057763ed2fe32ed2fffb978ca Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Fri, 13 Jan 2023 15:09:25 +0100 Subject: [PATCH] db: Change the DB storage type to NaiveDateTime The entire internals of the server now work using only NaiveDateTime, since we know they are all UTC. At the fringes (LDAP, GraphQL, JWT tokens) we convert back into UTC to make sure we have a clear API. This allows us to be compatible with Postgres (which doesn't support DateTime, only NaiveDateTime). This change is backwards compatible since in SQlite with Sea-query/Sea-ORM, the UTC datetimes are stored without a timezone, as simple strings. It's the same format as NaiveDateTime. Fixes #87. --- server/src/domain/handler.rs | 10 ++++++-- server/src/domain/ldap/user.rs | 6 ++++- server/src/domain/model/groups.rs | 2 +- .../src/domain/model/jwt_refresh_storage.rs | 2 +- server/src/domain/model/jwt_storage.rs | 2 +- .../src/domain/model/password_reset_tokens.rs | 2 +- server/src/domain/model/users.rs | 2 +- .../src/domain/sql_group_backend_handler.rs | 2 +- server/src/domain/sql_migrations.rs | 4 +-- server/src/domain/sql_tables.rs | 4 +-- server/src/domain/sql_user_backend_handler.rs | 2 +- server/src/domain/types.rs | 23 ++++++++++------- server/src/infra/graphql/query.rs | 11 ++++---- server/src/infra/ldap_handler.rs | 25 +++++++++++-------- server/src/infra/sql_backend_handler.rs | 4 +-- 15 files changed, 60 insertions(+), 41 deletions(-) diff --git a/server/src/domain/handler.rs b/server/src/domain/handler.rs index ef43e93..a39c256 100644 --- a/server/src/domain/handler.rs +++ b/server/src/domain/handler.rs @@ -140,8 +140,14 @@ mod tests { fn test_uuid_time() { use chrono::prelude::*; let user_id = "bob"; - let date1 = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(); - let date2 = Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 12).unwrap(); + let date1 = Utc + .with_ymd_and_hms(2014, 7, 8, 9, 10, 11) + .unwrap() + .naive_utc(); + let date2 = Utc + .with_ymd_and_hms(2014, 7, 8, 9, 10, 12) + .unwrap() + .naive_utc(); assert_ne!( Uuid::from_name_and_date(user_id, &date1), Uuid::from_name_and_date(user_id, &date2) diff --git a/server/src/domain/ldap/user.rs b/server/src/domain/ldap/user.rs index 08f9853..6903aa4 100644 --- a/server/src/domain/ldap/user.rs +++ b/server/src/domain/ldap/user.rs @@ -1,3 +1,4 @@ +use chrono::TimeZone; use ldap3_proto::{ proto::LdapOp, LdapFilter, LdapPartialAttribute, LdapResultCode, LdapSearchResultEntry, }; @@ -49,7 +50,10 @@ fn get_user_attribute( }) .collect(), "cn" | "displayname" => vec![user.display_name.clone()?.into_bytes()], - "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339().into_bytes()], + "createtimestamp" | "modifytimestamp" => vec![chrono::Utc + .from_utc_datetime(&user.creation_date) + .to_rfc3339() + .into_bytes()], "1.1" => return None, // We ignore the operational attribute wildcard. "+" => return None, diff --git a/server/src/domain/model/groups.rs b/server/src/domain/model/groups.rs index 748a61e..d9a74c8 100644 --- a/server/src/domain/model/groups.rs +++ b/server/src/domain/model/groups.rs @@ -11,7 +11,7 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub group_id: GroupId, pub display_name: String, - pub creation_date: chrono::DateTime, + pub creation_date: chrono::NaiveDateTime, pub uuid: Uuid, } diff --git a/server/src/domain/model/jwt_refresh_storage.rs b/server/src/domain/model/jwt_refresh_storage.rs index d7753ff..ebca1b1 100644 --- a/server/src/domain/model/jwt_refresh_storage.rs +++ b/server/src/domain/model/jwt_refresh_storage.rs @@ -11,7 +11,7 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub refresh_token_hash: i64, pub user_id: UserId, - pub expiry_date: chrono::DateTime, + pub expiry_date: chrono::NaiveDateTime, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/server/src/domain/model/jwt_storage.rs b/server/src/domain/model/jwt_storage.rs index 6fc6a4e..6ca9208 100644 --- a/server/src/domain/model/jwt_storage.rs +++ b/server/src/domain/model/jwt_storage.rs @@ -11,7 +11,7 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub jwt_hash: i64, pub user_id: UserId, - pub expiry_date: chrono::DateTime, + pub expiry_date: chrono::NaiveDateTime, pub blacklisted: bool, } diff --git a/server/src/domain/model/password_reset_tokens.rs b/server/src/domain/model/password_reset_tokens.rs index 54b1bea..a252b36 100644 --- a/server/src/domain/model/password_reset_tokens.rs +++ b/server/src/domain/model/password_reset_tokens.rs @@ -11,7 +11,7 @@ pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub token: String, pub user_id: UserId, - pub expiry_date: chrono::DateTime, + pub expiry_date: chrono::NaiveDateTime, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/server/src/domain/model/users.rs b/server/src/domain/model/users.rs index a9f1b02..32f8d86 100644 --- a/server/src/domain/model/users.rs +++ b/server/src/domain/model/users.rs @@ -18,7 +18,7 @@ pub struct Model { pub first_name: Option, pub last_name: Option, pub avatar: Option, - pub creation_date: chrono::DateTime, + pub creation_date: chrono::NaiveDateTime, pub password_hash: Option>, pub totp_secret: Option, pub mfa_type: Option, diff --git a/server/src/domain/sql_group_backend_handler.rs b/server/src/domain/sql_group_backend_handler.rs index aaca7fe..5367090 100644 --- a/server/src/domain/sql_group_backend_handler.rs +++ b/server/src/domain/sql_group_backend_handler.rs @@ -116,7 +116,7 @@ impl GroupBackendHandler for SqlBackendHandler { #[instrument(skip_all, level = "debug", ret, err)] async fn create_group(&self, group_name: &str) -> Result { debug!(?group_name); - let now = chrono::Utc::now(); + let now = chrono::Utc::now().naive_utc(); let uuid = Uuid::from_name_and_date(group_name, &now); let new_group = model::groups::ActiveModel { display_name: ActiveValue::Set(group_name.to_owned()), diff --git a/server/src/domain/sql_migrations.rs b/server/src/domain/sql_migrations.rs index ff5bb21..7efb152 100644 --- a/server/src/domain/sql_migrations.rs +++ b/server/src/domain/sql_migrations.rs @@ -170,7 +170,7 @@ pub async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_o struct ShortGroupDetails { group_id: GroupId, display_name: String, - creation_date: chrono::DateTime, + creation_date: chrono::NaiveDateTime, } for result in ShortGroupDetails::find_by_statement( builder.build( @@ -220,7 +220,7 @@ pub async fn upgrade_to_v1(pool: &DbConnection) -> std::result::Result<(), sea_o #[derive(FromQueryResult)] struct ShortUserDetails { user_id: UserId, - creation_date: chrono::DateTime, + creation_date: chrono::NaiveDateTime, } for result in ShortUserDetails::find_by_statement( builder.build( diff --git a/server/src/domain/sql_tables.rs b/server/src/domain/sql_tables.rs index 1bc4f77..0f202b0 100644 --- a/server/src/domain/sql_tables.rs +++ b/server/src/domain/sql_tables.rs @@ -67,7 +67,7 @@ mod tests { #[derive(FromQueryResult, PartialEq, Eq, Debug)] struct ShortUserDetails { display_name: String, - creation_date: chrono::DateTime, + creation_date: chrono::NaiveDateTime, } let result = ShortUserDetails::find_by_statement(raw_statement( r#"SELECT display_name, creation_date FROM users WHERE user_id = "bôb""#, @@ -80,7 +80,7 @@ mod tests { result, ShortUserDetails { display_name: "Bob Bobbersön".to_owned(), - creation_date: Utc.timestamp_opt(0, 0).unwrap() + creation_date: Utc.timestamp_opt(0, 0).unwrap().naive_utc(), } ); } diff --git a/server/src/domain/sql_user_backend_handler.rs b/server/src/domain/sql_user_backend_handler.rs index dc7b99e..9220dff 100644 --- a/server/src/domain/sql_user_backend_handler.rs +++ b/server/src/domain/sql_user_backend_handler.rs @@ -158,7 +158,7 @@ impl UserBackendHandler for SqlBackendHandler { #[instrument(skip_all, level = "debug", err)] async fn create_user(&self, request: CreateUserRequest) -> Result<()> { debug!(user_id = ?request.user_id); - let now = chrono::Utc::now(); + let now = chrono::Utc::now().naive_utc(); let uuid = Uuid::from_name_and_date(request.user_id.as_str(), &now); let new_user = model::users::ActiveModel { user_id: Set(request.user_id), diff --git a/server/src/domain/types.rs b/server/src/domain/types.rs index 76673d8..494f8f9 100644 --- a/server/src/domain/types.rs +++ b/server/src/domain/types.rs @@ -1,3 +1,4 @@ +use chrono::{NaiveDateTime, TimeZone}; use sea_orm::{ entity::IntoActiveValue, sea_query::{value::ValueType, ArrayType, ColumnType, Nullable, ValueTypeErr}, @@ -7,18 +8,23 @@ use serde::{Deserialize, Serialize}; pub use super::model::{GroupColumn, UserColumn}; -pub type DateTime = chrono::DateTime; - #[derive(PartialEq, Hash, Eq, Clone, Debug, Default, Serialize, Deserialize)] #[serde(try_from = "&str")] pub struct Uuid(String); impl Uuid { - pub fn from_name_and_date(name: &str, creation_date: &DateTime) -> Self { + pub fn from_name_and_date(name: &str, creation_date: &NaiveDateTime) -> Self { Uuid( uuid::Uuid::new_v3( &uuid::Uuid::NAMESPACE_X500, - &[name.as_bytes(), creation_date.to_rfc3339().as_bytes()].concat(), + &[ + name.as_bytes(), + chrono::Utc + .from_utc_datetime(creation_date) + .to_rfc3339() + .as_bytes(), + ] + .concat(), ) .to_string(), ) @@ -308,15 +314,14 @@ pub struct User { pub first_name: Option, pub last_name: Option, pub avatar: Option, - pub creation_date: DateTime, + pub creation_date: NaiveDateTime, pub uuid: Uuid, } #[cfg(test)] impl Default for User { fn default() -> Self { - use chrono::TimeZone; - let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap(); + let epoch = chrono::Utc.timestamp_opt(0, 0).unwrap().naive_utc(); User { user_id: UserId::default(), email: String::new(), @@ -373,7 +378,7 @@ impl TryFromU64 for GroupId { pub struct Group { pub id: GroupId, pub display_name: String, - pub creation_date: DateTime, + pub creation_date: NaiveDateTime, pub uuid: Uuid, pub users: Vec, } @@ -382,7 +387,7 @@ pub struct Group { pub struct GroupDetails { pub group_id: GroupId, pub display_name: String, - pub creation_date: DateTime, + pub creation_date: NaiveDateTime, pub uuid: Uuid, } diff --git a/server/src/infra/graphql/query.rs b/server/src/infra/graphql/query.rs index 03091f6..7c97050 100644 --- a/server/src/infra/graphql/query.rs +++ b/server/src/infra/graphql/query.rs @@ -3,6 +3,7 @@ use crate::domain::{ ldap::utils::map_user_field, types::{GroupDetails, GroupId, UserColumn, UserId}, }; +use chrono::TimeZone; use juniper::{graphql_object, FieldResult, GraphQLInputObject}; use serde::{Deserialize, Serialize}; use tracing::{debug, debug_span, Instrument}; @@ -230,7 +231,7 @@ impl User { } fn creation_date(&self) -> chrono::DateTime { - self.user.creation_date + chrono::Utc.from_utc_datetime(&self.user.creation_date) } fn uuid(&self) -> &str { @@ -275,7 +276,7 @@ impl From for User { pub struct Group { group_id: i32, display_name: String, - creation_date: chrono::DateTime, + creation_date: chrono::NaiveDateTime, uuid: String, members: Option>, _phantom: std::marker::PhantomData>, @@ -290,7 +291,7 @@ impl Group { self.display_name.clone() } fn creation_date(&self) -> chrono::DateTime { - self.creation_date + chrono::Utc.from_utc_datetime(&self.creation_date) } fn uuid(&self) -> String { self.uuid.clone() @@ -389,7 +390,7 @@ mod tests { Ok(DomainUser { user_id: UserId::new("bob"), email: "bob@bobbers.on".to_string(), - creation_date: chrono::Utc.timestamp_millis_opt(42).unwrap(), + creation_date: chrono::Utc.timestamp_millis_opt(42).unwrap().naive_utc(), uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), ..Default::default() }) @@ -398,7 +399,7 @@ mod tests { groups.insert(GroupDetails { group_id: GroupId(3), display_name: "Bobbersons".to_string(), - creation_date: chrono::Utc.timestamp_nanos(42), + creation_date: chrono::Utc.timestamp_nanos(42).naive_utc(), uuid: crate::uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), }); mock.expect_get_user_groups() diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs index 790d2e2..8ae3e1c 100644 --- a/server/src/infra/ldap_handler.rs +++ b/server/src/infra/ldap_handler.rs @@ -667,7 +667,7 @@ mod tests { set.insert(GroupDetails { group_id: GroupId(42), display_name: group, - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), }); Ok(set) @@ -754,7 +754,7 @@ mod tests { set.insert(GroupDetails { group_id: GroupId(42), display_name: "lldap_admin".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), }); Ok(set) @@ -841,7 +841,7 @@ mod tests { groups: Some(vec![GroupDetails { group_id: GroupId(42), display_name: "rockstars".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), }]), }]) @@ -1006,7 +1006,10 @@ mod tests { last_name: Some("Cricket".to_string()), avatar: Some(JpegPhoto::for_tests()), uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), - creation_date: Utc.with_ymd_and_hms(2014, 7, 8, 9, 10, 11).unwrap(), + creation_date: Utc + .with_ymd_and_hms(2014, 7, 8, 9, 10, 11) + .unwrap() + .naive_utc(), }, groups: None, }, @@ -1135,14 +1138,14 @@ mod tests { Group { id: GroupId(1), display_name: "group_1".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![UserId::new("bob"), UserId::new("john")], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }, Group { id: GroupId(3), display_name: "BestGroup".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![UserId::new("john")], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }, @@ -1228,7 +1231,7 @@ mod tests { Ok(vec![Group { display_name: "group_1".to_string(), id: GroupId(1), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }]) @@ -1279,7 +1282,7 @@ mod tests { Ok(vec![Group { display_name: "group_1".to_string(), id: GroupId(1), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }]) @@ -1555,7 +1558,7 @@ mod tests { Ok(vec![Group { id: GroupId(1), display_name: "group_1".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![UserId::new("bob"), UserId::new("john")], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }]) @@ -1629,7 +1632,7 @@ mod tests { Ok(vec![Group { id: GroupId(1), display_name: "group_1".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), users: vec![UserId::new("bob"), UserId::new("john")], uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"), }]) @@ -1962,7 +1965,7 @@ mod tests { groups.insert(GroupDetails { group_id: GroupId(0), display_name: "lldap_admin".to_string(), - creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap(), + creation_date: chrono::Utc.timestamp_opt(42, 42).unwrap().naive_utc(), uuid: uuid!("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), }); mock.expect_get_user_groups() diff --git a/server/src/infra/sql_backend_handler.rs b/server/src/infra/sql_backend_handler.rs index 91741cc..253eca8 100644 --- a/server/src/infra/sql_backend_handler.rs +++ b/server/src/infra/sql_backend_handler.rs @@ -61,7 +61,7 @@ impl TcpBackendHandler for SqlBackendHandler { let new_token = model::jwt_refresh_storage::Model { refresh_token_hash: refresh_token_hash as i64, user_id: user.clone(), - expiry_date: chrono::Utc::now() + duration, + expiry_date: chrono::Utc::now().naive_utc() + duration, } .into_active_model(); new_token.insert(&self.sql_pool).await?; @@ -131,7 +131,7 @@ impl TcpBackendHandler for SqlBackendHandler { let new_token = model::password_reset_tokens::Model { token: token.clone(), user_id: user.clone(), - expiry_date: chrono::Utc::now() + duration, + expiry_date: chrono::Utc::now().naive_utc() + duration, } .into_active_model(); new_token.insert(&self.sql_pool).await?;