-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/ideal-world/bios
- Loading branch information
Showing
25 changed files
with
878 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,6 @@ | ||
use http::Extensions; | ||
use spacegate_shell::{kernel::extension::ExtensionPack as _, BoxError}; | ||
use tardis::serde_json::{self, Value}; | ||
|
||
use self::audit_log_param::LogParamContent; | ||
|
||
pub mod audit_log_param; | ||
pub mod before_encrypt_body; | ||
pub mod cert_info; | ||
pub mod notification; | ||
pub mod request_crypto_status; | ||
pub enum ExtensionPackEnum { | ||
LogParamContent(), | ||
None, | ||
} | ||
|
||
impl From<String> for ExtensionPackEnum { | ||
fn from(value: String) -> Self { | ||
match value.as_str() { | ||
"log_content" => ExtensionPackEnum::LogParamContent(), | ||
_ => ExtensionPackEnum::None, | ||
} | ||
} | ||
} | ||
impl ExtensionPackEnum { | ||
pub fn _to_value(&self, ext: &Extensions) -> Result<Option<Value>, BoxError> { | ||
match self { | ||
ExtensionPackEnum::LogParamContent() => { | ||
if let Some(ext) = LogParamContent::get(ext) { | ||
return Ok(Some(serde_json::to_value(ext)?)); | ||
} | ||
} | ||
ExtensionPackEnum::None => (), | ||
} | ||
Ok(None) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
151 changes: 120 additions & 31 deletions
151
backend/gateways/spacegate-plugins/src/extension/notification.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,148 @@ | ||
use std::{borrow::Cow, collections::HashMap, sync::Arc}; | ||
use std::{collections::HashMap, sync::Arc, time::Duration}; | ||
|
||
use http::{header::CONTENT_TYPE, HeaderName, HeaderValue, Uri}; | ||
use serde::{Deserialize, Serialize}; | ||
use spacegate_shell::{kernel::backend_service::http_client_service::HttpClient, SgBody, SgRequest}; | ||
use tardis::{log as tracing, serde_json}; | ||
use tardis::{basic::dto::TardisContext, log as tracing, tokio}; | ||
|
||
/// Context to call notification api | ||
/// | ||
/// Extract it from request extensions, and call [`NotificationContext::notify`] to send notification | ||
/// Extract it from request extensions, and call [`NotificationContext::report`] to send notification | ||
#[derive(Debug, Clone)] | ||
pub struct NotificationContext { | ||
pub(crate) api: Arc<Uri>, | ||
pub(crate) headers: Arc<HashMap<HeaderName, HeaderValue>>, | ||
pub(crate) client: HttpClient, | ||
pub(crate) reach_api: Arc<str>, | ||
pub(crate) log_api: Arc<str>, | ||
pub(crate) spi_app_id: Arc<str>, | ||
pub(crate) audit_log_tag: Arc<str>, | ||
pub(crate) audit_log_token_header_name: Arc<str>, | ||
pub(crate) templates: Arc<HashMap<String, NotifyPluginConfigTemplatesItem>>, | ||
pub(crate) audit_param: Arc<AuditLogParam>, | ||
pub(crate) cache_client: RedisClient, | ||
pub(crate) dedup_cache_cool_down: Duration, | ||
} | ||
|
||
impl NotificationContext { | ||
fn build_notification_request(&self, req: &ReachMsgSendReq) -> SgRequest { | ||
let req_bytes = serde_json::to_vec(&req).expect("ReachMsgSendReq is a valid json"); | ||
let body = SgBody::full(req_bytes); | ||
let mut req = SgRequest::new(body); | ||
req.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); | ||
*req.uri_mut() = self.api.as_ref().clone(); | ||
for (k, v) in self.headers.iter() { | ||
req.headers_mut().insert(k.clone(), v.clone()); | ||
pub fn submit_notify(&self, req: ReachMsgSendReq, dedup_hash: u64) { | ||
if self.reach_api.is_empty() { | ||
tracing::debug!("reach api is empty, skip sending notification"); | ||
return; | ||
} | ||
req | ||
let cache_client = self.cache_client.clone(); | ||
let ctx = TardisContext { | ||
ak: self.spi_app_id.to_string(), | ||
own_paths: self.spi_app_id.to_string(), | ||
..Default::default() | ||
}; | ||
let cool_down = self.dedup_cache_cool_down.as_secs().min(1); | ||
tokio::spawn(async move { | ||
let key = format!("sg:plugin:{}:{}", NotifyPlugin::CODE, dedup_hash); | ||
let mut conn = cache_client.get_conn().await; | ||
// check if the key exists | ||
if let Ok(Some(_)) = conn.get::<'_, _, Option<String>>(&key).await { | ||
tracing::debug!("dedup cache hit, skip sending notification"); | ||
return; | ||
} | ||
|
||
// set the dedup key | ||
if let Err(e) = conn.set_ex::<'_, _, _,Option<String>>(&key, "1", cool_down).await { | ||
tracing::error!(error = ?e, "set dedup cache failed"); | ||
return; | ||
} | ||
|
||
let funs = NotifyPlugin::get_funs_inst_by_plugin_code(); | ||
let response = bios_sdk_invoke::clients::reach_client::ReachClient::send_message(&req.into(), &funs, &ctx).await; | ||
if let Err(e) = response { | ||
tracing::error!(error = ?e, "send notification failed"); | ||
} | ||
}); | ||
} | ||
pub async fn notify(&self, req: &ReachMsgSendReq) { | ||
let notify_response = self.client.clone().request(self.build_notification_request(req)).await; | ||
if !notify_response.status().is_success() { | ||
tracing::warn!(response = ?notify_response, "send notification failed"); | ||
} | ||
pub fn report<R: Report>(&self, response: &SgResponse, report: R) { | ||
let replace = report.get_replacement(); | ||
let key = report.key(); | ||
|
||
let Ok(response) = notify_response.into_body().dump().await.inspect_err(|e| { | ||
tracing::error!(error = ?e, "failed to read response body"); | ||
}) else { | ||
return; | ||
}; | ||
let response_str = String::from_utf8_lossy(response.get_dumped().expect("just dump body")); | ||
tracing::debug!(response = ?response_str, "receive notification api response"); | ||
if let Some(template) = self.templates.get(key) { | ||
if let Some(notify_req) = template.reach.as_ref() { | ||
let mut req = notify_req.clone(); | ||
req.merge_replace(replace.clone()); | ||
let context = self.clone(); | ||
context.submit_notify(req, report.dedup_hash()); | ||
} | ||
if let Some(log_template) = template.audit_log.as_ref() { | ||
let formatted = format_template(log_template, &replace); | ||
self.submit_audit_log(response, Some(formatted)); | ||
} | ||
} | ||
} | ||
pub fn submit_audit_log(&self, response: &SgResponse, extra_info: Option<String>) { | ||
let mut log_param_content = self.audit_param.as_ref().clone().merge_audit_log_param_content(response, true, &self.audit_log_token_header_name); | ||
if let Some(extra_info) = extra_info { | ||
log_param_content.op = extra_info; | ||
} | ||
log_param_content.send_audit_log(&self.spi_app_id, &self.log_api, &self.audit_log_tag); | ||
} | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct TamperReport {} | ||
|
||
pub struct UnauthorizedOperationReport {} | ||
|
||
pub struct CertLockReport {} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct ContentFilterForbiddenReport { | ||
pub(crate) forbidden_reason: String, | ||
} | ||
|
||
use spacegate_shell::{ | ||
ext_redis::{redis::AsyncCommands, RedisClient}, | ||
plugin::{ | ||
schemars::{self, JsonSchema}, | ||
Plugin, | ||
}, | ||
SgResponse, | ||
}; | ||
|
||
use crate::plugin::{ | ||
notify::{format_template, NotifyPlugin, NotifyPluginConfigTemplates, NotifyPluginConfigTemplatesItem, Report}, | ||
PluginBiosExt, | ||
}; | ||
|
||
use super::audit_log_param::AuditLogParam; | ||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)] | ||
pub struct ReachMsgSendReq { | ||
pub scene_code: String, | ||
pub receives: Vec<ReachMsgReceive>, | ||
pub rel_item_id: String, | ||
pub replace: HashMap<String, String>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
impl ReachMsgSendReq { | ||
pub fn merge_replace<K: Into<String>>(&mut self, replace: impl IntoIterator<Item = (K, String)>) { | ||
self.replace.extend(replace.into_iter().map(|(k, v)| (k.into(), v))); | ||
} | ||
} | ||
|
||
impl From<ReachMsgSendReq> for bios_sdk_invoke::clients::reach_client::ReachMsgSendReq { | ||
fn from(val: ReachMsgSendReq) -> Self { | ||
bios_sdk_invoke::clients::reach_client::ReachMsgSendReq { | ||
scene_code: val.scene_code, | ||
receives: val.receives.into_iter().map(Into::into).collect(), | ||
rel_item_id: val.rel_item_id, | ||
replace: val.replace, | ||
} | ||
} | ||
} | ||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)] | ||
pub struct ReachMsgReceive { | ||
pub receive_group_code: String, | ||
pub receive_kind: String, | ||
pub receive_ids: Vec<String>, | ||
} | ||
|
||
impl From<ReachMsgReceive> for bios_sdk_invoke::clients::reach_client::ReachMsgReceive { | ||
fn from(val: ReachMsgReceive) -> Self { | ||
bios_sdk_invoke::clients::reach_client::ReachMsgReceive { | ||
receive_group_code: val.receive_group_code, | ||
receive_kind: val.receive_kind, | ||
receive_ids: val.receive_ids, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.