Skip to content

Commit

Permalink
added org secrets api (#384)
Browse files Browse the repository at this point in the history
  • Loading branch information
pigeonhands authored Jun 3, 2023
1 parent e192894 commit c45dbf2
Show file tree
Hide file tree
Showing 10 changed files with 487 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ tokio = { version = "1.17.0", default-features = false, features = [
] }
tokio-test = "0.4.2"
wiremock = "0.5.3"
crypto_box = { version = "0.8.2", features = ["seal"] }
base64 = "0.21.2"

[features]
default = ["rustls", "timeout", "tracing", "retry"]
Expand Down
42 changes: 42 additions & 0 deletions examples/create_org_secret.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use base64::{engine::general_purpose::STANDARD as B64, Engine};
use crypto_box::{self, aead::OsRng, PublicKey};
use octocrab::{
models::orgs::secrets::{CreateOrganizationSecret, Visibility},
Octocrab,
};
use std::convert::TryInto;

#[tokio::main]
async fn main() -> octocrab::Result<()> {
let token = std::env::var("GITHUB_TOKEN").expect("GITHUB_TOKEN env variable is required");

let octocrab = Octocrab::builder().personal_token(token).build()?;
let org = octocrab.orgs("owner");
let secrets = org.secrets();

let public_key = secrets.get_public_key().await?;

let crypto_pk = {
let org_pk_bytes = B64.decode(public_key.key).unwrap();
let pk_array: [u8; crypto_box::KEY_SIZE] = org_pk_bytes.try_into().unwrap();
PublicKey::from(pk_array)
};

let encrypted_value = crypto_box::seal(&mut OsRng, &crypto_pk, b"Very secret value").unwrap();

let result = secrets
.create_or_update_secret(
"TEST_SECRET_RS",
&CreateOrganizationSecret {
encrypted_value: &B64.encode(encrypted_value),
key_id: &public_key.key_id,
visibility: Visibility::Private,
selected_repository_ids: None,
},
)
.await?;

println!("{:?}", result);

Ok(())
}
9 changes: 9 additions & 0 deletions src/api/orgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod events;
mod list_members;
mod list_repos;
mod secrets;

use crate::error::HttpSnafu;
use crate::Octocrab;
Expand All @@ -12,6 +13,7 @@ use snafu::ResultExt;
pub use self::events::ListOrgEventsBuilder;
pub use self::list_members::ListOrgMembersBuilder;
pub use self::list_repos::ListReposBuilder;
pub use self::secrets::OrgSecretsHandler;

/// A client to GitHub's organization API.
///
Expand Down Expand Up @@ -220,4 +222,11 @@ impl<'octo> OrgHandler<'octo> {
pub fn list_members(&self) -> list_members::ListOrgMembersBuilder {
list_members::ListOrgMembersBuilder::new(self)
}

/// Handle secrets on the organizaton
/// ```no_run
/// ```
pub fn secrets(&self) -> secrets::OrgSecretsHandler<'_> {
secrets::OrgSecretsHandler::new(self)
}
}
161 changes: 161 additions & 0 deletions src/api/orgs/secrets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
use http::StatusCode;
use snafu::GenerateImplicitData;

use super::OrgHandler;
use crate::models::orgs::secrets::{CreateOrganizationSecret, CreateOrganizationSecretResponse};

/// A client to GitHub's organization API.
///
/// Created with [`Octocrab::orgs`].
pub struct OrgSecretsHandler<'octo> {
org: &'octo OrgHandler<'octo>,
}

