mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
327 lines
11 KiB
Rust
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());
|
|
}
|