diff --git a/examples/get_code_scanning_alerts.rs b/examples/get_code_scanning_alerts.rs new file mode 100644 index 00000000..d93a3477 --- /dev/null +++ b/examples/get_code_scanning_alerts.rs @@ -0,0 +1,61 @@ +use http::header::ACCEPT; +use octocrab::params::AlertState; +use octocrab::Octocrab; + +const OWNER: &str = "org"; +const REPO: &str = "some-repo"; + +#[tokio::main] +async fn main() { + // example for Code Scanning alerts API with OAuth GitHub App + let client_id = secrecy::SecretString::from(std::env::var("GITHUB_CLIENT_ID").unwrap()); + let crab = octocrab::Octocrab::builder() + .base_uri("https://github.com") + .unwrap() + .add_header(ACCEPT, "application/json".to_string()) + .build() + .unwrap(); + + let codes = crab + .authenticate_as_device(&client_id, ["security_events"]) + .await + .unwrap(); + println!( + "Go to {} and enter code {}", + codes.verification_uri, codes.user_code + ); + let auth = codes.poll_until_available(&crab, &client_id).await.unwrap(); + println!( + "Auth: scope {:?}; token type {}", + auth.scope, auth.token_type + ); + let octocrab = Octocrab::builder() + .oauth(auth) + .add_header(ACCEPT, "application/vnd.github+json".to_string()) + .build() + .unwrap(); + // Get all Code Scanning alerts for a repo + let a = octocrab + .code_scannings(OWNER.to_owned(), REPO.to_owned()) + .list() + .send() + .await + .unwrap(); + println!("{:?}", a); + // Get a single Code Scanning alert + let single_alert = octocrab + .code_scannings(OWNER.to_owned(), REPO.to_owned()) + .get(1) + .await + .unwrap(); + println!("{:?}", single_alert); + // Update (Open) a Code Scanning alert + let updated_alert = octocrab + .code_scannings(OWNER.to_owned(), REPO.to_owned()) + .update(1) + .state(AlertState::Open) + .send() + .await + .unwrap(); + println!("{:?}", updated_alert); +} diff --git a/examples/get_secret_scanning_alerts.rs b/examples/get_secret_scanning_alerts.rs new file mode 100644 index 00000000..bd0772e0 --- /dev/null +++ b/examples/get_secret_scanning_alerts.rs @@ -0,0 +1,69 @@ +use http::header::ACCEPT; +use octocrab::models::repos::secret_scanning_alert::UpdateSecretScanningAlert; +use octocrab::Octocrab; + +const OWNER: &str = "org"; +const REPO: &str = "some-repo"; + +#[tokio::main] +async fn main() { + // example for Secret Scanning alerts API with OAuth GitHub App + let client_id = secrecy::SecretString::from(std::env::var("GITHUB_CLIENT_ID").unwrap()); + let crab = octocrab::Octocrab::builder() + .base_uri("https://github.com") + .unwrap() + .add_header(ACCEPT, "application/json".to_string()) + .build() + .unwrap(); + + let codes = crab + .authenticate_as_device(&client_id, ["security_events"]) + .await + .unwrap(); + println!( + "Go to {} and enter code {}", + codes.verification_uri, codes.user_code + ); + let auth = codes.poll_until_available(&crab, &client_id).await.unwrap(); + println!( + "Auth: scope {:?}; token type {}", + auth.scope, auth.token_type + ); + let octocrab = Octocrab::builder() + .oauth(auth) + .add_header(ACCEPT, "application/vnd.github+json".to_string()) + .build() + .unwrap(); + // Get all Secret Scanning alerts for a repo + let a = octocrab + .repos(OWNER, REPO) + .secrets_scanning() + .direction("asc") + .get_alerts() + .await + .unwrap(); + println!("{:?}", a); + // Get a single Secret Scanning alert + let single_alert = octocrab + .repos(OWNER, REPO) + .secrets_scanning() + .get_alert(5) + .await + .unwrap(); + println!("{:?}", single_alert); + // Update (dismiss) a Secret Scanning alert + let updated_alert = octocrab + .repos(OWNER, REPO) + .secrets_scanning() + .update_alert( + 5, + Some(&UpdateSecretScanningAlert { + state: "resolved", + resolution: Some("used_in_tests"), + resolution_comment: Some("Mock value that is used in tests"), + }), + ) + .await + .unwrap(); + println!("{:?}", updated_alert); +} diff --git a/src/api/repos.rs b/src/api/repos.rs index 7ba50667..f39ea5c0 100644 --- a/src/api/repos.rs +++ b/src/api/repos.rs @@ -20,6 +20,7 @@ mod merges; mod pulls; pub mod release_assets; pub mod releases; +mod secret_scanning_alerts; mod secrets; mod stargazers; mod status; @@ -42,6 +43,7 @@ pub use merges::MergeBranchBuilder; pub use pulls::ListPullsBuilder; pub use release_assets::ReleaseAssetsHandler; pub use releases::ReleasesHandler; +pub use secret_scanning_alerts::RepoSecretScanningAlertsHandler; pub use secrets::RepoSecretsHandler; pub use stargazers::ListStarGazersBuilder; pub use status::{CreateStatusBuilder, ListStatusesBuilder}; @@ -755,6 +757,11 @@ impl<'octo> RepoHandler<'octo> { RepoDependabotAlertsHandler::new(self) } + /// Handle secrets scanning alerts on the repository + pub fn secrets_scanning(&self) -> RepoSecretScanningAlertsHandler<'_> { + RepoSecretScanningAlertsHandler::new(self) + } + /// Creates a new Git commit object. /// See https://docs.github.com/en/rest/git/commits?apiVersion=2022-11-28#create-a-commit /// ```no_run diff --git a/src/api/repos/secret_scanning_alerts.rs b/src/api/repos/secret_scanning_alerts.rs new file mode 100644 index 00000000..84aa9b5f --- /dev/null +++ b/src/api/repos/secret_scanning_alerts.rs @@ -0,0 +1,189 @@ +use super::RepoHandler; + +/// A client to GitHub's repository Secret Scanning API. +/// +/// Created with [`Octocrab::repos`]. +pub struct RepoSecretScanningAlertsHandler<'octo> { + handler: &'octo RepoHandler<'octo>, + params: Params, +} + +#[derive(serde::Serialize)] +struct Params { + #[serde(skip_serializing_if = "Option::is_none")] + per_page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + state: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + severity: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + ecosystem: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + package: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + manifest: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + scope: Option, + #[serde(skip_serializing_if = "Option::is_none")] + sort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + direction: Option, +} + +impl<'octo> RepoSecretScanningAlertsHandler<'octo> { + pub(crate) fn new(repo: &'octo RepoHandler<'octo>) -> Self { + Self { + handler: repo, + params: Params { + per_page: None, + page: None, + state: None, + severity: None, + ecosystem: None, + package: None, + manifest: None, + scope: None, + sort: None, + direction: None, + }, + } + } + + /// Lists all Secret Scanning Alerts available in a repository. + /// You must authenticate using an access token with the `repo` or `security_events` scope to use this endpoint. + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// # let octocrab = octocrab::Octocrab::default(); + /// let all_secrets = octocrab.repos("owner", "repo") + /// .secrets_scanning() + /// .direction("asc") + /// .get_alerts() + /// .await?; + /// # Ok(()) + /// # } + pub async fn get_alerts( + &self, + ) -> crate::Result> + { + let route = format!("/{}/secret-scanning/alerts", self.handler.repo); + self.handler.crab.get(route, Some(&self.params)).await + } + + /// Results per page (max 100). + pub fn per_page(mut self, per_page: impl Into) -> Self { + self.params.per_page = Some(per_page.into()); + self + } + + /// Page number of the results to fetch. + pub fn page(mut self, page: impl Into) -> Self { + self.params.page = Some(page.into()); + self + } + + /// Filter Secret Scanning Alerts by state. + pub fn state(mut self, state: impl Into>) -> Self { + self.params.state = Some(state.into()); + self + } + + /// Filter Secret Scanning Alerts by severity. + pub fn severity(mut self, severity: impl Into>) -> Self { + self.params.severity = Some(severity.into()); + self + } + + /// Filter Secret Scanning Alerts by ecosystem. + pub fn ecosystem(mut self, ecosystem: impl Into>) -> Self { + self.params.ecosystem = Some(ecosystem.into()); + self + } + + /// Filter Secret Scanning Alerts by package. + pub fn package(mut self, package: impl Into>) -> Self { + self.params.package = Some(package.into()); + self + } + + /// Filter Secret Scanning Alerts by manifest. + pub fn manifest(mut self, manifest: impl Into>) -> Self { + self.params.manifest = Some(manifest.into()); + self + } + + /// Filter Secret Scanning Alerts by scope. + pub fn scope(mut self, scope: impl Into) -> Self { + self.params.scope = Some(scope.into()); + self + } + + /// Sort Secret Scanning Alerts. + pub fn sort(mut self, sort: impl Into) -> Self { + self.params.sort = Some(sort.into()); + self + } + + /// Sort direction of Secret Scanning Alerts. + pub fn direction(mut self, direction: impl Into) -> Self { + self.params.direction = Some(direction.into()); + self + } + + /// Lists single Secret Scanning Alert for a repository. + /// You must authenticate using an access token with the `repo` or `security_events` scope to use this endpoint. + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// # let octocrab = octocrab::Octocrab::default(); + /// let all_secrets = octocrab.repos("owner", "repo") + /// .secrets_scanning() + /// .get_alert(5) + /// .await?; + /// # Ok(()) + /// # } + pub async fn get_alert( + &self, + alert_number: u32, + ) -> crate::Result { + let route = format!( + "/{}/secret-scanning/alerts/{}", + self.handler.repo, alert_number + ); + self.handler.crab.get(route, None::<&()>).await + } + + /// Updates a Secret Scanning alert. + /// You must authenticate using an access token with the `security_events ` scope to use this endpoint. + /// ```no_run + /// # async fn run() -> octocrab::Result<()> { + /// # let octocrab = octocrab::Octocrab::default(); + /// use octocrab::models::repos::secret_scanning_alert::UpdateSecretScanningAlert; + /// + /// let result = octocrab.repos("owner", "repo") + /// .secrets_scanning() + /// .update_alert( + /// 5, + /// Some(&UpdateSecretScanningAlert { + /// state: "dismissed", + /// dismissed_reason: Some("no_bandwidth"), + /// dismissed_comment: Some("I don't have time to fix this right now"), + /// }) + /// ) + /// .await?; + /// # Ok(()) + /// # } + pub async fn update_alert( + &self, + alert_number: u32, + alert_update: Option< + &crate::models::repos::secret_scanning_alert::UpdateSecretScanningAlert<'_>, + >, + ) -> crate::Result { + let route = format!( + "/{}/secret-scanning/alerts/{}", + self.handler.repo, alert_number + ); + self.handler.crab.patch(route, alert_update).await + } +} diff --git a/src/models/code_scannings.rs b/src/models/code_scannings.rs index ecda9642..c3665539 100644 --- a/src/models/code_scannings.rs +++ b/src/models/code_scannings.rs @@ -33,6 +33,7 @@ pub struct CodeScanningAlert { pub enum CodeScanningState { Open, Dismissed, + Fixed, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -50,6 +51,7 @@ pub enum DismissedReason { #[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)] #[non_exhaustive] pub struct Dismisser { + // isn't is the same as SimpleUser? pub login: String, pub id: UserId, pub node_id: String, @@ -80,14 +82,33 @@ pub struct Rule { pub id: Option, pub name: String, #[serde(skip_serializing_if = "Option::is_none")] - pub severity: Option, + pub severity: Option, /* per documentation is is an enum, should we change the type?: + "description": "The severity of the alert.", + "enum": [ + "none", + "note", + "warning", + "error", + null + ]*/ pub description: String, #[serde(skip_serializing_if = "Option::is_none")] pub full_description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub tags: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub security_severity_level: Option, + pub security_severity_level: Option, /* per documentation it is enum, shoud we change it?: + "enum": [ + "low", + "medium", + "high", + "critical", + null + ] */ + #[serde(skip_serializing_if = "Option::is_none")] + pub help: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub help_uri: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -108,11 +129,24 @@ pub struct MostRecentInstance { pub analysis_key: String, pub environment: String, pub category: String, - pub state: String, + pub state: String, /* change to enum? doc: + "enum": [ + "open", + "dismissed", + "fixed", + null + ] */ pub commit_sha: String, pub message: Message, pub location: Location, - pub classifications: Vec, + pub classifications: Vec, /* change to enum? doc: + "enum": [ + "source", + "generated", + "test", + "library", + null + ] */ } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/models/repos.rs b/src/models/repos.rs index 7f12a882..f18f6ef8 100644 --- a/src/models/repos.rs +++ b/src/models/repos.rs @@ -8,6 +8,7 @@ use snafu::ResultExt; use url::Url; pub mod dependabot; +pub mod secret_scanning_alert; pub mod secrets; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/src/models/repos/code_scanning_alert.rs b/src/models/repos/code_scanning_alert.rs deleted file mode 100644 index b929506c..00000000 --- a/src/models/repos/code_scanning_alert.rs +++ /dev/null @@ -1,115 +0,0 @@ -use super::super::*; - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct CodeScanningAlert { - pub number: i64, - pub created_at: DateTime, - pub updated_at: DateTime, - pub url: Url, - pub html_url: Url, - pub instances_url: Url, - pub state: Option, - pub fixed_at: DateTime, - pub dismissed_by: Option, - pub dismissed_at: Option>, - pub dismissed_reason: Option, - pub dismissed_comment: Option, - pub rule: Rule, - pub tool: Tool, - pub most_recent_instance: MostRecentInstance, - pub instances_url: Url, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum State { - Dismissed, - Open, - Fixed, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum DismissedReason { - #[serde(rename = "false positive")] - FalsePositive, - #[serde(rename = "won't fix")] - WontFix, - #[serde(rename = "used in tests")] - UsedInTests, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Rule { - pub id: Option, - pub severity: Option, - pub security_severity_level: Option - pub tags: Vec, - pub description: String, - pub full_description: Option, - pub name: String, - pub help: Option, - pub help_uri: Option, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum Severity { - None, - Note, - Warning, - Error, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum SecuritySeverityLevel { - Low, - Medium, - High, - Critical, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Tool { - pub name: String, - pub guid: Option, - pub version: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct MostRecentInstance { - pub r#ref: String, - pub analysis_key: String, - pub category: String, - pub environment: String, - pub state: Option, - pub commit_sha: String, - pub message: Message, - pub location: Location, - pub html_url: Option, - pub classifications: Option>, -} - - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Message { - pub text: String, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Location { - pub path: String, - pub start_line: i64, - pub end_line: i64, - pub start_column: i64, - pub end_column: i64, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum Classifications { - Source, - Generated, - Test, - Library, -} \ No newline at end of file diff --git a/src/models/repos/dependabot.rs b/src/models/repos/dependabot.rs index 8a15a5ef..b31540f6 100644 --- a/src/models/repos/dependabot.rs +++ b/src/models/repos/dependabot.rs @@ -19,7 +19,6 @@ pub struct DependabotAlert { pub auto_dismissed_at: Option>, } - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum DissmisedReason { diff --git a/src/models/repos/secret_scanning_alert.rs b/src/models/repos/secret_scanning_alert.rs index 5f003818..656f9721 100644 --- a/src/models/repos/secret_scanning_alert.rs +++ b/src/models/repos/secret_scanning_alert.rs @@ -46,4 +46,13 @@ pub enum Validity { Active, Inactive, Unknown, -} \ No newline at end of file +} + +#[derive(Debug, Clone, PartialEq, Serialize)] +pub struct UpdateSecretScanningAlert<'a> { + pub state: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + pub resolution_comment: Option<&'a str>, +}