277 lines
8.5 KiB
Rust
277 lines
8.5 KiB
Rust
use axum::http::StatusCode;
|
|
use helpers::*;
|
|
use serde_json::json;
|
|
|
|
use attune_common::{
|
|
models::enums::{ArtifactType, ArtifactVisibility, OwnerType, RetentionPolicyType},
|
|
repositories::{
|
|
artifact::{ArtifactRepository, CreateArtifactInput},
|
|
identity::{
|
|
CreatePermissionAssignmentInput, CreatePermissionSetInput, IdentityRepository,
|
|
PermissionAssignmentRepository, PermissionSetRepository,
|
|
},
|
|
key::{CreateKeyInput, KeyRepository},
|
|
Create,
|
|
},
|
|
};
|
|
|
|
mod helpers;
|
|
|
|
async fn register_scoped_user(
|
|
ctx: &TestContext,
|
|
login: &str,
|
|
grants: serde_json::Value,
|
|
) -> Result<String> {
|
|
let response = ctx
|
|
.post(
|
|
"/auth/register",
|
|
json!({
|
|
"login": login,
|
|
"password": "TestPassword123!",
|
|
"display_name": format!("Scoped User {}", login),
|
|
}),
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
assert_eq!(response.status(), StatusCode::CREATED);
|
|
let body: serde_json::Value = response.json().await?;
|
|
let token = body["data"]["access_token"]
|
|
.as_str()
|
|
.expect("missing access token")
|
|
.to_string();
|
|
|
|
let identity = IdentityRepository::find_by_login(&ctx.pool, login)
|
|
.await?
|
|
.expect("registered identity should exist");
|
|
|
|
let permset = PermissionSetRepository::create(
|
|
&ctx.pool,
|
|
CreatePermissionSetInput {
|
|
r#ref: format!("test.scoped_{}", uuid::Uuid::new_v4().simple()),
|
|
pack: None,
|
|
pack_ref: None,
|
|
label: Some("Scoped Test Permission Set".to_string()),
|
|
description: Some("Scoped test grants".to_string()),
|
|
grants,
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
PermissionAssignmentRepository::create(
|
|
&ctx.pool,
|
|
CreatePermissionAssignmentInput {
|
|
identity: identity.id,
|
|
permset: permset.id,
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
Ok(token)
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_pack_scoped_key_permissions_enforce_owner_refs() {
|
|
let ctx = TestContext::new()
|
|
.await
|
|
.expect("Failed to create test context");
|
|
|
|
let token = register_scoped_user(
|
|
&ctx,
|
|
&format!("scoped_keys_{}", uuid::Uuid::new_v4().simple()),
|
|
json!([
|
|
{
|
|
"resource": "keys",
|
|
"actions": ["read"],
|
|
"constraints": {
|
|
"owner_types": ["pack"],
|
|
"owner_refs": ["python_example"]
|
|
}
|
|
}
|
|
]),
|
|
)
|
|
.await
|
|
.expect("Failed to register scoped user");
|
|
|
|
KeyRepository::create(
|
|
&ctx.pool,
|
|
CreateKeyInput {
|
|
r#ref: format!("python_example_key_{}", uuid::Uuid::new_v4().simple()),
|
|
owner_type: OwnerType::Pack,
|
|
owner: Some("python_example".to_string()),
|
|
owner_identity: None,
|
|
owner_pack: None,
|
|
owner_pack_ref: Some("python_example".to_string()),
|
|
owner_action: None,
|
|
owner_action_ref: None,
|
|
owner_sensor: None,
|
|
owner_sensor_ref: None,
|
|
name: "Python Example Key".to_string(),
|
|
encrypted: false,
|
|
encryption_key_hash: None,
|
|
value: json!("allowed"),
|
|
},
|
|
)
|
|
.await
|
|
.expect("Failed to create scoped key");
|
|
|
|
let blocked_key = KeyRepository::create(
|
|
&ctx.pool,
|
|
CreateKeyInput {
|
|
r#ref: format!("other_pack_key_{}", uuid::Uuid::new_v4().simple()),
|
|
owner_type: OwnerType::Pack,
|
|
owner: Some("other_pack".to_string()),
|
|
owner_identity: None,
|
|
owner_pack: None,
|
|
owner_pack_ref: Some("other_pack".to_string()),
|
|
owner_action: None,
|
|
owner_action_ref: None,
|
|
owner_sensor: None,
|
|
owner_sensor_ref: None,
|
|
name: "Other Pack Key".to_string(),
|
|
encrypted: false,
|
|
encryption_key_hash: None,
|
|
value: json!("blocked"),
|
|
},
|
|
)
|
|
.await
|
|
.expect("Failed to create blocked key");
|
|
|
|
let allowed_list = ctx
|
|
.get("/api/v1/keys", Some(&token))
|
|
.await
|
|
.expect("Failed to list keys");
|
|
assert_eq!(allowed_list.status(), StatusCode::OK);
|
|
let allowed_body: serde_json::Value = allowed_list.json().await.expect("Invalid key list");
|
|
assert_eq!(
|
|
allowed_body["data"]
|
|
.as_array()
|
|
.expect("expected list")
|
|
.len(),
|
|
1
|
|
);
|
|
assert_eq!(allowed_body["data"][0]["owner"], "python_example");
|
|
|
|
let blocked_get = ctx
|
|
.get(&format!("/api/v1/keys/{}", blocked_key.r#ref), Some(&token))
|
|
.await
|
|
.expect("Failed to fetch blocked key");
|
|
assert_eq!(blocked_get.status(), StatusCode::NOT_FOUND);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "integration test — requires database"]
|
|
async fn test_pack_scoped_artifact_permissions_enforce_owner_refs() {
|
|
let ctx = TestContext::new()
|
|
.await
|
|
.expect("Failed to create test context");
|
|
|
|
let token = register_scoped_user(
|
|
&ctx,
|
|
&format!("scoped_artifacts_{}", uuid::Uuid::new_v4().simple()),
|
|
json!([
|
|
{
|
|
"resource": "artifacts",
|
|
"actions": ["read", "create"],
|
|
"constraints": {
|
|
"owner_types": ["pack"],
|
|
"owner_refs": ["python_example"]
|
|
}
|
|
}
|
|
]),
|
|
)
|
|
.await
|
|
.expect("Failed to register scoped user");
|
|
|
|
let allowed_artifact = ArtifactRepository::create(
|
|
&ctx.pool,
|
|
CreateArtifactInput {
|
|
r#ref: format!("python_example.allowed_{}", uuid::Uuid::new_v4().simple()),
|
|
scope: OwnerType::Pack,
|
|
owner: "python_example".to_string(),
|
|
r#type: ArtifactType::FileText,
|
|
visibility: ArtifactVisibility::Private,
|
|
retention_policy: RetentionPolicyType::Versions,
|
|
retention_limit: 5,
|
|
name: Some("Allowed Artifact".to_string()),
|
|
description: None,
|
|
content_type: Some("text/plain".to_string()),
|
|
execution: None,
|
|
data: None,
|
|
},
|
|
)
|
|
.await
|
|
.expect("Failed to create allowed artifact");
|
|
|
|
let blocked_artifact = ArtifactRepository::create(
|
|
&ctx.pool,
|
|
CreateArtifactInput {
|
|
r#ref: format!("other_pack.blocked_{}", uuid::Uuid::new_v4().simple()),
|
|
scope: OwnerType::Pack,
|
|
owner: "other_pack".to_string(),
|
|
r#type: ArtifactType::FileText,
|
|
visibility: ArtifactVisibility::Private,
|
|
retention_policy: RetentionPolicyType::Versions,
|
|
retention_limit: 5,
|
|
name: Some("Blocked Artifact".to_string()),
|
|
description: None,
|
|
content_type: Some("text/plain".to_string()),
|
|
execution: None,
|
|
data: None,
|
|
},
|
|
)
|
|
.await
|
|
.expect("Failed to create blocked artifact");
|
|
|
|
let allowed_get = ctx
|
|
.get(
|
|
&format!("/api/v1/artifacts/{}", allowed_artifact.id),
|
|
Some(&token),
|
|
)
|
|
.await
|
|
.expect("Failed to fetch allowed artifact");
|
|
assert_eq!(allowed_get.status(), StatusCode::OK);
|
|
|
|
let blocked_get = ctx
|
|
.get(
|
|
&format!("/api/v1/artifacts/{}", blocked_artifact.id),
|
|
Some(&token),
|
|
)
|
|
.await
|
|
.expect("Failed to fetch blocked artifact");
|
|
assert_eq!(blocked_get.status(), StatusCode::NOT_FOUND);
|
|
|
|
let create_allowed = ctx
|
|
.post(
|
|
"/api/v1/artifacts",
|
|
json!({
|
|
"ref": format!("python_example.created_{}", uuid::Uuid::new_v4().simple()),
|
|
"scope": "pack",
|
|
"owner": "python_example",
|
|
"type": "file_text",
|
|
"name": "Created Artifact"
|
|
}),
|
|
Some(&token),
|
|
)
|
|
.await
|
|
.expect("Failed to create allowed artifact");
|
|
assert_eq!(create_allowed.status(), StatusCode::CREATED);
|
|
|
|
let create_blocked = ctx
|
|
.post(
|
|
"/api/v1/artifacts",
|
|
json!({
|
|
"ref": format!("other_pack.created_{}", uuid::Uuid::new_v4().simple()),
|
|
"scope": "pack",
|
|
"owner": "other_pack",
|
|
"type": "file_text",
|
|
"name": "Blocked Artifact"
|
|
}),
|
|
Some(&token),
|
|
)
|
|
.await
|
|
.expect("Failed to create blocked artifact");
|
|
assert_eq!(create_blocked.status(), StatusCode::FORBIDDEN);
|
|
}
|