impl<'octo> OrgSecretsHandler<'octo> {
pub(crate) fn new(org: &'octo OrgHandler<'octo>) -> Self {
Self { org }
}

fn owner(&self) -> &String {
&self.org.owner
}

/// Lists all secrets available in an organization without revealing their encrypted values.
/// You must authenticate using an access token with the admin:org scope to use this endpoint.
/// GitHub Apps must have the secrets organization permission to use this endpoint.
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// # let octocrab = octocrab::Octocrab::default();
/// let org = octocrab.orgs("owner");
/// let secrets = org.secrets();
/// let all_secrets = secrets.get_secrets().await?;
/// # Ok(())
/// # }
pub async fn get_secrets(
&self,
) -> crate::Result<crate::models::orgs::secrets::OrganizationSecrets> {
let route = format!("/orgs/{org}/actions/secrets", org = self.owner());
self.org.crab.get(route, None::<&()>).await
}

// Gets your public key, which you need to encrypt secrets. You need to encrypt a secret before you can create or update secrets.
// You must authenticate using an access token with the admin:org scope to use this endpoint.
// GitHub Apps must have the secrets organization permission to use this endpoint.
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// # let octocrab = octocrab::Octocrab::default();
/// let org = octocrab.orgs("owner");
/// let secrets = org.secrets();
/// let public_key = secrets.get_public_key().await?;
/// # Ok(())
/// # }
pub async fn get_public_key(&self) -> crate::Result<crate::models::PublicKey> {
let route = format!("/orgs/{org}/actions/secrets/public-key", org = self.owner());
self.org.crab.get(route, None::<&()>).await
}

/// Gets a specific secret from the organization without revealing its encrypted values.
/// You must authenticate using an access token with the admin:org scope to use this endpoint.
/// GitHub Apps must have the secrets organization permission to use this endpoint.
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// # let octocrab = octocrab::Octocrab::default();
/// let org = octocrab.orgs("owner");
/// let secrets = org.secrets();
/// let secret_info = secrets.get_secret("TOKEN").await?;
/// # Ok(())
/// # }
pub async fn get_secret(
&self,
secret_name: impl AsRef<str>,
) -> crate::Result<crate::models::orgs::secrets::OrganizationSecret> {
let route = format!(
"/orgs/{org}/actions/secrets/{secret_name}",
org = self.owner(),
secret_name = secret_name.as_ref()
);
self.org.crab.get(route, None::<&()>).await
}

/// Creates or updates an organization secret with an encrypted value.
/// Encrypt your secret using [`crypto_box`](https://crates.io/crates/crypto_box).
/// You must authenticate using an access token with the admin:org scope to use this endpoint.
/// GitHub Apps must have the secrets organization permission to use this endpoint
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// # let octocrab = octocrab::Octocrab::default();
/// use octocrab::models::orgs::secrets::{
/// CreateOrganizationSecret, CreateOrganizationSecretResponse,
/// Visibility
/// };
///
/// let org = octocrab.orgs("owner");
/// let secrets = org.secrets();
/// let result = secrets.create_or_update_secret("GH_TOKEN", &CreateOrganizationSecret{
/// key_id: "123456",
/// encrypted_value: "some-b64-encrypted-string",
/// visibility: Visibility::Selected,
/// selected_repository_ids: None,
/// }).await?;
///
/// match result {
/// CreateOrganizationSecretResponse::Created => println!("Created secret!"),
/// CreateOrganizationSecretResponse::Updated => println!("Updated secret!"),
/// }
/// # Ok(())
/// # }
pub async fn create_or_update_secret(
&self,
secret_name: impl AsRef<str>,
secret: &CreateOrganizationSecret<'_>,
) -> crate::Result<crate::models::orgs::secrets::CreateOrganizationSecretResponse> {
let route = format!(
"/orgs/{org}/actions/secrets/{secret_name}",
org = self.owner(),
secret_name = secret_name.as_ref()
);

let resp = {
let resp = self.org.crab._put(route, Some(secret)).await?;
crate::map_github_error(resp).await?
};

match resp.status() {
StatusCode::CREATED => Ok(CreateOrganizationSecretResponse::Created),
StatusCode::NO_CONTENT => Ok(CreateOrganizationSecretResponse::Updated),
status_code => Err(crate::Error::Other {
source: format!(
"Unexpected status code from request: {}",
status_code.as_str()
)
.into(),
backtrace: snafu::Backtrace::generate(),
}),
}
}

/// Deletes an organization secret.
/// You must authenticate using an access token with the admin:org scope to use this endpoint.
/// GitHub Apps must have the secrets organization permission to use this endpoint
/// ```no_run
/// # async fn run() -> octocrab::Result<()> {
/// # let octocrab = octocrab::Octocrab::default();
/// let org = octocrab.orgs("owner");
/// let secrets = org.secrets();
///
/// secrets.delete_secret("GH_TOKEN").await?;
///
/// # Ok(())
/// # }
pub async fn delete_secret(&self, secret_name: impl AsRef<str>) -> crate::Result<()> {
let route = format!(
"/orgs/{org}/actions/secrets/{secret_name}",
org = self.owner(),
secret_name = secret_name.as_ref()
);

let resp = self.org.crab._delete(route, None::<&()>).await?;
crate::map_github_error(resp).await?;
Ok(())
}
}
1 change: 1 addition & 0 deletions src/models/orgs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
pub mod secrets;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
Expand Down
48 changes: 48 additions & 0 deletions src/models/orgs/secrets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::super::*;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Visibility {
All,
Private,
Selected,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrganizationSecret {
pub name: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub visibility: Visibility,
#[serde(skip_serializing_if = "Option::is_none")]
pub selected_repositories_url: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct OrganizationSecrets {
pub total_count: i32,
pub secrets: Vec<OrganizationSecret>,
}

#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct CreateOrganizationSecret<'a> {
/// Value for your secret,
/// encrypted with LibSodium using the public key retrieved from the Get an organization public key endpoint.
pub encrypted_value: &'a str,
/// ID of the key you used to encrypt the secret.
pub key_id: &'a str,
/// Which type of organization repositories have access to the organization secret.
pub visibility: Visibility,
/// An array of repository ids that can access the organization secret.
/// You can only provide a list of repository ids when the visibility is set to selected.
#[serde(skip_serializing_if = "Option::is_none")]
pub selected_repository_ids: Option<&'a [u32]>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CreateOrganizationSecretResponse {
Created,
Updated,
}
Loading

0 comments on commit c45dbf2

Please sign in to comment.