Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions src/api/v4/auth/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,3 @@ pub mod types;
pub(super) fn openapi_router() -> OpenApiRouter<ServiceState> {
v3_token::openapi_router()
}

#[cfg(test)]
mod tests {

use crate::policy::{MockPolicy, MockPolicyFactory, PolicyEvaluationResult};

fn get_policy_factory_mock() -> MockPolicyFactory {
let mut policy_factory_mock = MockPolicyFactory::default();
policy_factory_mock.expect_instantiate().returning(|| {
let mut policy_mock = MockPolicy::default();
policy_mock
.expect_enforce()
.returning(|_, _, _, _| Ok(PolicyEvaluationResult::allowed()));
Ok(policy_mock)
});
policy_factory_mock
}
}
14 changes: 13 additions & 1 deletion src/revoke/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
// SPDX-License-Identifier: Apache-2.0
//! Token revocation: Backends.

//! Revocation provider Backend trait.
use async_trait::async_trait;
use dyn_clone::DynClone;

Expand All @@ -25,6 +25,9 @@ pub mod error;
pub mod sql;

#[async_trait]
/// RevokeBackend trait.
///
/// Backend driver interface expected by the revocation provider.
pub trait RevokeBackend: DynClone + Send + Sync + std::fmt::Debug {
/// Set config
fn set_config(&mut self, config: Config);
Expand All @@ -37,6 +40,15 @@ pub trait RevokeBackend: DynClone + Send + Sync + std::fmt::Debug {
state: &ServiceState,
token: &Token,
) -> Result<bool, RevokeProviderError>;

/// Revoke the token.
///
/// Mark the token as revoked to prohibit from being used even while not expired.
async fn revoke_token(
&self,
state: &ServiceState,
token: &Token,
) -> Result<(), RevokeProviderError>;
}

dyn_clone::clone_trait_object!(RevokeBackend);
86 changes: 13 additions & 73 deletions src/revoke/backend/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@
//! Revoke provider: database backend.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use super::RevokeBackend;
use crate::config::Config;
use crate::db::entity::revocation_event as db_revocation_event;
use crate::keystone::ServiceState;
use crate::revoke::RevokeProviderError;
use crate::revoke::backend::error::RevokeDatabaseError;
use crate::revoke::types::*;
use crate::token::types::Token;

mod create;
mod list;

/// Sql Database revocation backend.
Expand Down Expand Up @@ -79,77 +78,18 @@ impl RevokeBackend for SqlBackend {
Ok(false)
}
}
}

/// Revocation event.
#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
#[builder(setter(strip_option, into))]
pub struct RevocationEvent {
pub domain_id: Option<String>,
pub project_id: Option<String>,
pub user_id: Option<String>,
pub role_id: Option<String>,
pub trust_id: Option<String>,
pub consumer_id: Option<String>,
pub access_token_id: Option<String>,
pub issued_before: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
pub revoked_at: DateTime<Utc>,
pub audit_id: Option<String>,
pub audit_chain_id: Option<String>,
}

/// Revocation list parameters.
///
/// It may be necessary to list revocation events not related to the certain token.
#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
#[builder(setter(strip_option, into))]
struct RevocationEventListParameters {
//pub access_token_id: Option<String>,
//pub audit_chain_id: Option<String>,
#[builder(default)]
pub audit_id: Option<String>,
//pub consumer_id: Option<String>,
#[builder(default)]
pub domain_id: Option<String>,
#[builder(default)]
pub expires_at: Option<DateTime<Utc>>,
#[builder(default)]
pub issued_before: Option<DateTime<Utc>>,
#[builder(default)]
pub project_id: Option<String>,
#[builder(default)]
pub revoked_at: Option<DateTime<Utc>>,
//pub role_id: Option<String>,
//pub trust_id: Option<String>,
#[builder(default)]
pub user_id: Option<Vec<String>>,
}

