From 854d128b516b0165ca4e34b6cfd68e964a6c12c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Wed, 3 Apr 2024 15:58:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=E5=A2=9E=E5=8A=A0=E4=B8=8B?= =?UTF-8?q?=E5=8F=91=E8=AE=B8=E5=8F=AF=E8=AF=81=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- license_server/src/certificate.rs | 8 +++ license_server/src/controllers/license.rs | 58 +++++++++++++++ license_server/src/controllers/mod.rs | 5 +- license_server/src/utils.rs | 2 +- license_server/src/vo/license.rs | 87 +++++++++++++++++++++++ license_server/src/vo/mod.rs | 2 + 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 license_server/src/vo/license.rs diff --git a/license_server/src/certificate.rs b/license_server/src/certificate.rs index b612c95..51571ba 100644 --- a/license_server/src/certificate.rs +++ b/license_server/src/certificate.rs @@ -34,3 +34,11 @@ pub async fn load_certificates(certificate_filename: &str) -> anyhow::Result<()> } Ok(()) } + +pub fn get_public_key() -> &'static X509 { + LICENSE_PUBKEY.get().unwrap() +} + +pub fn get_private_key() -> &'static PKey { + LICENSE_PRIKEY.get().unwrap() +} diff --git a/license_server/src/controllers/license.rs b/license_server/src/controllers/license.rs index e69de29..6496b6e 100644 --- a/license_server/src/controllers/license.rs +++ b/license_server/src/controllers/license.rs @@ -0,0 +1,58 @@ +use axum::{http::StatusCode, response::IntoResponse, routing, Json, Router}; +use base64::{engine::general_purpose::STANDARD, Engine as _}; +use openssl::{hash::MessageDigest, rsa::Padding, sign::Signer}; +use serde_json::json; + +use crate::vo::{self, LicenseRequestForm}; + +pub struct LicenseController { + routes: Router, +} + +impl Into for LicenseController { + fn into(self) -> Router { + self.routes + } +} + +impl LicenseController { + pub fn init() -> Self { + let routes = Router::new().route("/license", routing::post(issue_license)); + + Self { routes } + } +} + +async fn issue_license(Json(request_form): Json) -> impl IntoResponse { + let mut license = vo::License::new( + request_form.licensee_name, + request_form.assignee_name, + request_form.assignee_email, + ); + for p in request_form.request_products.into_iter() { + license.add_product(p, request_form.valid_days); + } + let serialized_license = license.serialize(); + + let private_key = crate::certificate::get_private_key(); + let mut signer = Signer::new(MessageDigest::sha1(), private_key).unwrap(); + signer.set_rsa_padding(Padding::PKCS1).unwrap(); + signer.update(serialized_license.as_bytes()).unwrap(); + + let cert = crate::certificate::get_public_key() + .public_key() + .unwrap() + .public_key_to_der() + .unwrap(); + + let base64_license = STANDARD.encode(serialized_license); + let base64_signature = STANDARD.encode(signer.sign_to_vec().unwrap()); + let base64_cert = STANDARD.encode(cert); + + let license_response = format!( + "{}-{}-{}-{}", + license.license_id, base64_license, base64_signature, base64_cert + ); + + (StatusCode::OK, Json(json!({"license": license_response}))) +} diff --git a/license_server/src/controllers/mod.rs b/license_server/src/controllers/mod.rs index af04848..be7571c 100644 --- a/license_server/src/controllers/mod.rs +++ b/license_server/src/controllers/mod.rs @@ -5,7 +5,10 @@ use axum::Router; /// 生成可迭代的转化为路由定义的控制器列表。 pub fn controllers() -> Box>> { - let controllers: Vec> = vec![Box::new(products::ProductsController::init().into())]; + let controllers: Vec> = vec![ + Box::new(products::ProductsController::init().into()), + Box::new(license::LicenseController::init().into()), + ]; Box::from(controllers.into_iter()) } diff --git a/license_server/src/utils.rs b/license_server/src/utils.rs index b4c32c2..04dcfe3 100644 --- a/license_server/src/utils.rs +++ b/license_server/src/utils.rs @@ -11,7 +11,7 @@ pub fn empty_to_none>(s: S) -> Option { const RAND_LINCENSE_STR_SRC: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; -pub fn generate_product_license_id() -> String { +pub fn generate_license_id() -> String { let choices = RAND_LINCENSE_STR_SRC.as_bytes(); let mut rng = thread_rng(); let mut code: Vec = vec![]; diff --git a/license_server/src/vo/license.rs b/license_server/src/vo/license.rs new file mode 100644 index 0000000..3533330 --- /dev/null +++ b/license_server/src/vo/license.rs @@ -0,0 +1,87 @@ +use chrono::{Duration, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::utils; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Product { + pub code: String, + pub fallback_date: String, + pub paid_up_to: String, + pub extended: bool, +} + +impl Product { + pub fn new>(code: S, authorize_days: i64) -> Self { + let today = Utc::now().date_naive(); + let expires_date = (today + Duration::days(authorize_days)) + .format("%Y-%m-%d") + .to_string(); + Self { + code: code.as_ref().to_string(), + fallback_date: expires_date.clone(), + paid_up_to: expires_date, + extended: true, + } + } +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct License { + pub license_id: String, + pub licensee_name: String, + pub assignee_name: String, + pub assignee_email: String, + pub license_restriction: String, + pub check_concurrent_use: bool, + pub products: Vec, + #[serde(rename = "metadata")] + pub meta_data: String, + pub hash: String, + pub grace_period_days: i32, + pub auto_prolongated: bool, + pub is_auto_prolongated: bool, +} + +impl License { + pub fn new( + licensee_name: String, + assignee_name: String, + assignee_email: Option, + ) -> Self { + Self { + license_id: utils::generate_license_id(), + licensee_name, + assignee_name, + assignee_email: assignee_email.unwrap_or(String::new()), + license_restriction: String::new(), + check_concurrent_use: false, + products: vec![], + meta_data: String::from("0120230102PPAA013009"), + hash: String::from("41472961/0:1563609451"), + grace_period_days: 7, + auto_prolongated: true, + is_auto_prolongated: true, + } + } + + pub fn add_product>(&mut self, product_code: S, valid_days: i64) { + self.products.push(Product::new(product_code, valid_days)); + } + + pub fn serialize(&self) -> String { + serde_json::to_string(self).unwrap() + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct LicenseRequestForm { + pub licensee_name: String, + pub assignee_name: String, + pub assignee_email: Option, + pub valid_days: i64, + pub request_products: Vec, +} diff --git a/license_server/src/vo/mod.rs b/license_server/src/vo/mod.rs index 01a41e3..956cf0f 100644 --- a/license_server/src/vo/mod.rs +++ b/license_server/src/vo/mod.rs @@ -1,3 +1,5 @@ +mod license; mod shared; +pub use license::*; pub use shared::*;