mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(rust): add Provider trait + refactor C ABI to route through Provider
SelfHostedProvider implements Provider trait for offline license verification
This commit is contained in:
@@ -1,23 +1,27 @@
|
|||||||
// CraftLabs 授权核心库 — Rust 实现
|
|
||||||
// 导出 C ABI 接口,与 native/include/craftlabs_auth.h 一致
|
|
||||||
// 对齐 docs/平台架构思路.md §3.1
|
|
||||||
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
mod activate;
|
mod trait_provider;
|
||||||
mod heartbeat;
|
|
||||||
mod license;
|
|
||||||
mod security;
|
|
||||||
mod error;
|
mod error;
|
||||||
|
mod security;
|
||||||
mod session;
|
mod session;
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod provider_selfhosted;
|
pub mod provider_selfhosted;
|
||||||
|
|
||||||
|
use trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus};
|
||||||
|
use provider_selfhosted::SelfHostedProvider;
|
||||||
|
|
||||||
pub struct CraftContext {
|
pub struct CraftContext {
|
||||||
dummy: i32,
|
pub provider: Option<Box<dyn Provider>>,
|
||||||
|
pub initialized: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CraftContext {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
CraftContext { provider: None, initialized: false }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -36,41 +40,43 @@ pub struct LicenseInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn c_str_to_string(ptr: *const c_char) -> String {
|
unsafe fn c_str_to_string(ptr: *const c_char) -> String {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() { String::new() } else { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static OK_MSG: &[u8] = b"ok\0";
|
static OK_MSG: &[u8] = b"ok\0";
|
||||||
static FAIL_MSG: &[u8] = b"failure\0";
|
fn ok_result() -> CraftResult { CraftResult { success: 1, message: OK_MSG.as_ptr() as *const c_char } }
|
||||||
|
|
||||||
fn ok_result() -> CraftResult {
|
|
||||||
CraftResult {
|
|
||||||
success: 1,
|
|
||||||
message: OK_MSG.as_ptr() as *const c_char,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fail_result() -> CraftResult {
|
fn fail_result() -> CraftResult {
|
||||||
CraftResult {
|
static FAIL: &[u8] = b"failure\0";
|
||||||
success: 0,
|
CraftResult { success: 0, message: FAIL.as_ptr() as *const c_char }
|
||||||
message: FAIL_MSG.as_ptr() as *const c_char,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_provider(config: &str) -> String {
|
||||||
|
serde_json::from_str::<serde_json::Value>(config)
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.get("provider").and_then(|p| p.as_str().map(|s| s.to_string())))
|
||||||
|
.unwrap_or_else(|| "selfhosted".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
|
pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftContext {
|
||||||
let _config = unsafe { c_str_to_string(config_json) };
|
let config_str = unsafe { c_str_to_string(config_json) };
|
||||||
|
|
||||||
#[cfg(feature = "security-hardening")]
|
let mut ctx = Box::new(CraftContext::new());
|
||||||
{
|
let mut provider: Box<dyn Provider> = match parse_provider(&config_str).as_str() {
|
||||||
security::anti_debug::anti_debug_check();
|
"selfhosted" => Box::new(SelfHostedProvider::new()),
|
||||||
let _ = security::integrity::integrity_check();
|
_ => Box::new(SelfHostedProvider::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match provider.initialize(&ctx, &config_str) {
|
||||||
|
Ok(()) => {
|
||||||
|
ctx.provider = Some(provider);
|
||||||
|
ctx.initialized = true;
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
ctx.initialized = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = Box::new(CraftContext::new());
|
|
||||||
Box::into_raw(ctx)
|
Box::into_raw(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,77 +84,72 @@ pub extern "C" fn craft_initialize(config_json: *const c_char) -> *mut CraftCont
|
|||||||
pub extern "C" fn craft_activate(
|
pub extern "C" fn craft_activate(
|
||||||
handle: *mut CraftContext,
|
handle: *mut CraftContext,
|
||||||
license_key: *const c_char,
|
license_key: *const c_char,
|
||||||
config_json: *const c_char,
|
_config_json: *const c_char,
|
||||||
) -> CraftResult {
|
) -> CraftResult {
|
||||||
if handle.is_null() {
|
if handle.is_null() { return fail_result(); }
|
||||||
return fail_result();
|
let ctx = unsafe { &*handle };
|
||||||
}
|
|
||||||
let key = unsafe { c_str_to_string(license_key) };
|
let key = unsafe { c_str_to_string(license_key) };
|
||||||
let config = unsafe { c_str_to_string(config_json) };
|
ctx.provider.as_ref()
|
||||||
activate::core_activate(&key, &config)
|
.and_then(|p| p.activate(ctx, &key).ok())
|
||||||
|
.map_or_else(fail_result, |_| ok_result())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult {
|
pub extern "C" fn craft_check_license(handle: *mut CraftContext) -> CraftResult {
|
||||||
if handle.is_null() {
|
if handle.is_null() { return fail_result(); }
|
||||||
return fail_result();
|
let ctx = unsafe { &*handle };
|
||||||
}
|
ctx.provider.as_ref()
|
||||||
license::check_license(unsafe { &*handle })
|
.and_then(|p| p.check_license(ctx).ok())
|
||||||
|
.map_or_else(fail_result, |s| if s.licensed { ok_result() } else { fail_result() })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo {
|
pub extern "C" fn craft_get_license_info(handle: *mut CraftContext) -> *mut LicenseInfo {
|
||||||
if handle.is_null() {
|
if handle.is_null() { return ptr::null_mut(); }
|
||||||
return ptr::null_mut();
|
let ctx = unsafe { &*handle };
|
||||||
}
|
ctx.provider.as_ref()
|
||||||
license::get_license_info(unsafe { &*handle })
|
.map(|p| p.get_license_info(ctx))
|
||||||
|
.map_or(ptr::null_mut(), |info| Box::into_raw(Box::new(info)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
|
pub extern "C" fn craft_free_license_info(info: *mut LicenseInfo) {
|
||||||
if !info.is_null() {
|
if !info.is_null() { unsafe { drop(Box::from_raw(info)); } }
|
||||||
unsafe {
|
|
||||||
drop(Box::from_raw(info));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_has_feature(
|
pub extern "C" fn craft_has_feature(handle: *mut CraftContext, feature_name: *const c_char) -> i32 {
|
||||||
handle: *mut CraftContext,
|
if handle.is_null() { return 0; }
|
||||||
feature_name: *const c_char,
|
let ctx = unsafe { &*handle };
|
||||||
) -> i32 {
|
|
||||||
if handle.is_null() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let name = unsafe { c_str_to_string(feature_name) };
|
let name = unsafe { c_str_to_string(feature_name) };
|
||||||
if license::has_feature(unsafe { &*handle }, &name) {
|
ctx.provider.as_ref().map_or(0, |p| if p.has_feature(ctx, &name) { 1 } else { 0 })
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
|
pub extern "C" fn craft_release(handle: *mut CraftContext) -> CraftResult {
|
||||||
if handle.is_null() {
|
if handle.is_null() { return fail_result(); }
|
||||||
return fail_result();
|
let ctx = unsafe { &mut *handle };
|
||||||
|
if let Some(ref mut p) = ctx.provider {
|
||||||
|
let _ = p.release();
|
||||||
}
|
}
|
||||||
license::release_license(unsafe { &mut *handle })
|
ok_result()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
|
pub extern "C" fn craft_heartbeat(handle: *mut CraftContext) -> CraftResult {
|
||||||
if handle.is_null() {
|
if handle.is_null() { return fail_result(); }
|
||||||
return fail_result();
|
let ctx = unsafe { &*handle };
|
||||||
}
|
ctx.provider.as_ref()
|
||||||
heartbeat::do_heartbeat(unsafe { &*handle })
|
.and_then(|p| p.heartbeat(ctx).ok())
|
||||||
|
.map_or_else(fail_result, |_| ok_result())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
|
pub extern "C" fn craft_destroy(handle: *mut CraftContext) {
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
let ctx = &mut *handle;
|
||||||
|
if let Some(ref mut p) = ctx.provider { p.close(); }
|
||||||
drop(Box::from_raw(handle));
|
drop(Box::from_raw(handle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::{
|
|||||||
CraftContext, LicenseInfo,
|
CraftContext, LicenseInfo,
|
||||||
device::DeviceFingerprint,
|
device::DeviceFingerprint,
|
||||||
error::LicenseError,
|
error::LicenseError,
|
||||||
|
trait_provider::{Provider, ActivateResponse, HeartbeatResponse, LicenseStatus},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct SelfHostedConfig {
|
pub struct SelfHostedConfig {
|
||||||
@@ -131,3 +132,58 @@ impl SelfHostedProvider {
|
|||||||
self.cache.persist(&self.device_fp)
|
self.cache.persist(&self.device_fp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Provider trait 实现 ──
|
||||||
|
|
||||||
|
impl Provider for SelfHostedProvider {
|
||||||
|
fn initialize(&mut self, _ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError> {
|
||||||
|
let cfg: serde_json::Value = serde_json::from_str(config_json)
|
||||||
|
.map_err(|_| LicenseError::InvalidFormat("config json"))?;
|
||||||
|
let sh = cfg.get("selfhosted").ok_or(LicenseError::ConfigMissing("selfhosted section"))?;
|
||||||
|
|
||||||
|
let base_url = sh.get("baseUrl").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
|
let tenant_key = sh.get("tenantKey").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||||
|
let offline_grace_days = sh.get("offlineGraceDays").and_then(|v| v.as_u64()).unwrap_or(7) as u32;
|
||||||
|
let heartbeat_interval_hours = sh.get("heartbeatIntervalHours").and_then(|v| v.as_u64()).unwrap_or(24) as u32;
|
||||||
|
let public_key_pem = option_env!("CRAFTLABS_SELFHOSTED_PUBKEY").unwrap_or("").to_string();
|
||||||
|
|
||||||
|
self.initialize(base_url, tenant_key, offline_grace_days, heartbeat_interval_hours, public_key_pem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&self, _ctx: &CraftContext, _license_key: &str) -> Result<ActivateResponse, LicenseError> {
|
||||||
|
Err(LicenseError::Network("online activation not yet implemented".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_license(&self, _ctx: &CraftContext) -> Result<LicenseStatus, LicenseError> {
|
||||||
|
self.check_license_offline()?;
|
||||||
|
let cached = self.cache.license.as_ref().ok_or(LicenseError::NoCachedLicense)?;
|
||||||
|
Ok(LicenseStatus {
|
||||||
|
licensed: true,
|
||||||
|
not_after: cached.not_after,
|
||||||
|
features: cached.features.clone(),
|
||||||
|
device_count: 0,
|
||||||
|
max_devices: cached.max_devices,
|
||||||
|
heartbeat_due: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn heartbeat(&self, _ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError> {
|
||||||
|
Err(LicenseError::Network("heartbeat not yet implemented".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_feature(&self, _ctx: &CraftContext, name: &str) -> bool {
|
||||||
|
self.has_feature_offline(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release(&mut self) -> Result<(), LicenseError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_license_info(&self, _ctx: &CraftContext) -> LicenseInfo {
|
||||||
|
self.get_license_info_offline()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
let _ = self.persist_cache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::{CraftContext, LicenseInfo, error::LicenseError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LicenseStatus {
|
||||||
|
pub licensed: bool,
|
||||||
|
pub not_after: Option<i64>,
|
||||||
|
pub features: HashMap<String, bool>,
|
||||||
|
pub device_count: u32,
|
||||||
|
pub max_devices: u32,
|
||||||
|
pub heartbeat_due: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActivateResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub device_id: String,
|
||||||
|
pub license_payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HeartbeatResponse {
|
||||||
|
pub valid: bool,
|
||||||
|
pub lease_until: Option<i64>,
|
||||||
|
pub update_available: bool,
|
||||||
|
pub new_license_payload: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Provider: Send + Sync {
|
||||||
|
fn initialize(&mut self, ctx: &CraftContext, config_json: &str) -> Result<(), LicenseError>;
|
||||||
|
fn activate(&self, ctx: &CraftContext, license_key: &str) -> Result<ActivateResponse, LicenseError>;
|
||||||
|
fn check_license(&self, ctx: &CraftContext) -> Result<LicenseStatus, LicenseError>;
|
||||||
|
fn heartbeat(&self, ctx: &CraftContext) -> Result<HeartbeatResponse, LicenseError>;
|
||||||
|
fn has_feature(&self, ctx: &CraftContext, name: &str) -> bool;
|
||||||
|
fn release(&mut self) -> Result<(), LicenseError>;
|
||||||
|
fn get_license_info(&self, ctx: &CraftContext) -> LicenseInfo;
|
||||||
|
fn close(&mut self);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user