impl TryFrom<&Token> for RevocationEventListParameters {
type Error = RevokeProviderError;
fn try_from(value: &Token) -> Result<Self, Self::Error> {
// TODO: for trust token user_id can be trustee_id or trustor_id
Ok(Self {
//access_token_id: None,
//audit_chain_id: None,
audit_id: Some(
value
.audit_ids()
.first()
.ok_or_else(|| RevokeProviderError::TokenHasNoAuditId)?,
)
.cloned(),
//consumer_id: None,
domain_id: value.domain().map(|domain| domain.id.clone()),
expires_at: None,
issued_before: Some(*value.issued_at()),
project_id: value.project_id().cloned(),
revoked_at: None,
//role_id: None,
//trust_id: None,
user_id: Some(vec![value.user_id().clone()]),
})
/// Revoke the token.
///
/// Mark the token as revoked to prohibit from being used even while not expired.
async fn revoke_token(
&self,
state: &ServiceState,
token: &Token,
) -> Result<(), RevokeProviderError> {
Ok(create::create(&state.db, token.try_into()?)
.await
.map(|_| ())?)
}
}

Expand Down
150 changes: 150 additions & 0 deletions src/revoke/backend/sql/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// 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.
//
// SPDX-License-Identifier: Apache-2.0
//! Create a token revocation record.

use sea_orm::DatabaseConnection;
use sea_orm::entity::*;

use super::{RevocationEvent, RevocationEventCreate};
use crate::db::entity::revocation_event as db_revocation_event;
use crate::revoke::backend::error::{RevokeDatabaseError, db_err};

/// Create token revocation record.
///
/// Invalidate the token before the regular expiration.
pub async fn create(
db: &DatabaseConnection,
revocation: RevocationEventCreate,
) -> Result<RevocationEvent, RevokeDatabaseError> {
let entry = db_revocation_event::ActiveModel {
id: NotSet,
access_token_id: revocation
.access_token_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
audit_chain_id: revocation
.audit_chain_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
audit_id: revocation
.audit_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
consumer_id: revocation
.consumer_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
domain_id: revocation
.domain_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
expires_at: revocation
.expires_at
.map(|val| Set(Some(val.naive_utc())))
.unwrap_or(NotSet),
issued_before: Set(revocation.issued_before.naive_utc()),
project_id: revocation
.project_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
revoked_at: Set(revocation.revoked_at.naive_utc()),
role_id: revocation.role_id.clone().map(Set).unwrap_or(NotSet).into(),
trust_id: revocation
.trust_id
.clone()
.map(Set)
.unwrap_or(NotSet)
.into(),
user_id: revocation.user_id.clone().map(Set).unwrap_or(NotSet).into(),
};

let db_entry: db_revocation_event::Model = entry
.insert(db)
.await
.map_err(|err| db_err(err, "creating token revocation event"))?;

db_entry.try_into()
}

#[cfg(test)]
mod tests {
use chrono::{Days, Utc};
use sea_orm::{DatabaseBackend, MockDatabase, Transaction};

use super::super::tests::get_mock;
use super::*;

#[tokio::test]
async fn test_create() {
let time1 = Utc::now();
let time2 = time1.checked_add_days(Days::new(1)).unwrap();
let time3 = time2.checked_add_days(Days::new(1)).unwrap();
// Create MockDatabase with mock query results
let db = MockDatabase::new(DatabaseBackend::Postgres)
.append_query_results([vec![get_mock()]])
.into_connection();

let req = RevocationEventCreate {
access_token_id: Some("access_token_id".into()),
audit_chain_id: Some("audit_chain_id".into()),
audit_id: Some("audit_id".into()),
consumer_id: Some("consumer_id".into()),
domain_id: Some("domain_id".into()),
expires_at: Some(time1),
issued_before: time2,
project_id: Some("project_id".into()),
revoked_at: time3,
role_id: Some("role_id".into()),
trust_id: Some("trust_id".into()),
user_id: Some("uid".into()),
};

create(&db, req).await.unwrap();

// Checking transaction log
assert_eq!(
db.into_transaction_log(),
[Transaction::from_sql_and_values(
DatabaseBackend::Postgres,
r#"INSERT INTO "revocation_event" ("domain_id", "project_id", "user_id", "role_id", "trust_id", "consumer_id", "access_token_id", "issued_before", "expires_at", "revoked_at", "audit_id", "audit_chain_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id", "domain_id", "project_id", "user_id", "role_id", "trust_id", "consumer_id", "access_token_id", "issued_before", "expires_at", "revoked_at", "audit_id", "audit_chain_id""#,
[
"domain_id".into(),
"project_id".into(),
"uid".into(),
"role_id".into(),
"trust_id".into(),
"consumer_id".into(),
"access_token_id".into(),
time2.naive_utc().into(),
time1.naive_utc().into(),
time3.naive_utc().into(),
"audit_id".into(),
"audit_chain_id".into()
]
),]
);
}
}
2 changes: 1 addition & 1 deletion src/revoke/backend/sql/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use sea_orm::DatabaseConnection;
use sea_orm::entity::*;
use sea_orm::query::*;

use super::{RevocationEvent, RevocationEventListParameters};
use crate::db::entity::{
prelude::RevocationEvent as DbRevocationEvent, revocation_event as db_revocation_event,
};
use crate::revoke::backend::error::{RevokeDatabaseError, db_err};
use crate::revoke::types::{RevocationEvent, RevocationEventListParameters};

fn build_query_filters(
params: &RevocationEventListParameters,
Expand Down
6 changes: 6 additions & 0 deletions src/revoke/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ mock! {
state: &ServiceState,
token: &Token,
) -> Result<bool, RevokeProviderError>;

async fn revoke_token(
&self,
state: &ServiceState,
token: &Token,
) -> Result<(), RevokeProviderError>;
}

impl Clone for RevokeProvider {
Expand Down
11 changes: 11 additions & 0 deletions src/revoke/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,15 @@ impl RevokeApi for RevokeProvider {
) -> Result<bool, RevokeProviderError> {
self.backend_driver.is_token_revoked(state, token).await
}

/// Revoke the token.
///
/// Mark the token as revoked to prohibit from being used even while not expired.
async fn revoke_token(
&self,
state: &ServiceState,
token: &Token,
) -> Result<(), RevokeProviderError> {
self.backend_driver.revoke_token(state, token).await
}
}
Loading
Loading