diff --git a/tests/integration/src/assignment/grant.rs b/tests/integration/src/assignment/grant.rs index 946ecec7..0d4faa15 100644 --- a/tests/integration/src/assignment/grant.rs +++ b/tests/integration/src/assignment/grant.rs @@ -15,9 +15,11 @@ mod list; mod revoke; +use std::sync::Arc; + use eyre::Report; use sea_orm::{DbConn, entity::*}; -use std::sync::Arc; +use tempfile::TempDir; use openstack_keystone::config::Config; use openstack_keystone::db::entity::{prelude::*, project}; @@ -26,7 +28,6 @@ use openstack_keystone::plugin_manager::PluginManager; use openstack_keystone::policy::PolicyFactory; use openstack_keystone::provider::Provider; -//use super::setup_schema; use crate::common::{bootstrap, get_isolated_database}; async fn setup_assignment_data(db: &DbConn) -> Result<(), Report> { @@ -70,18 +71,26 @@ async fn setup_assignment_data(db: &DbConn) -> Result<(), Report> { Ok(()) } -async fn get_state() -> Result, Report> { +async fn get_state() -> Result<(Arc, TempDir), Report> { let db = get_isolated_database().await?; setup_assignment_data(&db).await?; - let cfg: Config = Config::default(); + let tmp_fernet_repo = TempDir::new()?; + + let mut cfg: Config = Config::default(); + cfg.auth.methods = vec!["application_credential".into(), "password".into()]; + cfg.fernet_tokens.key_repository = tmp_fernet_repo.path().to_path_buf(); + let fernet_utils = openstack_keystone::token::backend::fernet::utils::FernetUtils { + key_repository: cfg.fernet_tokens.key_repository.clone(), + max_active_keys: cfg.fernet_tokens.max_active_keys, + }; + fernet_utils.initialize_key_repository()?; let plugin_manager = PluginManager::default(); let provider = Provider::new(cfg.clone(), plugin_manager)?; - Ok(Arc::new(Service::new( - cfg, - db, - provider, - PolicyFactory::default(), - )?)) + + Ok(( + Arc::new(Service::new(cfg, db, provider, PolicyFactory::default())?), + tmp_fernet_repo, + )) } diff --git a/tests/integration/src/assignment/grant/list.rs b/tests/integration/src/assignment/grant/list.rs index f790cd22..497f9dae 100644 --- a/tests/integration/src/assignment/grant/list.rs +++ b/tests/integration/src/assignment/grant/list.rs @@ -73,7 +73,7 @@ async fn init_data(state: &ServiceState) -> Result<()> { #[traced_test] #[tokio::test] async fn test_list_user_domain() -> Result<()> { - let state = get_state().await?; + let (state, _) = get_state().await?; init_data(&state).await?; assert_eq!( @@ -119,7 +119,7 @@ async fn test_list_user_domain() -> Result<()> { #[tokio::test] async fn test_list_user_tl_project() -> Result<()> { - let state = get_state().await?; + let (state, _) = get_state().await?; init_data(&state).await?; assert_eq!( @@ -158,7 +158,7 @@ async fn test_list_user_tl_project() -> Result<()> { #[tokio::test] async fn test_list_user_sub_project() -> Result<()> { - let state = get_state().await?; + let (state, _) = get_state().await?; init_data(&state).await?; assert_eq!( diff --git a/tests/integration/src/assignment/grant/revoke.rs b/tests/integration/src/assignment/grant/revoke.rs index 156fc1a6..7771a626 100644 --- a/tests/integration/src/assignment/grant/revoke.rs +++ b/tests/integration/src/assignment/grant/revoke.rs @@ -16,12 +16,19 @@ use eyre::Result; use tracing_test::traced_test; +use uuid::Uuid; +use openstack_keystone::application_credential::ApplicationCredentialApi; +use openstack_keystone::application_credential::types::*; use openstack_keystone::assignment::{AssignmentApi, types::*}; +use openstack_keystone::auth::*; use openstack_keystone::keystone::ServiceState; +use openstack_keystone::resource::types::ProjectBuilder; +use openstack_keystone::role::types::*; +use openstack_keystone::token::{TokenApi, TokenProviderError}; use super::get_state; -use crate::common::create_role; +use crate::common::{create_role, create_user}; async fn grant_exists( state: &ServiceState, @@ -61,7 +68,7 @@ async fn grant_exists( #[traced_test] #[tokio::test] async fn test_revoke_user_project_grant() -> Result<()> { - let state = get_state().await?; + let (state, _tmp) = get_state().await?; create_role(&state, "role_revoke_1").await?; // Create a direct grant @@ -95,3 +102,164 @@ async fn test_revoke_user_project_grant() -> Result<()> { Ok(()) } + +#[traced_test] +#[tokio::test] +async fn test_revoke_user_project_grant_auth_impact() -> Result<()> { + let (state, _tmp) = get_state().await?; + + let user = create_user(&state, Some("user_a")).await?; + // Create two roles: one that will be granted and revoked, and another to confirm that revocation is specific + create_role(&state, "role_revoke_auth").await?; + create_role(&state, "role_exist_auth").await?; + + // Grant first role that will be revoked + let grant = state + .provider + .get_assignment_provider() + .create_grant( + &state, + AssignmentCreate::user_project(&user.id, "project_a", "role_revoke_auth", false), + ) + .await?; + + assert!( + grant_exists(&state, &user.id, "project_a", "role_revoke_auth", true).await?, + "Grant should exist after creation" + ); + + // Grant second role that will remain unaffected + let _ = state + .provider + .get_assignment_provider() + .create_grant( + &state, + AssignmentCreate::user_project(&user.id, "project_a", "role_exist_auth", false), + ) + .await?; + assert!( + grant_exists(&state, &user.id, "project_a", "role_exist_auth", true).await?, + "Grant should exist after creation" + ); + // Create application credential and issue a token BEFORE revocation + let cred: ApplicationCredentialCreateResponse = state + .provider + .get_application_credential_provider() + .create_application_credential( + &state, + ApplicationCredentialCreate { + access_rules: None, + name: Uuid::new_v4().to_string(), + project_id: "project_a".into(), + roles: vec![ + RoleRef { + id: "role_revoke_auth".into(), + domain_id: None, + name: Some("role_exist_auth".into()), + }, + RoleRef { + id: "role_exist_auth".into(), + name: Some("role_exist_auth".into()), + domain_id: None, + }, + ], + user_id: user.id.clone(), + ..Default::default() + }, + ) + .await?; + + let authz = AuthzInfo::Project( + ProjectBuilder::default() + .id(cred.project_id.clone()) + .name("project_a") + .domain_id("domain_a") + .enabled(true) + .build()?, + ); + + let pre_revoke_token = state.provider.get_token_provider().issue_token( + AuthenticatedInfoBuilder::default() + .application_credential(cred.clone()) + .user_id(user.id.clone()) + .user(user.clone()) + .methods(vec!["application_credential".into()]) + .build()?, + authz.clone(), + None, + )?; + let pre_revoke_encoded = state + .provider + .get_token_provider() + .encode_token(&pre_revoke_token)?; + + // Sanity check: token is valid before revocation + assert!( + state + .provider + .get_token_provider() + .validate_token(&state, &pre_revoke_encoded, None, None) + .await + .is_ok(), + "Token should be valid before revocation" + ); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + // --- Revoke the grant --- + state + .provider + .get_assignment_provider() + .revoke_grant(&state, grant) + .await?; + + // CHECK 1: listing roles no longer returns the revoked role + assert!( + !grant_exists(&state, &user.id, "project_a", "role_revoke_auth", true).await?, + "Grant should not exist after revocation" + ); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + // CHECK 2: new auth does not obtain the role + let post_revoke_token = state.provider.get_token_provider().issue_token( + AuthenticatedInfoBuilder::default() + .application_credential(cred.clone()) + .user_id(user.id.clone()) + .user(user.clone()) + .methods(vec!["application_credential".into()]) + .build()?, + authz, + None, + )?; + let post_revoke_encoded = state + .provider + .get_token_provider() + .encode_token(&post_revoke_token)?; + + let validated = state + .provider + .get_token_provider() + .validate_token(&state, &post_revoke_encoded, None, None) + .await?; + + let roles = validated + .effective_roles() + .expect("Token should have effective roles"); + + assert!(roles.iter().any(|r| r.id == "role_exist_auth")); + assert!(!roles.iter().any(|r| r.id == "role_revoke_auth")); + + // CHECK 3: existing auth (issued before revocation) is no longer accepted + assert!( + matches!( + state + .provider + .get_token_provider() + .validate_token(&state, &pre_revoke_encoded, None, None) + .await, + Err(TokenProviderError::TokenRevoked) + ), + "Pre-revocation token should fail validation after grant is revoked" + ); + + Ok(()) +}