DELETE /v1/hosts/id

This commit is contained in:
core 2023-05-08 21:46:33 -04:00
parent 4ca0b54686
commit eaa1e86fef
Signed by: core
GPG Key ID: FDBF740DADDCEECF
2 changed files with 222 additions and 2 deletions

View File

@ -98,6 +98,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.service(routes::v1::hosts::get_hosts)
.service(routes::v1::hosts::create_hosts_request)
.service(routes::v1::hosts::get_host)
.service(routes::v1::hosts::delete_host)
}).bind(CONFIG.server.bind)?.run().await?;
Ok(())

View File

@ -28,15 +28,20 @@
// This endpoint has full parity with the original API. It has been recreated from the original API documentation.
// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs.
// This endpoint requires the `definednetworking` extension to be enabled to be used.
//
//#DELETE /v1/hosts/{host_id} t+parity:full t+type:documented t+status:done t+feature:definednetworking
// This endpoint has full parity with the original API. It has been recreated from the original API documentation.
// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs.
// This endpoint requires the `definednetworking` extension to be enabled to be used.
use std::net::{Ipv4Addr, SocketAddrV4};
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{HttpRequest, HttpResponse, get, post};
use actix_web::{HttpRequest, HttpResponse, get, post, delete};
use actix_web::web::{Data, Json, Path, Query};
use chrono::{TimeZone, Utc};
use log::error;
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait};
use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait, ModelTrait};
use serde::{Serialize, Deserialize};
use trifid_api_entities::entity::{host, host_static_address, network, organization};
use crate::AppState;
@ -825,3 +830,217 @@ pub async fn get_host(id: Path<String>, req_info: HttpRequest, db: Data<AppState
metadata: GetHostResponseMetadata {},
})
}
#[derive(Serialize, Deserialize)]
pub struct DeleteHostResponse {
pub data: DeleteHostData,
pub metadata: DeleteHostMetadata
}
#[derive(Serialize, Deserialize)]
pub struct DeleteHostData {}
#[derive(Serialize, Deserialize)]
pub struct DeleteHostMetadata {}
#[delete("/v1/hosts/{host_id}")]
pub async fn delete_host(id: Path<String>, req_info: HttpRequest, db: Data<AppState>) -> HttpResponse {
// For this endpoint, you either need to be a fully authenticated user OR a token with hosts:delete
let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent);
let api_token_info = enforce_api_token(&req_info, &["hosts:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent);
// If neither are present, throw an error
if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) {
return HttpResponse::Unauthorized().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_UNAUTHORIZED".to_string(),
message: "This endpoint requires either a fully authenticated user or a token with the hosts:delete scope".to_string(),
path: None,
}
],
})
}
// If both are present, throw an error
if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) {
return HttpResponse::BadRequest().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(),
message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(),
path: None
}
],
})
}
let org_id = match api_token_info {
TokenInfo::ApiToken(tkn) => tkn.organization,
_ => {
// we have a session token, which means we have to do a db request to get the organization that this user owns
let user = match session_info {
TokenInfo::AuthToken(tkn) => tkn.session_info.user,
_ => unreachable!()
};
let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await {
Ok(r) => r,
Err(e) => {
error!("database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error performing the database request, please try again later.".to_string(),
path: None,
}
],
});
}
};
if let Some(org) = org {
org.id
} else {
return HttpResponse::Unauthorized().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_NO_ORG".to_string(),
message: "This user does not own any organizations. Try using an API token instead.".to_string(),
path: None
}
],
})
}
}
};
let net_id;
let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await {
Ok(r) => r,
Err(e) => {
error!("database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error performing the database request, please try again later.".to_string(),
path: None,
}
],
});
}
};
if let Some(net) = net {
net_id = net.id;
} else {
return HttpResponse::Unauthorized().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_NO_NET".to_string(),
message: "This user does not own any networks. Try using an API token instead.".to_string(),
path: None
}
],
})
}
let host = match host::Entity::find().filter(host::Column::Id.eq(id.into_inner())).one(&db.conn).await {
Ok(h) => h,
Err(e) => {
error!("Database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error with the database query. Please try again later.".to_string(),
path: None
}
],
})
}
};
let host = match host {
Some(h) => h,
None => {
return HttpResponse::Unauthorized().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_UNAUTHORIZED".to_string(),
message: "This resource does not exist or you do not have permission to access it.".to_string(),
path: None
}
],
})
}
};
if host.network != net_id {
return HttpResponse::Unauthorized().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_UNAUTHORIZED".to_string(),
message: "This resource does not exist or you do not have permission to access it.".to_string(),
path: None
}
],
})
}
let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await {
Ok(h) => h,
Err(e) => {
error!("Database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error with the database query. Please try again later.".to_string(),
path: None
}
],
})
}
};
match host.delete(&db.conn).await {
Ok(_) => (),
Err(e) => {
error!("Database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error with the database query. Please try again later.".to_string(),
path: None
}
],
})
}
}
for address in static_addresses {
match address.delete(&db.conn).await {
Ok(_) => (),
Err(e) => {
error!("Database error: {}", e);
return HttpResponse::InternalServerError().json(APIErrorsResponse {
errors: vec![
APIError {
code: "ERR_DB_ERROR".to_string(),
message: "There was an error with the database query. Please try again later.".to_string(),
path: None
}
],
})
}
}
}
HttpResponse::Ok().json(DeleteHostResponse {
data: DeleteHostData {},
metadata: DeleteHostMetadata {},
})
}