feat(server):增加下发许可证功能。
This commit is contained in:
		| @@ -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<Private> { | ||||
|     LICENSE_PRIKEY.get().unwrap() | ||||
| } | ||||
|   | ||||
| @@ -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<Router> 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<LicenseRequestForm>) -> 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}))) | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,10 @@ use axum::Router; | ||||
|  | ||||
| /// 生成可迭代的转化为路由定义的控制器列表。 | ||||
| pub fn controllers() -> Box<dyn Iterator<Item = Box<Router>>> { | ||||
|     let controllers: Vec<Box<Router>> = vec![Box::new(products::ProductsController::init().into())]; | ||||
|     let controllers: Vec<Box<Router>> = vec![ | ||||
|         Box::new(products::ProductsController::init().into()), | ||||
|         Box::new(license::LicenseController::init().into()), | ||||
|     ]; | ||||
|  | ||||
|     Box::from(controllers.into_iter()) | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ pub fn empty_to_none<S: Into<String>>(s: S) -> Option<String> { | ||||
|  | ||||
| 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<String> = vec![]; | ||||
|   | ||||
							
								
								
									
										87
									
								
								license_server/src/vo/license.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								license_server/src/vo/license.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -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<S: AsRef<str>>(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<Product>, | ||||
|     #[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<String>, | ||||
|     ) -> 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<S: AsRef<str>>(&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<String>, | ||||
|     pub valid_days: i64, | ||||
|     pub request_products: Vec<String>, | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| mod license; | ||||
| mod shared; | ||||
|  | ||||
| pub use license::*; | ||||
| pub use shared::*; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user