From a4e9ad3d0b61d42eee45dc699dba02020596601f Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 2 Nov 2021 12:58:13 +0100 Subject: [PATCH] Split the data-model in different modules --- crates/data-model/src/lib.rs | 335 +----------------- .../src/oauth2/authorization_grant.rs | 146 ++++++++ crates/data-model/src/oauth2/client.rs | 34 ++ crates/data-model/src/oauth2/mod.rs | 23 ++ crates/data-model/src/oauth2/session.rs | 43 +++ crates/data-model/src/tokens.rs | 63 ++++ crates/data-model/src/traits.rs | 37 ++ crates/data-model/src/users.rs | 106 ++++++ 8 files changed, 463 insertions(+), 324 deletions(-) create mode 100644 crates/data-model/src/oauth2/authorization_grant.rs create mode 100644 crates/data-model/src/oauth2/client.rs create mode 100644 crates/data-model/src/oauth2/mod.rs create mode 100644 crates/data-model/src/oauth2/session.rs create mode 100644 crates/data-model/src/tokens.rs create mode 100644 crates/data-model/src/traits.rs create mode 100644 crates/data-model/src/users.rs diff --git a/crates/data-model/src/lib.rs b/crates/data-model/src/lib.rs index e3e8fbf5..0f392a65 100644 --- a/crates/data-model/src/lib.rs +++ b/crates/data-model/src/lib.rs @@ -12,330 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::num::NonZeroU32; - -use chrono::{DateTime, Duration, Utc}; -use oauth2_types::{pkce::CodeChallengeMethod, requests::ResponseMode, scope::Scope}; -use serde::Serialize; -use thiserror::Error; -use url::Url; - pub mod errors; +pub(crate) mod oauth2; +pub(crate) mod tokens; +pub(crate) mod traits; +pub(crate) mod users; -pub trait StorageBackendMarker: StorageBackend {} - -pub trait StorageBackend { - type UserData: Clone + std::fmt::Debug + PartialEq; - type AuthenticationData: Clone + std::fmt::Debug + PartialEq; - type BrowserSessionData: Clone + std::fmt::Debug + PartialEq; - type ClientData: Clone + std::fmt::Debug + PartialEq; - type SessionData: Clone + std::fmt::Debug + PartialEq; - type AuthorizationGrantData: Clone + std::fmt::Debug + PartialEq; - type AccessTokenData: Clone + std::fmt::Debug + PartialEq; - type RefreshTokenData: Clone + std::fmt::Debug + PartialEq; -} - -impl StorageBackend for () { - type AccessTokenData = (); - type AuthenticationData = (); - type AuthorizationGrantData = (); - type BrowserSessionData = (); - type ClientData = (); - type RefreshTokenData = (); - type SessionData = (); - type UserData = (); -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct User { - #[serde(skip_serializing)] - pub data: T::UserData, - pub username: String, - pub sub: String, -} - -impl User -where - T::UserData: Default, -{ - pub fn samples() -> Vec { - vec![User { - data: Default::default(), - username: "john".to_string(), - sub: "123-456".to_string(), - }] - } -} - -impl From> for User<()> { - fn from(u: User) -> Self { - User { - data: (), - username: u.username, - sub: u.sub, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct Authentication { - #[serde(skip_serializing)] - pub data: T::AuthenticationData, - pub created_at: DateTime, -} - -impl From> for Authentication<()> { - fn from(a: Authentication) -> Self { - Authentication { - data: (), - created_at: a.created_at, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct BrowserSession { - #[serde(skip_serializing)] - pub data: T::BrowserSessionData, - pub user: User, - pub created_at: DateTime, - pub last_authentication: Option>, -} - -impl From> for BrowserSession<()> { - fn from(s: BrowserSession) -> Self { - BrowserSession { - data: (), - user: s.user.into(), - created_at: s.created_at, - last_authentication: s.last_authentication.map(Into::into), - } - } -} - -impl BrowserSession -where - T::BrowserSessionData: Default, - T::UserData: Default, -{ - pub fn samples() -> Vec { - User::::samples() - .into_iter() - .map(|user| BrowserSession { - data: Default::default(), - user, - created_at: Utc::now(), - last_authentication: None, - }) - .collect() - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct Client { - #[serde(skip_serializing)] - pub data: T::ClientData, - pub client_id: String, -} - -impl From> for Client<()> { - fn from(c: Client) -> Self { - Client { - data: (), - client_id: c.client_id, - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct Session { - #[serde(skip_serializing)] - pub data: T::SessionData, - pub browser_session: BrowserSession, - pub client: Client, - pub scope: Scope, -} - -impl From> for Session<()> { - fn from(s: Session) -> Self { - Session { - data: (), - browser_session: s.browser_session.into(), - client: s.client.into(), - scope: s.scope, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AccessToken { - pub data: T::AccessTokenData, - pub jti: String, - pub token: String, - pub expires_after: Duration, - pub created_at: DateTime, -} - -impl From> for AccessToken<()> { - fn from(t: AccessToken) -> Self { - AccessToken { - data: (), - jti: t.jti, - token: t.token, - expires_after: t.expires_after, - created_at: t.created_at, - } - } -} - -impl AccessToken { - pub fn exp(&self) -> DateTime { - self.created_at + self.expires_after - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct RefreshToken { - pub data: T::RefreshTokenData, - pub token: String, - pub created_at: DateTime, - pub access_token: Option>, -} - -impl From> for RefreshToken<()> { - fn from(t: RefreshToken) -> Self { - RefreshToken { - data: (), - token: t.token, - created_at: t.created_at, - access_token: t.access_token.map(Into::into), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub struct Pkce { - pub challenge_method: CodeChallengeMethod, - pub challenge: String, -} - -impl Pkce { - pub fn new(challenge_method: CodeChallengeMethod, challenge: String) -> Self { - Pkce { - challenge_method, - challenge, - } - } - - pub fn verify(&self, verifier: &str) -> bool { - self.challenge_method.verify(&self.challenge, verifier) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub struct AuthorizationCode { - pub code: String, - pub pkce: Option, -} - -#[derive(Debug, Error)] -#[error("invalid state transition")] -pub struct InvalidTransitionError; - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub enum AuthorizationGrantStage { - Pending, - Fulfilled { - session: Session, - fulfilled_at: DateTime, +pub use self::{ + oauth2::{ + AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Client, Pkce, Session, }, - Exchanged { - session: Session, - fulfilled_at: DateTime, - exchanged_at: DateTime, - }, - Cancelled { - cancelled_at: DateTime, - }, -} - -impl Default for AuthorizationGrantStage { - fn default() -> Self { - Self::Pending - } -} - -impl AuthorizationGrantStage { - pub fn new() -> Self { - Self::Pending - } - - pub fn fulfill( - self, - fulfilled_at: DateTime, - session: Session, - ) -> Result { - match self { - Self::Pending => Ok(Self::Fulfilled { - fulfilled_at, - session, - }), - _ => Err(InvalidTransitionError), - } - } - - pub fn exchange(self, exchanged_at: DateTime) -> Result { - match self { - Self::Fulfilled { - fulfilled_at, - session, - } => Ok(Self::Exchanged { - fulfilled_at, - exchanged_at, - session, - }), - _ => Err(InvalidTransitionError), - } - } - - pub fn cancel(self, cancelled_at: DateTime) -> Result { - match self { - Self::Pending => Ok(Self::Cancelled { cancelled_at }), - _ => Err(InvalidTransitionError), - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -#[serde(bound = "T: StorageBackend")] -pub struct AuthorizationGrant { - #[serde(skip_serializing)] - pub data: T::AuthorizationGrantData, - #[serde(flatten)] - pub stage: AuthorizationGrantStage, - pub code: Option, - pub client: Client, - pub redirect_uri: Url, - pub scope: Scope, - pub state: Option, - pub nonce: Option, - pub max_age: Option, - pub acr_values: Option, - pub response_mode: ResponseMode, - pub response_type_token: bool, - pub response_type_id_token: bool, - pub created_at: DateTime, -} - -impl AuthorizationGrant { - pub fn max_auth_time(&self) -> DateTime { - let max_age: Option = self.max_age.map(|x| x.get().into()); - self.created_at + Duration::seconds(max_age.unwrap_or(3600 * 24 * 365)) - } -} + tokens::{AccessToken, RefreshToken}, + traits::{StorageBackend, StorageBackendMarker}, + users::{Authentication, BrowserSession, User}, +}; diff --git a/crates/data-model/src/oauth2/authorization_grant.rs b/crates/data-model/src/oauth2/authorization_grant.rs new file mode 100644 index 00000000..f0734975 --- /dev/null +++ b/crates/data-model/src/oauth2/authorization_grant.rs @@ -0,0 +1,146 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::num::NonZeroU32; + +use chrono::{DateTime, Duration, Utc}; +use oauth2_types::{pkce::CodeChallengeMethod, requests::ResponseMode}; +use serde::Serialize; +use thiserror::Error; +use url::Url; + +use super::{client::Client, session::Session}; +use crate::traits::StorageBackend; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Pkce { + pub challenge_method: CodeChallengeMethod, + pub challenge: String, +} + +impl Pkce { + pub fn new(challenge_method: CodeChallengeMethod, challenge: String) -> Self { + Pkce { + challenge_method, + challenge, + } + } + + pub fn verify(&self, verifier: &str) -> bool { + self.challenge_method.verify(&self.challenge, verifier) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct AuthorizationCode { + pub code: String, + pub pkce: Option, +} + +#[derive(Debug, Error)] +#[error("invalid state transition")] +pub struct InvalidTransitionError; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub enum AuthorizationGrantStage { + Pending, + Fulfilled { + session: Session, + fulfilled_at: DateTime, + }, + Exchanged { + session: Session, + fulfilled_at: DateTime, + exchanged_at: DateTime, + }, + Cancelled { + cancelled_at: DateTime, + }, +} + +impl Default for AuthorizationGrantStage { + fn default() -> Self { + Self::Pending + } +} + +impl AuthorizationGrantStage { + pub fn new() -> Self { + Self::Pending + } + + pub fn fulfill( + self, + fulfilled_at: DateTime, + session: Session, + ) -> Result { + match self { + Self::Pending => Ok(Self::Fulfilled { + fulfilled_at, + session, + }), + _ => Err(InvalidTransitionError), + } + } + + pub fn exchange(self, exchanged_at: DateTime) -> Result { + match self { + Self::Fulfilled { + fulfilled_at, + session, + } => Ok(Self::Exchanged { + fulfilled_at, + exchanged_at, + session, + }), + _ => Err(InvalidTransitionError), + } + } + + pub fn cancel(self, cancelled_at: DateTime) -> Result { + match self { + Self::Pending => Ok(Self::Cancelled { cancelled_at }), + _ => Err(InvalidTransitionError), + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct AuthorizationGrant { + #[serde(skip_serializing)] + pub data: T::AuthorizationGrantData, + #[serde(flatten)] + pub stage: AuthorizationGrantStage, + pub code: Option, + pub client: Client, + pub redirect_uri: Url, + pub scope: oauth2_types::scope::Scope, + pub state: Option, + pub nonce: Option, + pub max_age: Option, + pub acr_values: Option, + pub response_mode: ResponseMode, + pub response_type_token: bool, + pub response_type_id_token: bool, + pub created_at: DateTime, +} + +impl AuthorizationGrant { + pub fn max_auth_time(&self) -> DateTime { + let max_age: Option = self.max_age.map(|x| x.get().into()); + self.created_at + Duration::seconds(max_age.unwrap_or(3600 * 24 * 365)) + } +} diff --git a/crates/data-model/src/oauth2/client.rs b/crates/data-model/src/oauth2/client.rs new file mode 100644 index 00000000..2d8705b7 --- /dev/null +++ b/crates/data-model/src/oauth2/client.rs @@ -0,0 +1,34 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::Serialize; + +use crate::traits::{StorageBackend, StorageBackendMarker}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct Client { + #[serde(skip_serializing)] + pub data: T::ClientData, + pub client_id: String, +} + +impl From> for Client<()> { + fn from(c: Client) -> Self { + Client { + data: (), + client_id: c.client_id, + } + } +} diff --git a/crates/data-model/src/oauth2/mod.rs b/crates/data-model/src/oauth2/mod.rs new file mode 100644 index 00000000..6309fd7e --- /dev/null +++ b/crates/data-model/src/oauth2/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(self) mod authorization_grant; +pub(self) mod client; +pub(self) mod session; + +pub use self::{ + authorization_grant::{AuthorizationCode, AuthorizationGrant, AuthorizationGrantStage, Pkce}, + client::Client, + session::Session, +}; diff --git a/crates/data-model/src/oauth2/session.rs b/crates/data-model/src/oauth2/session.rs new file mode 100644 index 00000000..5558d87c --- /dev/null +++ b/crates/data-model/src/oauth2/session.rs @@ -0,0 +1,43 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use oauth2_types::scope::Scope; +use serde::Serialize; + +use super::client::Client; +use crate::{ + traits::{StorageBackend, StorageBackendMarker}, + users::BrowserSession, +}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct Session { + #[serde(skip_serializing)] + pub data: T::SessionData, + pub browser_session: BrowserSession, + pub client: Client, + pub scope: Scope, +} + +impl From> for Session<()> { + fn from(s: Session) -> Self { + Session { + data: (), + browser_session: s.browser_session.into(), + client: s.client.into(), + scope: s.scope, + } + } +} diff --git a/crates/data-model/src/tokens.rs b/crates/data-model/src/tokens.rs new file mode 100644 index 00000000..eca52762 --- /dev/null +++ b/crates/data-model/src/tokens.rs @@ -0,0 +1,63 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Duration, Utc}; + +use crate::traits::{StorageBackend, StorageBackendMarker}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccessToken { + pub data: T::AccessTokenData, + pub jti: String, + pub token: String, + pub expires_after: Duration, + pub created_at: DateTime, +} + +impl From> for AccessToken<()> { + fn from(t: AccessToken) -> Self { + AccessToken { + data: (), + jti: t.jti, + token: t.token, + expires_after: t.expires_after, + created_at: t.created_at, + } + } +} + +impl AccessToken { + pub fn exp(&self) -> DateTime { + self.created_at + self.expires_after + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RefreshToken { + pub data: T::RefreshTokenData, + pub token: String, + pub created_at: DateTime, + pub access_token: Option>, +} + +impl From> for RefreshToken<()> { + fn from(t: RefreshToken) -> Self { + RefreshToken { + data: (), + token: t.token, + created_at: t.created_at, + access_token: t.access_token.map(Into::into), + } + } +} diff --git a/crates/data-model/src/traits.rs b/crates/data-model/src/traits.rs new file mode 100644 index 00000000..835801a8 --- /dev/null +++ b/crates/data-model/src/traits.rs @@ -0,0 +1,37 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub trait StorageBackendMarker: StorageBackend {} + +pub trait StorageBackend { + type UserData: Clone + std::fmt::Debug + PartialEq; + type AuthenticationData: Clone + std::fmt::Debug + PartialEq; + type BrowserSessionData: Clone + std::fmt::Debug + PartialEq; + type ClientData: Clone + std::fmt::Debug + PartialEq; + type SessionData: Clone + std::fmt::Debug + PartialEq; + type AuthorizationGrantData: Clone + std::fmt::Debug + PartialEq; + type AccessTokenData: Clone + std::fmt::Debug + PartialEq; + type RefreshTokenData: Clone + std::fmt::Debug + PartialEq; +} + +impl StorageBackend for () { + type AccessTokenData = (); + type AuthenticationData = (); + type AuthorizationGrantData = (); + type BrowserSessionData = (); + type ClientData = (); + type RefreshTokenData = (); + type SessionData = (); + type UserData = (); +} diff --git a/crates/data-model/src/users.rs b/crates/data-model/src/users.rs new file mode 100644 index 00000000..cc9dce63 --- /dev/null +++ b/crates/data-model/src/users.rs @@ -0,0 +1,106 @@ +// Copyright 2021 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Utc}; +use serde::Serialize; + +use crate::traits::{StorageBackend, StorageBackendMarker}; + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct User { + #[serde(skip_serializing)] + pub data: T::UserData, + pub username: String, + pub sub: String, +} + +impl User +where + T::UserData: Default, +{ + pub fn samples() -> Vec { + vec![User { + data: Default::default(), + username: "john".to_string(), + sub: "123-456".to_string(), + }] + } +} + +impl From> for User<()> { + fn from(u: User) -> Self { + User { + data: (), + username: u.username, + sub: u.sub, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct Authentication { + #[serde(skip_serializing)] + pub data: T::AuthenticationData, + pub created_at: DateTime, +} + +impl From> for Authentication<()> { + fn from(a: Authentication) -> Self { + Authentication { + data: (), + created_at: a.created_at, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(bound = "T: StorageBackend")] +pub struct BrowserSession { + #[serde(skip_serializing)] + pub data: T::BrowserSessionData, + pub user: User, + pub created_at: DateTime, + pub last_authentication: Option>, +} + +impl From> for BrowserSession<()> { + fn from(s: BrowserSession) -> Self { + BrowserSession { + data: (), + user: s.user.into(), + created_at: s.created_at, + last_authentication: s.last_authentication.map(Into::into), + } + } +} + +impl BrowserSession +where + T::BrowserSessionData: Default, + T::UserData: Default, +{ + pub fn samples() -> Vec { + User::::samples() + .into_iter() + .map(|user| BrowserSession { + data: Default::default(), + user, + created_at: Utc::now(), + last_authentication: None, + }) + .collect() + } +}