use crate::{
AzureHttpClient, Result,
ops::rbac::RbacOps,
types::rbac::{
RoleAssignment, RoleAssignmentCreateRequest, RoleAssignmentListResult, RoleDefinition,
RoleDefinitionListResult,
},
};
pub struct RbacClient<'a> {
ops: RbacOps<'a>,
client: &'a AzureHttpClient,
}
impl<'a> RbacClient<'a> {
pub(crate) fn new(client: &'a AzureHttpClient) -> Self {
Self {
ops: RbacOps::new(client),
client,
}
}
pub async fn list_role_definitions(&self) -> Result<RoleDefinitionListResult> {
self.ops
.list_role_definitions(self.client.subscription_id())
.await
}
pub async fn get_role_definition(&self, role_definition_id: &str) -> Result<RoleDefinition> {
self.ops
.get_role_definition(self.client.subscription_id(), role_definition_id)
.await
}
pub async fn list_role_assignments(&self) -> Result<RoleAssignmentListResult> {
self.ops
.list_role_assignments(self.client.subscription_id())
.await
}
pub async fn get_role_assignment(&self, role_assignment_id: &str) -> Result<RoleAssignment> {
self.ops
.get_role_assignment(self.client.subscription_id(), role_assignment_id)
.await
}
pub async fn create_role_assignment(
&self,
role_assignment_name: &str,
body: &RoleAssignmentCreateRequest,
) -> Result<RoleAssignment> {
self.ops
.create_role_assignment(self.client.subscription_id(), role_assignment_name, body)
.await
}
pub async fn delete_role_assignment(&self, role_assignment_id: &str) -> Result<()> {
self.ops
.delete_role_assignment(self.client.subscription_id(), role_assignment_id)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MockClient;
const SUB_ID: &str = "test-subscription-id";
const READER_GUID: &str = "acdd72a7-3385-48ef-bd42-f606fba81ae7";
const ASSIGNMENT_NAME: &str = "c1a2b3c4-d5e6-7f80-9abc-def012345678";
fn make_client(mock: MockClient) -> AzureHttpClient {
AzureHttpClient::from_mock(mock)
}
#[tokio::test]
async fn list_role_definitions_returns_roles() {
let mut mock = MockClient::new();
mock.expect_get(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleDefinitions"),
)
.returning_json(serde_json::json!({
"value": [
{
"id": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
"name": "acdd72a7-3385-48ef-bd42-f606fba81ae7",
"type": "Microsoft.Authorization/roleDefinitions",
"properties": {
"roleName": "Reader",
"description": "View all resources",
"type": "BuiltInRole",
"permissions": [{"actions": ["*/read"], "notActions": []}],
"assignableScopes": ["/subscriptions/test-subscription-id"]
}
}
]
}));
let client = make_client(mock);
let rbac = client.rbac();
let result = rbac
.list_role_definitions()
.await
.expect("list_role_definitions failed");
assert_eq!(result.value.len(), 1);
let rd = &result.value[0];
assert_eq!(rd.name.as_deref(), Some(READER_GUID));
let props = rd.properties.as_ref().unwrap();
assert_eq!(props.role_name.as_deref(), Some("Reader"));
assert_eq!(props.r#type.as_deref(), Some("BuiltInRole"));
assert_eq!(props.permissions[0].actions, vec!["*/read"]);
assert_eq!(props.assignable_scopes.len(), 1);
}
#[tokio::test]
async fn get_role_definition_deserializes_permissions() {
let mut mock = MockClient::new();
mock.expect_get(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleDefinitions/{READER_GUID}"),
)
.returning_json(serde_json::json!({
"id": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
"name": "acdd72a7-3385-48ef-bd42-f606fba81ae7",
"type": "Microsoft.Authorization/roleDefinitions",
"properties": {
"roleName": "Reader",
"permissions": [
{"actions": ["*/read"], "notActions": [], "dataActions": [], "notDataActions": []}
],
"assignableScopes": ["/"]
}
}));
let client = make_client(mock);
let rd = client
.rbac()
.get_role_definition(READER_GUID)
.await
.expect("get_role_definition failed");
assert_eq!(rd.name.as_deref(), Some(READER_GUID));
let props = rd.properties.as_ref().unwrap();
assert_eq!(props.role_name.as_deref(), Some("Reader"));
assert_eq!(props.permissions.len(), 1);
assert_eq!(props.permissions[0].actions, vec!["*/read"]);
assert!(props.permissions[0].not_actions.is_empty());
}
#[tokio::test]
async fn list_role_assignments_returns_assignments() {
let mut mock = MockClient::new();
mock.expect_get(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleAssignments"),
)
.returning_json(serde_json::json!({
"value": [
{
"id": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleAssignments/00000000-0000-0000-0000-000000000001",
"name": "00000000-0000-0000-0000-000000000001",
"type": "Microsoft.Authorization/roleAssignments",
"properties": {
"roleDefinitionId": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
"principalId": "fa8b3842-840e-40cb-ae98-86f7d631e6c8",
"principalType": "User",
"scope": "/subscriptions/test-subscription-id"
}
}
]
}));
let client = make_client(mock);
let result = client
.rbac()
.list_role_assignments()
.await
.expect("list_role_assignments failed");
assert_eq!(result.value.len(), 1);
let a = &result.value[0];
assert_eq!(
a.name.as_deref(),
Some("00000000-0000-0000-0000-000000000001")
);
let props = a.properties.as_ref().unwrap();
assert_eq!(props.principal_type.as_deref(), Some("User"));
assert!(props.role_definition_id.is_some());
}
#[tokio::test]
async fn get_role_assignment_deserializes_properties() {
let mut mock = MockClient::new();
mock.expect_get(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleAssignments/{ASSIGNMENT_NAME}"),
)
.returning_json(serde_json::json!({
"id": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleAssignments/c1a2b3c4-d5e6-7f80-9abc-def012345678",
"name": "c1a2b3c4-d5e6-7f80-9abc-def012345678",
"type": "Microsoft.Authorization/roleAssignments",
"properties": {
"roleDefinitionId": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
"principalId": "fa8b3842-840e-40cb-ae98-86f7d631e6c8",
"principalType": "User",
"scope": "/subscriptions/test-subscription-id"
}
}));
let client = make_client(mock);
let a = client
.rbac()
.get_role_assignment(ASSIGNMENT_NAME)
.await
.expect("get_role_assignment failed");
assert_eq!(a.name.as_deref(), Some(ASSIGNMENT_NAME));
let props = a.properties.as_ref().unwrap();
assert_eq!(
props.principal_id.as_deref(),
Some("fa8b3842-840e-40cb-ae98-86f7d631e6c8")
);
assert_eq!(props.principal_type.as_deref(), Some("User"));
assert!(props.scope.is_some());
}
#[tokio::test]
async fn create_role_assignment_returns_assignment() {
let mut mock = MockClient::new();
mock.expect_put(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleAssignments/{ASSIGNMENT_NAME}"),
)
.returning_json(serde_json::json!({
"id": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleAssignments/c1a2b3c4-d5e6-7f80-9abc-def012345678",
"name": "c1a2b3c4-d5e6-7f80-9abc-def012345678",
"type": "Microsoft.Authorization/roleAssignments",
"properties": {
"roleDefinitionId": "/subscriptions/test-subscription-id/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7",
"principalId": "fa8b3842-840e-40cb-ae98-86f7d631e6c8",
"principalType": "User",
"scope": "/subscriptions/test-subscription-id"
}
}));
let client = make_client(mock);
use crate::types::rbac::{RoleAssignmentCreateRequest, RoleAssignmentRequestProperties};
let body = RoleAssignmentCreateRequest {
properties: RoleAssignmentRequestProperties {
role_definition_id: format!(
"/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleDefinitions/{READER_GUID}"
),
principal_id: "fa8b3842-840e-40cb-ae98-86f7d631e6c8".into(),
principal_type: Some("User".into()),
..Default::default()
},
};
let a = client
.rbac()
.create_role_assignment(ASSIGNMENT_NAME, &body)
.await
.expect("create_role_assignment failed");
assert_eq!(a.name.as_deref(), Some(ASSIGNMENT_NAME));
assert!(a.id.is_some());
let props = a.properties.as_ref().unwrap();
assert_eq!(
props.principal_id.as_deref(),
Some("fa8b3842-840e-40cb-ae98-86f7d631e6c8")
);
}
#[tokio::test]
async fn delete_role_assignment_succeeds() {
let mut mock = MockClient::new();
mock.expect_delete(
&format!("/subscriptions/{SUB_ID}/providers/Microsoft.Authorization/roleAssignments/{ASSIGNMENT_NAME}"),
)
.returning_json(serde_json::json!({}));
let client = make_client(mock);
client
.rbac()
.delete_role_assignment(ASSIGNMENT_NAME)
.await
.expect("delete_role_assignment failed");
}
}