PUT /v1/roles/roleid
This commit is contained in:
parent
a5a21ba75b
commit
458cd57519
|
@ -93,6 +93,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.service(routes::v1::roles::get_roles)
|
.service(routes::v1::roles::get_roles)
|
||||||
.service(routes::v1::roles::get_role)
|
.service(routes::v1::roles::get_role)
|
||||||
.service(routes::v1::roles::delete_role)
|
.service(routes::v1::roles::delete_role)
|
||||||
|
.service(routes::v1::roles::update_role_request)
|
||||||
.service(routes::v1::trifid::trifid_extensions)
|
.service(routes::v1::trifid::trifid_extensions)
|
||||||
}).bind(CONFIG.server.bind)?.run().await?;
|
}).bind(CONFIG.server.bind)?.run().await?;
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,12 @@
|
||||||
//#DELETE /v1/roles/{role_id} t+parity:full t+type:documented t+status:done
|
//#DELETE /v1/roles/{role_id} t+parity:full t+type:documented t+status:done
|
||||||
// This endpoint has full parity with the original API. It has been recreated from the original API documentation.
|
// 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 is considered done. No major features should be added or removed, unless it fixes bugs.
|
||||||
|
//#PUT /v1/roles/{role_id} t+parity:full t+type:documented t+status:done
|
||||||
|
// 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.
|
||||||
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use actix_web::{get, HttpRequest, HttpResponse, post};
|
use actix_web::{get, HttpRequest, HttpResponse, post, put};
|
||||||
use actix_web::web::{Data, Json, Path, Query};
|
use actix_web::web::{Data, Json, Path, Query};
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
@ -51,6 +54,7 @@ use actix_web::delete;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct CreateRoleRequest {
|
pub struct CreateRoleRequest {
|
||||||
|
#[serde(default)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub description: String,
|
pub description: String,
|
||||||
|
@ -58,6 +62,15 @@ pub struct CreateRoleRequest {
|
||||||
pub firewall_rules: Vec<RoleFirewallRule>
|
pub firewall_rules: Vec<RoleFirewallRule>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct UpdateRoleRequest {
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub description: String,
|
||||||
|
#[serde(default, rename = "firewallRules")]
|
||||||
|
pub firewall_rules: Vec<RoleFirewallRule>
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RoleFirewallRule {
|
pub struct RoleFirewallRule {
|
||||||
pub protocol: RoleProtocol,
|
pub protocol: RoleProtocol,
|
||||||
|
@ -663,7 +676,7 @@ pub struct GetRoleResponse {
|
||||||
pub struct GetRoleResponseMetadata {}
|
pub struct GetRoleResponseMetadata {}
|
||||||
|
|
||||||
#[delete("/v1/roles/{role_id}")]
|
#[delete("/v1/roles/{role_id}")]
|
||||||
pub async fn delete_role(net: Path<String>, req_info: HttpRequest, db: Data<AppState>) -> HttpResponse {
|
pub async fn delete_role(role: 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 roles:delete
|
// For this endpoint, you either need to be a fully authenticated user OR a token with roles:delete
|
||||||
let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent);
|
let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent);
|
||||||
let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent);
|
let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
@ -694,7 +707,7 @@ pub async fn delete_role(net: Path<String>, req_info: HttpRequest, db: Data<AppS
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let role: Option<role::Model> = match role::Entity::find().filter(role::Column::Id.eq(net.into_inner())).one(&db.conn).await {
|
let role: Option<role::Model> = match role::Entity::find().filter(role::Column::Id.eq(role.into_inner())).one(&db.conn).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database error: {}", e);
|
error!("database error: {}", e);
|
||||||
|
@ -755,3 +768,184 @@ pub struct RoleDeleteResponseData {}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RoleDeleteResponseMetadata {}
|
pub struct RoleDeleteResponseMetadata {}
|
||||||
|
|
||||||
|
|
||||||
|
#[put("/v1/roles/{role_id}")]
|
||||||
|
pub async fn update_role_request(role: Path<String>, req: Json<CreateRoleRequest>, req_info: HttpRequest, db: Data<AppState>) -> HttpResponse {
|
||||||
|
// For this endpoint, you either need to be a fully authenticated user OR a token with roles:create
|
||||||
|
let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent);
|
||||||
|
let api_token_info = enforce_api_token(&req_info, &["roles:create"], &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 roles:create 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 = 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 existing_rules: Vec<firewall_rule::Model> = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(role.clone())).all(&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,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for rule in &existing_rules {
|
||||||
|
match rule.clone().delete(&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,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_role_model = role::Model {
|
||||||
|
id: role.into_inner(),
|
||||||
|
name: req.name.clone(),
|
||||||
|
description: req.description.clone(),
|
||||||
|
organization: org,
|
||||||
|
created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64,
|
||||||
|
modified_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let firewall_rules: Vec<firewall_rule::Model> = req.firewall_rules.iter().map(|i| {
|
||||||
|
firewall_rule::Model {
|
||||||
|
id: random_id("rule"),
|
||||||
|
role: new_role_model.id.clone(),
|
||||||
|
protocol: i.protocol.to_string(),
|
||||||
|
description: i.description.clone(),
|
||||||
|
allowed_role_id: i.allowed_role_id.clone(),
|
||||||
|
port_range_from: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).from as i32,
|
||||||
|
port_range_to: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).to as i32,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let new_role_model_clone = new_role_model.clone();
|
||||||
|
let firewall_rules_clone = firewall_rules.clone();
|
||||||
|
|
||||||
|
let new_role_active_model = new_role_model.into_active_model();
|
||||||
|
match new_role_active_model.update(&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 creating the new role. Please try again later".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for rule in &firewall_rules_clone {
|
||||||
|
let active_model = rule.clone().into_active_model();
|
||||||
|
match active_model.insert(&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 creating the new role. Please try again later".to_string(),
|
||||||
|
path: None
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(RoleCreateResponse {
|
||||||
|
data: RoleResponse {
|
||||||
|
id: Some(new_role_model_clone.id.clone()),
|
||||||
|
name: Some(new_role_model_clone.name.clone()),
|
||||||
|
description: Some(new_role_model_clone.description),
|
||||||
|
firewall_rules: req.firewall_rules.clone(),
|
||||||
|
created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(),
|
||||||
|
modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(),
|
||||||
|
},
|
||||||
|
metadata: RoleCreateResponseMetadata {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue