Files

327 lines
11 KiB
Rust

// 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<KeystoneAuthProvider> {
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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| 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<Credentials>| c.clone())
.ok()
.flatten();
// Outside any scope, should be None or error
assert!(result.is_none());
}