attackerkb_api_rs/
lib.rs

1//! A guide to the public REST API for AttackerKB. To generate an API key, navigate to the API tab on your AttackerKB Profile Page.
2//!  For more details on the API referer to <https://extensions.rapid7.com/extension/rapid7-attackerkb>
3use crate::error::{Error, ErrorResponse, StatusCode};
4use crate::pagination::KBResponse;
5use crate::v1::query::{AssessmentsParameters, ContributorsParameters, TopicsParameters};
6use reqwest::{header, ClientBuilder, RequestBuilder};
7use std::fmt::Display;
8
9pub mod error;
10pub mod pagination;
11pub mod v1;
12
13const BASE_URL: &str = "https://api.attackerkb.com";
14
15/// API client, API token needs to be provided when creating a new instance.
16#[derive(Debug, Clone)]
17pub struct AttackKBApi {
18  base_path: String,
19  version: ApiVersion,
20  client: reqwest::Client,
21}
22
23/// ApiVersion default: v1
24#[derive(Debug, Clone)]
25pub enum ApiVersion {
26  V1,
27}
28
29impl Default for ApiVersion {
30  fn default() -> Self {
31    Self::V1
32  }
33}
34
35impl Display for ApiVersion {
36  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37    write!(
38      f,
39      "{}",
40      match self {
41        ApiVersion::V1 => String::from("v1"),
42      }
43    )
44  }
45}
46
47impl AttackKBApi {
48  pub fn new(api_token: Option<impl Into<String>>) -> Result<Self, Error> {
49    let mut headers = reqwest::header::HeaderMap::new();
50    if let Some(api_token) = api_token {
51      let mut auth_value =
52        reqwest::header::HeaderValue::from_str(&format!("basic {}", api_token.into()))
53          .map_err(|source| Error::InvalidApiToken { source })?;
54      auth_value.set_sensitive(true);
55      headers.insert(
56        header::ACCEPT,
57        header::HeaderValue::from_static("application/json"),
58      );
59      headers.insert(header::AUTHORIZATION, auth_value);
60    }
61    let api_client = ClientBuilder::new()
62      .default_headers(headers)
63      .build()
64      .map_err(|source| Error::BuildingClient { source })?;
65    Ok(AttackKBApi {
66      base_path: BASE_URL.to_owned(),
67      version: ApiVersion::default(),
68      client: api_client,
69    })
70  }
71}
72
73impl AttackKBApi {
74  async fn request(&self, request: RequestBuilder) -> Result<KBResponse, Error> {
75    let request = request.build()?;
76    let resp = self
77      .client
78      .execute(request)
79      .await
80      .map_err(|source| Error::RequestFailed { source })?;
81    if !resp.status().is_success() {
82      return Err(Error::Api {
83        error: ErrorResponse {
84          message: "not success request".to_string(),
85          status: StatusCode::NonZeroU16(resp.status().as_u16()),
86        },
87      });
88    };
89    let result = resp
90      .json()
91      .await
92      .map_err(|source| Error::ResponseIo { source })?;
93    // let result = serde_json::from_str(&json).unwrap();
94    if let KBResponse::Error(err) = &result {
95      return Err(Error::Api { error: err.clone() });
96    }
97    Ok(result)
98  }
99}
100
101impl AttackKBApi {
102  /// Return all topics.
103  pub async fn topics(&self, query: &TopicsParameters) -> Result<KBResponse, Error> {
104    let u = format!("{}/{}/{}", self.base_path, self.version, "topics");
105    self.request(self.client.get(u).query(&query)).await
106  }
107  /// Return a specific topic.
108  pub async fn topic(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
109    let u = format!(
110      "{}/{}/{}/{}",
111      self.base_path,
112      self.version,
113      "topics",
114      id.into()
115    );
116    self.request(self.client.get(u)).await
117  }
118  /// Return all assessments.
119  pub async fn assessments(&self, query: &AssessmentsParameters) -> Result<KBResponse, Error> {
120    let u = format!("{}/{}/{}", self.base_path, self.version, "assessments");
121    self.request(self.client.get(u).query(&query)).await
122  }
123  /// Return a specific assessment.
124  pub async fn assessment(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
125    let u = format!(
126      "{}/{}/{}/{}",
127      self.base_path,
128      self.version,
129      "assessments",
130      id.into()
131    );
132    self.request(self.client.get(u)).await
133  }
134  /// Return all contributors.
135  pub async fn contributors(&self, query: &ContributorsParameters) -> Result<KBResponse, Error> {
136    let u = format!("{}/{}/{}", self.base_path, self.version, "contributors");
137    self.request(self.client.get(u).query(&query)).await
138  }
139  /// Return a specific contributor.
140  pub async fn contributor(&self, id: impl Into<String>) -> Result<KBResponse, Error> {
141    let u = format!(
142      "{}/{}/{}/{}",
143      self.base_path,
144      self.version,
145      "contributors",
146      id.into()
147    );
148    self.request(self.client.get(u)).await
149  }
150}
151
152#[cfg(test)]
153mod tests {
154  #[test]
155  fn it_works() {
156    let result = 2 + 2;
157    assert_eq!(result, 4);
158  }
159}