// Copyright 2024 RustFS Team // // 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 rustfs_credentials::Credentials; use rustfs_keystone::middleware::KEYSTONE_CREDENTIALS; use rustfs_keystone::{KeystoneAuthLayer, KeystoneAuthProvider, KeystoneClient, KeystoneVersion}; use std::collections::HashMap; use std::sync::Arc; /// Create a KeystoneAuthProvider for testing (no actual Keystone connection) fn create_test_auth_provider() -> Arc { let client = KeystoneClient::new( "http://localhost:5000".to_string(), KeystoneVersion::V3, Some("admin".to_string()), Some("secret".to_string()), Some("admin".to_string()), "Default".to_string(), false, // Don't verify SSL for tests std::time::Duration::from_secs(30), ); Arc::new(KeystoneAuthProvider::new(client, 1000, std::time::Duration::from_secs(300), true)) } /// Helper to create test credentials fn create_test_credentials(access_key: &str, parent_user: &str) -> Credentials { Credentials { access_key: access_key.to_string(), secret_key: String::new(), session_token: String::new(), expiration: None, status: "Active".to_string(), parent_user: parent_user.to_string(), groups: None, claims: None, name: None, description: None, } } #[test] fn test_layer_creation_with_provider() { // Test that KeystoneAuthLayer can be created with an auth provider let auth_provider = create_test_auth_provider(); let _layer = KeystoneAuthLayer::new(Some(auth_provider)); // If this compiles and runs, the layer was created successfully } #[test] fn test_layer_creation_without_provider() { // Test that KeystoneAuthLayer can be created without an auth provider (disabled mode) let _layer = KeystoneAuthLayer::new(None); // If this compiles and runs, the layer was created successfully } #[tokio::test] async fn test_task_local_storage_scope() { // Test that task-local storage works correctly with scope let test_creds = { let mut claims = HashMap::new(); claims.insert( "keystone".to_string(), serde_json::json!({ "user_id": "test-user-id", "project_id": "test-project-id", "roles": ["member"] }), ); Credentials { access_key: "keystone:test-user-id".to_string(), secret_key: String::new(), session_token: String::new(), expiration: None, status: "Active".to_string(), parent_user: "test-user".to_string(), groups: None, claims: Some(claims), name: None, description: None, } }; // Test that credentials are available within scope let result = KEYSTONE_CREDENTIALS .scope(Some(test_creds.clone()), async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.clone()) .unwrap_or(None) }) .await; assert!(result.is_some()); let retrieved = result.unwrap(); assert_eq!(retrieved.access_key, "keystone:test-user-id"); assert_eq!(retrieved.parent_user, "test-user"); } #[tokio::test] async fn test_task_local_storage_isolation() { // Test that task-local storage is isolated between different async tasks let creds1 = create_test_credentials("keystone:user1", "user1"); let creds2 = create_test_credentials("keystone:user2", "user2"); // Spawn two tasks with different credentials let task1 = tokio::spawn(async move { KEYSTONE_CREDENTIALS .scope(Some(creds1), async { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None) }) .await }); let task2 = tokio::spawn(async move { KEYSTONE_CREDENTIALS .scope(Some(creds2), async { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None) }) .await }); // Verify each task got its own credentials let result1 = task1.await.unwrap(); let result2 = task2.await.unwrap(); assert_eq!(result1, Some("user1".to_string())); assert_eq!(result2, Some("user2".to_string())); } #[tokio::test] async fn test_task_local_storage_none_scope() { // Test that scoping with None works correctly let result = KEYSTONE_CREDENTIALS .scope(None, async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.clone()) .unwrap_or(None) }) .await; assert!(result.is_none()); } #[tokio::test] async fn test_credentials_with_claims() { // Test that credentials with Keystone claims work correctly let mut claims = HashMap::new(); claims.insert( "keystone".to_string(), serde_json::json!({ "user_id": "test-user-id", "project_id": "test-project-id", "roles": ["admin", "member"] }), ); let creds = Credentials { access_key: "keystone:test-user-id".to_string(), secret_key: String::new(), session_token: String::new(), expiration: None, status: "Active".to_string(), parent_user: "test-user".to_string(), groups: None, claims: Some(claims), name: None, description: None, }; let result = KEYSTONE_CREDENTIALS .scope(Some(creds.clone()), async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.clone()) .unwrap_or(None) }) .await; assert!(result.is_some()); let retrieved = result.unwrap(); // Verify claims are preserved assert!(retrieved.claims.is_some()); let claims_map = retrieved.claims.unwrap(); assert!(claims_map.contains_key("keystone")); let keystone_claims = &claims_map["keystone"]; assert_eq!(keystone_claims["user_id"], "test-user-id"); assert_eq!(keystone_claims["project_id"], "test-project-id"); // Verify roles let roles = keystone_claims["roles"].as_array().unwrap(); assert_eq!(roles.len(), 2); assert!(roles.contains(&serde_json::json!("admin"))); assert!(roles.contains(&serde_json::json!("member"))); } #[tokio::test] async fn test_nested_scopes() { // Test that nested scopes work correctly (inner scope takes precedence) let outer_creds = create_test_credentials("keystone:outer", "outer-user"); let inner_creds = create_test_credentials("keystone:inner", "inner-user"); let result = KEYSTONE_CREDENTIALS .scope(Some(outer_creds), async { // In outer scope let outer_result = KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None); assert_eq!(outer_result, Some("outer-user".to_string())); // Enter inner scope KEYSTONE_CREDENTIALS .scope(Some(inner_creds), async { let inner_result = KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None); assert_eq!(inner_result, Some("inner-user".to_string())); inner_result }) .await }) .await; assert_eq!(result, Some("inner-user".to_string())); } #[test] fn test_auth_provider_configuration() { // Test that AuthProvider can be configured with different settings let client = KeystoneClient::new( "http://keystone.example.com:5000".to_string(), KeystoneVersion::V3, Some("test-admin".to_string()), Some("test-password".to_string()), Some("test-project".to_string()), "TestDomain".to_string(), true, std::time::Duration::from_secs(30), ); // Test with caching enabled let provider1 = KeystoneAuthProvider::new(client.clone(), 5000, std::time::Duration::from_secs(600), true); // Verify provider was created (if this compiles, it worked) drop(provider1); // Test with caching disabled let provider2 = KeystoneAuthProvider::new(client, 0, std::time::Duration::from_secs(0), false); drop(provider2); } #[tokio::test] async fn test_multiple_sequential_scopes() { // Test that multiple sequential scopes work correctly let creds1 = create_test_credentials("keystone:first", "first-user"); let creds2 = create_test_credentials("keystone:second", "second-user"); let creds3 = create_test_credentials("keystone:third", "third-user"); // First scope let result1 = KEYSTONE_CREDENTIALS .scope(Some(creds1), async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None) }) .await; assert_eq!(result1, Some("first-user".to_string())); // Second scope let result2 = KEYSTONE_CREDENTIALS .scope(Some(creds2), async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None) }) .await; assert_eq!(result2, Some("second-user".to_string())); // Third scope let result3 = KEYSTONE_CREDENTIALS .scope(Some(creds3), async { KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.as_ref().map(|cr| cr.parent_user.clone())) .unwrap_or(None) }) .await; assert_eq!(result3, Some("third-user".to_string())); } #[tokio::test] async fn test_task_local_outside_scope() { // Test that accessing task-local storage outside a scope returns None or error let result = KEYSTONE_CREDENTIALS .try_with(|c: &Option| c.clone()) .ok() .flatten(); // Outside any scope, should be None or error assert!(result.is_none()); }