cloud_terrastodon_entrypoint 0.36.0

Main entrypoint for the Cloud Terrastodon CLI
use cloud_terrastodon_azure::AzureTenantId;
use cloud_terrastodon_azure::create_oauth2_permission_grant;
use cloud_terrastodon_azure::fetch_all_entra_users;
use cloud_terrastodon_azure::fetch_all_service_principals;
use cloud_terrastodon_azure::fetch_oauth2_permission_grants;
use cloud_terrastodon_azure::fetch_oauth2_permission_scopes;
use cloud_terrastodon_azure::find_matching_oauth2_permission_grant;
use cloud_terrastodon_azure::join_oauth2_permission_grant_scopes;
use cloud_terrastodon_azure::merge_oauth2_permission_grant_scopes;
use cloud_terrastodon_azure::update_oauth2_permission_grant;
use cloud_terrastodon_user_input::Choice;
use cloud_terrastodon_user_input::PickerTui;
use eyre::Result;
use std::collections::HashSet;
use tracing::info;

pub async fn create_oauth2_permission_grants(tenant_id: AzureTenantId) -> Result<()> {
    info!("Fetching all service principals");
    let service_principals = fetch_all_service_principals(tenant_id).await?;
    let resource = PickerTui::new()
        .set_header("Pick the underlying resource being granted access to")
        .set_query("'Microsoft\\ Graph")
        .pick_one(service_principals.iter().map(|sp| Choice {
            key: format!("{} - {}", sp.id, sp.display_name),
            value: sp,
        }))?;
    info!("You chose: {} - {}", resource.display_name, resource.id);

    let client = PickerTui::new()
        .set_header("Pick the client accessing the resource")
        .set_query("'Graph\\ Explorer")
        .pick_one(service_principals.iter().map(|sp| Choice {
            key: format!("{} - {}", sp.id, sp.display_name),
            value: sp,
        }))?;
    info!("You chose: {} - {}", client.display_name, client.id);

    let scopes = fetch_oauth2_permission_scopes(resource.id)
        .await?
        .into_iter()
        .collect::<HashSet<_>>();

    let scopes_to_add = PickerTui::new()
        .set_header("Select the scopes you want to grant")
        .pick_many(scopes.iter().map(|scope| Choice {
            key: format!("{} - {}", scope.value, scope.user_consent_display_name),
            value: scope,
        }))?;

    let users = fetch_all_entra_users(tenant_id).await?;
    let users_to_add = PickerTui::new()
        .set_header(format!(
            "Select the users to add {} grants to",
            scopes_to_add.len()
        ))
        .pick_many(users.iter().map(|user| Choice {
            key: user.to_string(),
            value: user,
        }))?;

    let mut existing_grants = fetch_oauth2_permission_grants(tenant_id).await?;
    let requested_scope =
        join_oauth2_permission_grant_scopes(scopes_to_add.iter().map(|scope| scope.value.as_str()));

    for user in users_to_add {
        if let Some(existing) = find_matching_oauth2_permission_grant(
            &mut existing_grants,
            resource.id,
            client.id,
            user.id,
        ) {
            let new_scope = merge_oauth2_permission_grant_scopes(
                &existing.scope,
                requested_scope.split_ascii_whitespace(),
                std::iter::empty(),
            );
            if new_scope == existing.scope {
                println!("Grant already contains requested scopes for {}", user);
                continue;
            }

            println!("Updating scopes for {}", user);
            let () =
                update_oauth2_permission_grant(tenant_id, existing.id.clone(), new_scope.clone())
                    .await?;
            existing.scope = new_scope;
        } else {
            println!("Creating grant for {}", user);
            let created = create_oauth2_permission_grant(
                tenant_id,
                resource.id,
                client.id,
                user.id,
                requested_scope.clone(),
            )
            .await?;
            existing_grants.push(created);
        }
    }

    Ok(())
}