Licensing Service — Analysis Design & Solution Modeling
Tujuan Dokumen
Dokumen ini berisi analisis desain dan pemodelan solusi untuk Licensing Framework pada TessaERP.
Merupakan kelanjutan dari gitlab-issue-fr013-licensing-framework.md dan index.md, mencakup use case, flowchart, component diagram, sequence diagram, dan schema data.
Use Case
Aktor
| Aktor | Deskripsi |
|---|---|
| Vendor / Admin Neuron | Men-generate dan menandatangani file lisensi menggunakan Private Key |
| Customer / Operator | Mengupload file lisensi ke instance ERP |
| ERP Application | Memvalidasi lisensi dan membatasi akses berdasarkan entitlement |
| License Service | Layer abstraksi yang membaca dan memverifikasi lisensi |
Use Case Diagram
graph TD
Vendor((Vendor\nAdmin Neuron))
Customer((Customer\nOperator))
ERP((ERP Application))
LS((License Service))
UC01[UC-01: Generate License Payload]
UC02[UC-02: Sign License dengan Private Key]
UC03[UC-03: Distribusi license.lic ke Customer]
UC04[UC-04: Upload license.lic ke ERP]
UC05[UC-05: Validasi Lisensi saat Startup]
UC06[UC-06: Query Entitlement Feature]
UC07[UC-07: Query Limit Resource]
UC08[UC-08: Tampilkan Halaman Upload\nJika Lisensi Invalid]
UC09[UC-09: Blokir Akses ERP\nJika Lisensi Invalid]
Vendor --> UC01
Vendor --> UC02
UC01 --> UC02
Vendor --> UC03
Customer --> UC04
ERP --> UC05
ERP --> UC06
ERP --> UC07
ERP --> UC08
ERP --> UC09
UC05 --> LS
UC06 --> LS
UC07 --> LS
Detail Use Case
| ID | Use Case | Aktor | Pre-Condition | Post-Condition |
|---|---|---|---|---|
| UC-01 | Generate License Payload | Vendor | Data tenant dan entitlement tersedia | JSON payload terbentuk |
| UC-02 | Sign License | Vendor | Payload tersedia, Private Key tersedia | license.lic bertanda tangan |
| UC-03 | Distribusi license.lic | Vendor | license.lic sudah ditandatangani | File diterima customer |
| UC-04 | Upload license.lic | Customer | Instance ERP running, lisensi invalid / expired | Lisensi baru tersimpan |
| UC-05 | Validasi Lisensi saat Startup | ERP | Application start | Lisensi valid → ERP normal; Invalid → redirect /license |
| UC-06 | Query Entitlement Feature | ERP | Lisensi valid dan di-cache | true/false dikembalikan |
| UC-07 | Query Limit Resource | ERP | Lisensi valid dan di-cache | Nilai limit dikembalikan |
| UC-08 | Tampilkan Halaman Upload | ERP | Lisensi tidak valid | Kasir diarahkan ke /license |
| UC-09 | Blokir Akses ERP | ERP | Lisensi tidak valid | Seluruh route selain /license diblokir |
Flowchart
Alur Validasi Lisensi saat Startup
flowchart TD
A([Application Start]) --> B[Load license.lic\ndari LicenseFilePath]
B --> C{File ditemukan?}
C -- Tidak --> Z1[/"Reason: License file not found"\nLog error/]
C -- Ya --> D[Parse JSON payload]
D --> E{Format valid?}
E -- Tidak --> Z2[/"Reason: License file corrupted"\nLog error/]
E -- Ya --> F[Verify Signature\ngunakan Public Key]
F --> G{Signature valid?}
G -- Tidak --> Z3[/"Reason: License has been tampered"\nLog error/]
G -- Ya --> H{tenantId cocok\ndengan TENANT_ID env?}
H -- Tidak --> Z4[/"Reason: License issued for different tenant"\nLog error/]
H -- Ya --> I{License expired?}
I -- Ya --> Z5[/"Reason: License expired on date"\nLog error/]
I -- Tidak --> J[Cache License Information\ndi ILicenseProvider]
J --> K([ERP Startup Normal])
Z1 & Z2 & Z3 & Z4 & Z5 --> R[Redirect semua request → /license\nBlokir akses ERP]
Alur Upload License Baru
flowchart TD
A([User membuka /license]) --> B[Tampilkan halaman upload]
B --> C[User upload license.lic]
C --> D[Parse JSON]
D --> E{Format valid?}
E -- Tidak --> E1[Tampilkan error:\nFormat file tidak sesuai]
E -- Ya --> F[Verify Signature\ngunakan Public Key]
F --> G{Signature valid?}
G -- Tidak --> G1[Tampilkan error:\nFile corrupted or tampered]
G -- Ya --> H{tenantId cocok?}
H -- Tidak --> H1[Tampilkan error:\nLicense for different tenant]
H -- Ya --> I{Expired?}
I -- Ya --> I1[Tampilkan error:\nLicense expired on date]
I -- Tidak --> J[Simpan license.lic\nke LicenseFilePath]
J --> K[/"✅ Upload berhasil\nRestart application untuk menerapkan lisensi"/]
E1 & G1 & H1 & I1 --> B
Alur Generate License (Vendor Tool)
flowchart TD
A([Admin Neuron buka License Generator]) --> B[Input: TenantId, Edition,\nSubscription Type, Expiry,\nFeatures, Limits]
B --> C[Build JSON Payload]
C --> D[Sign Payload\ngunakan Private Key RSA ≥ 2048-bit]
D --> E[Wrap: payload + signature → license.lic]
E --> F([Export license.lic\nkirim ke customer])
Component Diagram
graph TD
subgraph Neuron_ERP["Neuron_ERP — Blazor Server"]
subgraph LIC_Pages["Pages / License"]
LP1[LicenseUpload.razor\n/license]
end
subgraph Middleware["Middleware Pipeline"]
MW1[LicenseValidationMiddleware\nBlokir route jika lisensi invalid]
end
subgraph ERP_Modules["ERP Modules — contoh penggunaan"]
EM1[MenuService.cs\nif HasFeature finance → ShowFinanceMenu]
EM2[UserService.cs\nif GetLimit users reached → blokir tambah user]
end
LP1 --> MW1
EM1 --> IL
EM2 --> IL
end
subgraph NeuronLibrary["NeuronLibrary — Business Logic"]
subgraph LIC_Svc["Helper / Licensing"]
IL[ILicenseProvider\ninterface]
LP[LicenseProvider.cs\nimplementasi]
LV[ILicenseVerificationService]
LVImpl[LicenseVerificationService.cs]
LM[LicenseModel.cs\nLicensePayload, LicenseValidationResult]
end
IL --> LP
LV --> LVImpl
LVImpl --> LM
LP --> LV
end
subgraph VendorTool["Vendor Tool — License Generator"]
LG[LicenseGenerator.cs\natau CLI tool]
PK[(Private Key\n.pem)]
LG --> PK
end
subgraph FS["File System"]
LF[(license.lic)]
PubK[(public.pem)]
end
LVImpl --> LF
LVImpl --> PubK
LG --> LF
MW1 --> IL
Sequence Diagram
Validasi Lisensi saat Application Startup
sequenceDiagram
participant App as Program.cs\nApplication Start
participant MW as LicenseValidationMiddleware
participant LP as LicenseProvider
participant LV as LicenseVerificationService
participant FS as File System
App->>LP: services.AddSingleton<ILicenseProvider>
App->>LV: Validate() on startup
LV->>FS: Read license.lic (LicenseFilePath)
FS-->>LV: file content (JSON)
LV->>LV: Verify RSA Signature (Public Key)
LV->>LV: Check tenantId == TENANT_ID env
LV->>LV: Check expiresAt > DateTime.Now
LV-->>LP: LicenseValidationResult {IsValid, Reason, ExpirationDate}
LP->>LP: Cache license info
Note over MW: Setiap HTTP request
MW->>LP: IsValid()
alt License Valid
LP-->>MW: true
MW->>MW: next() — lanjutkan request
else License Invalid
LP-->>MW: false
MW->>MW: Redirect → /license
end
UC-06: Query Entitlement Feature oleh ERP Module
sequenceDiagram
participant Mod as ERP Module\n(e.g. MenuService)
participant LP as ILicenseProvider
participant Cache as Cached License Info
Mod->>LP: HasFeature("finance")
LP->>Cache: Read features["finance"]
Cache-->>LP: true / false
LP-->>Mod: true / false
alt Feature tersedia
Mod->>Mod: tampilkan menu Finance
else Feature tidak tersedia
Mod->>Mod: sembunyikan menu Finance
end
Upload License Baru oleh Customer
sequenceDiagram
actor Customer
participant Page as LicenseUpload.razor
participant API as LicenseController
participant LV as LicenseVerificationService
participant FS as File System
Customer->>Page: upload license.lic
Page->>API: POST /api/license/upload (file)
API->>LV: ValidateFile(fileContent)
LV->>LV: Parse JSON
LV->>LV: Verify RSA Signature
LV->>LV: Check tenantId
LV->>LV: Check expiry
alt Semua valid
LV-->>API: IsValid = true
API->>FS: Save license.lic ke LicenseFilePath
API-->>Page: 200 OK
Page-->>Customer: ✅ Upload berhasil. Restart server untuk menerapkan.
else Ada error
LV-->>API: IsValid = false, Reason = ...
API-->>Page: 400 Bad Request + reason
Page-->>Customer: ❌ error message spesifik
end
Schema Data
License File Format (license.lic)
{
"payload": {
"licenseId": "LIC-2026-0001",
"tenantId": "TENANT001",
"edition": "Professional",
"subscription": {
"type": "annual",
"expiresAt": "2027-12-31"
},
"features": {
"sales": true,
"purchase": true,
"inventory": true,
"finance": false,
"manufacturing": true,
"hr": true
},
"limits": {
"users": 50
},
"constraints": {
"machineFingerprint": null
}
},
"signature": "BASE64_RSA_SIGNATURE"
}
Konfigurasi appsettings.json
{
"Licensing": {
"LicenseFilePath": "./license/license.lic",
"PublicKeyPath": "./license/public.pem",
"LicenseValidationEnabled": true
}
}
Class Diagram
classDiagram
class ILicenseProvider {
<<interface>>
+IsValid() bool
+HasFeature(featureCode: string) bool
+GetLimit(limitCode: string) int
+GetLicenseInfo() LicenseInfo
}
class LicenseProvider {
-_validationResult: LicenseValidationResult
-_payload: LicensePayload
+IsValid() bool
+HasFeature(featureCode: string) bool
+GetLimit(limitCode: string) int
+GetLicenseInfo() LicenseInfo
}
class ILicenseVerificationService {
<<interface>>
+Validate() LicenseValidationResult
+ValidateFile(content: string) LicenseValidationResult
}
class LicenseVerificationService {
-_config: LicensingOptions
+Validate() LicenseValidationResult
+ValidateFile(content: string) LicenseValidationResult
-VerifySignature(payload: string, sig: string) bool
-CheckTenantId(tenantId: string) bool
-CheckExpiry(expiresAt: DateTime) bool
}
class LicenseValidationResult {
+IsValid: bool
+Reason: string
+ExpirationDate: DateTime?
}
class LicensePayload {
+LicenseId: string
+TenantId: string
+Edition: string
+Subscription: SubscriptionInfo
+Features: Dictionary~string, bool~
+Limits: Dictionary~string, int~
+Constraints: ConstraintInfo
}
class SubscriptionInfo {
+Type: string
+ExpiresAt: DateTime
}
class ConstraintInfo {
+MachineFingerprint: string?
}
class LicensingOptions {
+LicenseFilePath: string
+PublicKeyPath: string
+LicenseValidationEnabled: bool
}
ILicenseProvider <|.. LicenseProvider
ILicenseVerificationService <|.. LicenseVerificationService
LicenseProvider --> ILicenseVerificationService
LicenseProvider --> LicensePayload
LicenseVerificationService --> LicenseValidationResult
LicenseVerificationService --> LicensePayload
LicenseVerificationService --> LicensingOptions
LicensePayload --> SubscriptionInfo
LicensePayload --> ConstraintInfo
Referensi
- GitLab Issue FR-013:
gitlab-issue-fr013-licensing-framework.md - Architecture Proposal:
index.md - GitLab Branch: fr013
- Licensing Framework Dev Doc: GitLab