Update License and P System
This commit is contained in:
parent
42e7bc7e10
commit
7d6ef9f0eb
9
.env
9
.env
@ -72,3 +72,12 @@ GITHUB_TOKEN=
|
||||
# Optional: Gitea Access Token for private instances
|
||||
GITEA_TOKEN=
|
||||
###< app/git-services ###
|
||||
|
||||
###> Plugin-System ###
|
||||
# Lizenz-Backend: LicenseValidator oder GiteaLicenseValidator
|
||||
LICENSE_BACKEND=LicenseValidator
|
||||
LICENSE_SERVER_URL=https://license.example.com
|
||||
GITEA_BASE_URL=https://git.example.com
|
||||
GITEA_ORGANIZATION=mycrm
|
||||
INSTANCE_ID=changeme
|
||||
###< Plugin-System ###
|
||||
|
||||
@ -3,9 +3,26 @@
|
||||
|
||||
###> Plugin-System ###
|
||||
|
||||
# Wähle Lizenzierungs-Backend: "LicenseValidator" (Standard) oder "GiteaLicenseValidator"
|
||||
# Standard: REST-API basierter Lizenzserver
|
||||
# Gitea: Nutzt Gitea Repository-Access als Lizenzierung
|
||||
LICENSE_BACKEND=LicenseValidator
|
||||
|
||||
# === Standard Lizenzserver (LICENSE_BACKEND=LicenseValidator) ===
|
||||
|
||||
# URL des Lizenzservers (ohne trailing slash)
|
||||
LICENSE_SERVER_URL=https://license.mycrm.local
|
||||
|
||||
# === Gitea Lizenzserver (LICENSE_BACKEND=GiteaLicenseValidator) ===
|
||||
|
||||
# Gitea Base URL (ohne trailing slash)
|
||||
GITEA_BASE_URL=https://git.mycrm.local
|
||||
|
||||
# Gitea Organisation oder User, der die Modul-Repositories besitzt
|
||||
GITEA_ORGANIZATION=mycrm
|
||||
|
||||
# === Gemeinsame Konfiguration ===
|
||||
|
||||
# Eindeutige Instance-ID (generiere mit: php bin/console app:generate-instance-id
|
||||
# oder verwende: uuidgen / openssl rand -hex 16)
|
||||
INSTANCE_ID=your-unique-instance-identifier-here
|
||||
@ -13,7 +30,9 @@ INSTANCE_ID=your-unique-instance-identifier-here
|
||||
###< Plugin-System ###
|
||||
|
||||
###> Modul-Lizenzen ###
|
||||
# Format: LICENSE_{MODULE_IDENTIFIER}=lizenzschlüssel
|
||||
|
||||
# === Für Standard-Lizenzserver (LICENSE_BACKEND=LicenseValidator) ===
|
||||
# Format: LICENSE_{MODULE_IDENTIFIER}=JWT-Token-vom-Lizenzserver
|
||||
|
||||
# Beispiel: Billing-Modul
|
||||
# LICENSE_BILLING=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
@ -21,8 +40,18 @@ INSTANCE_ID=your-unique-instance-identifier-here
|
||||
# Beispiel: Invoicing-Modul
|
||||
# LICENSE_INVOICING=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
|
||||
# Beispiel: Inventory-Modul
|
||||
# LICENSE_INVENTORY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
# === Für Gitea-Lizenzserver (LICENSE_BACKEND=GiteaLicenseValidator) ===
|
||||
# Format: GITEA_TOKEN_{MODULE_IDENTIFIER}=gitea-access-token
|
||||
|
||||
# Beispiel: Billing-Modul
|
||||
# GITEA_TOKEN_BILLING=abc123def456...
|
||||
|
||||
# Beispiel: Invoicing-Modul
|
||||
# GITEA_TOKEN_INVOICING=xyz789ghi012...
|
||||
|
||||
# Hinweis: Gitea Access Token generieren unter:
|
||||
# Gitea → Settings → Applications → Generate New Token
|
||||
# Erforderliche Scopes: repo (read access)
|
||||
|
||||
###< Modul-Lizenzen ###
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
auth.json
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
"doctrine/orm": "^3.5",
|
||||
"knpuniversity/oauth2-client-bundle": "*",
|
||||
"league/oauth2-client": "*",
|
||||
"mycrm/test-module": "*",
|
||||
"nelmio/cors-bundle": "^2.6",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
@ -112,5 +113,11 @@
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/stopwatch": "7.1.*",
|
||||
"symfony/web-profiler-bundle": "7.1.*"
|
||||
},
|
||||
"repositories": {
|
||||
"mycrm-test-module": {
|
||||
"type": "vcs",
|
||||
"url": "https://git.osdata-home.de/mycrm/mycrm-test-module"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
composer.lock
generated
38
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c70cc2a152b707d0dcf4c562bd06f8d5",
|
||||
"content-hash": "caa943b57e0e058e754382b31531775b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/core",
|
||||
@ -2038,6 +2038,42 @@
|
||||
],
|
||||
"time": "2025-03-24T10:02:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mycrm/test-module",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.osdata-home.de/mycrm/mycrm-test-module",
|
||||
"reference": "0630a840ffdca475208b51f7807196468e44c27c"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"symfony/framework-bundle": "^7.0"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.0-dev",
|
||||
"stable": "v1.0.0"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MyCRM\\TestModule\\": "src/"
|
||||
}
|
||||
},
|
||||
"license": [
|
||||
"proprietary"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Your Name",
|
||||
"email": "your.email@example.com"
|
||||
}
|
||||
],
|
||||
"description": "Test Module for myCRM - Demonstrates the plugin system",
|
||||
"time": "2025-12-03T15:57:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nelmio/cors-bundle",
|
||||
"version": "2.6.0",
|
||||
|
||||
@ -18,4 +18,5 @@ return [
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
MyCRM\TestModule\TestModuleBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
@ -14,6 +14,11 @@ services:
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
bind:
|
||||
$licenseServerUrl: '%env(LICENSE_SERVER_URL)%'
|
||||
$giteaBaseUrl: '%env(GITEA_BASE_URL)%'
|
||||
$giteaOrganization: '%env(GITEA_ORGANIZATION)%'
|
||||
$instanceId: '%env(INSTANCE_ID)%'
|
||||
|
||||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
# Plugin-System Services Configuration
|
||||
|
||||
services:
|
||||
# Lizenz-Validator
|
||||
App\Plugin\LicenseValidatorInterface:
|
||||
class: App\Service\LicenseValidator
|
||||
arguments:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
bind:
|
||||
$licenseServerUrl: '%env(LICENSE_SERVER_URL)%'
|
||||
$giteaBaseUrl: '%env(GITEA_BASE_URL)%'
|
||||
$giteaOrganization: '%env(GITEA_ORGANIZATION)%'
|
||||
$instanceId: '%env(INSTANCE_ID)%'
|
||||
|
||||
# Interface-Alias: Verwendet Gitea-Validator
|
||||
# Um auf Standard-Validator zu wechseln: Ändere zu @App\Service\LicenseValidator
|
||||
App\Plugin\LicenseValidatorInterface:
|
||||
alias: 'App\Service\GiteaLicenseValidator'
|
||||
|
||||
# Module Registry (automatisches TaggedIterator sammelt alle Plugins)
|
||||
App\Plugin\ModuleRegistry:
|
||||
arguments:
|
||||
|
||||
500
docs/GITEA_LICENSE_SYSTEM.md
Normal file
500
docs/GITEA_LICENSE_SYSTEM.md
Normal file
@ -0,0 +1,500 @@
|
||||
# Gitea-basiertes Lizenzierungs-System
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Gitea-basierte Lizenzierungs-System nutzt **Gitea Repository-Zugriff** als Lizenzierung für myCRM-Module. Anstatt einen separaten Lizenzserver zu betreiben, prüft das System, ob ein Benutzer Zugriff auf das private Gitea-Repository eines Moduls hat.
|
||||
|
||||
## Konzept
|
||||
|
||||
```
|
||||
Gitea Access Token → Repository-Zugriff → Gültige Lizenz
|
||||
```
|
||||
|
||||
### Vorteile
|
||||
|
||||
✅ **Keine separate Lizenzserver-API nötig** - Nutzt bestehende Gitea-Infrastruktur
|
||||
✅ **Einfache Verwaltung** - Access Control über Gitea UI
|
||||
✅ **Flexibel** - Granulare Berechtigungen pro Repository
|
||||
✅ **Sicher** - Nutzt Gitea's bewährte Authentifizierung
|
||||
✅ **Offline-Support** - 24h Cache + 7 Tage Grace Period
|
||||
✅ **Composer-Integration** - Gleiche Tokens für Composer und Lizenzierung
|
||||
|
||||
### Funktionsweise
|
||||
|
||||
1. **Modul-Repositories in Gitea erstellen** (z.B. `mycrm/mycrm-billing-module`)
|
||||
2. **Private Repositories** - Nur lizenzierte Benutzer haben Zugriff
|
||||
3. **Gitea Access Token** als Lizenzschlüssel verwenden
|
||||
4. **myCRM prüft Repository-Zugriff** über Gitea API
|
||||
5. **Zugriff = Lizenz gültig**
|
||||
|
||||
---
|
||||
|
||||
## Installation & Konfiguration
|
||||
|
||||
### 1. Gitea-Backend aktivieren
|
||||
|
||||
```bash
|
||||
# .env.local
|
||||
LICENSE_BACKEND=GiteaLicenseValidator
|
||||
GITEA_BASE_URL=https://git.mycrm.local
|
||||
GITEA_ORGANIZATION=mycrm
|
||||
INSTANCE_ID=deine-unique-instance-id
|
||||
```
|
||||
|
||||
### 2. Modul-Repository in Gitea erstellen
|
||||
|
||||
**Namenskonvention:** `mycrm-{module-identifier}-module`
|
||||
|
||||
Beispiele:
|
||||
- Billing-Modul: `mycrm-billing-module`
|
||||
- Invoicing-Modul: `mycrm-invoicing-module`
|
||||
- Inventory-Modul: `mycrm-inventory-module`
|
||||
|
||||
**Repository-Settings:**
|
||||
- **Visibility:** Private
|
||||
- **Owner:** Deine Organisation oder dein User (z.B. `mycrm`)
|
||||
|
||||
### 3. Gitea Access Token generieren
|
||||
|
||||
1. Gehe zu **Gitea → Settings → Applications**
|
||||
2. Klicke auf **"Generate New Token"**
|
||||
3. Name: `myCRM License - {Modul-Name}`
|
||||
4. **Scopes auswählen:**
|
||||
- ✅ `repo` (Read access to repositories)
|
||||
5. Token kopieren (wird nur einmal angezeigt!)
|
||||
|
||||
### 4. Lizenz in myCRM registrieren
|
||||
|
||||
#### Option A: CLI (empfohlen)
|
||||
```bash
|
||||
php bin/console app:module:license billing YOUR_GITEA_TOKEN_HERE
|
||||
```
|
||||
|
||||
#### Option B: Manuell in .env.local
|
||||
```bash
|
||||
# .env.local
|
||||
GITEA_TOKEN_BILLING=abc123def456...
|
||||
GITEA_TOKEN_INVOICING=xyz789ghi012...
|
||||
```
|
||||
|
||||
### 5. Verifizieren
|
||||
|
||||
```bash
|
||||
php bin/console app:module:list
|
||||
```
|
||||
|
||||
Ausgabe sollte zeigen:
|
||||
```
|
||||
billing ✓ Aktiv Gitea Repository-Zugriff bestätigt (mycrm/mycrm-billing-module)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repository-Metadaten für Lizenzinformationen
|
||||
|
||||
Das System kann Lizenzinformationen aus Repository-Metadaten extrahieren:
|
||||
|
||||
### Features über Topics
|
||||
|
||||
Füge Topics zum Repository hinzu, um Features zu definieren:
|
||||
|
||||
```
|
||||
Topics: feature-invoices, feature-payments, feature-reports
|
||||
```
|
||||
|
||||
Diese werden als `features` im Lizenz-Array zurückgegeben:
|
||||
```php
|
||||
[
|
||||
'valid' => true,
|
||||
'features' => ['invoices', 'payments', 'reports'],
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
### Ablaufdatum in Description
|
||||
|
||||
Füge das Ablaufdatum in die Repository-Description ein:
|
||||
|
||||
```
|
||||
Premium Billing Module for myCRM
|
||||
License expires: 2025-12-31
|
||||
```
|
||||
|
||||
Unterstützte Formate:
|
||||
- `expires: YYYY-MM-DD`
|
||||
- `valid until: YYYY-MM-DD`
|
||||
- `License expires: YYYY-MM-DD`
|
||||
|
||||
Das System parsed automatisch das Datum:
|
||||
```php
|
||||
[
|
||||
'valid' => true,
|
||||
'expiresAt' => DateTimeImmutable('2025-12-31'),
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Access Control: Wer darf welches Modul nutzen?
|
||||
|
||||
### Variante 1: Team-Zugriff per Gitea Teams
|
||||
|
||||
1. **Team in Gitea erstellen**
|
||||
- Gehe zu Organisation → Teams → "Create Team"
|
||||
- Name: `Premium Customers`
|
||||
|
||||
2. **Team-Mitglieder hinzufügen**
|
||||
- Team → Members → "Add Member"
|
||||
|
||||
3. **Repository dem Team zuweisen**
|
||||
- Repository → Settings → Collaboration
|
||||
- Add Team: `Premium Customers` mit **Read** Permission
|
||||
|
||||
4. **Jedes Team-Mitglied erhält Zugriff**
|
||||
- Alle Mitglieder können nun Tokens mit Repo-Zugriff generieren
|
||||
|
||||
### Variante 2: Individuelle Collaborators
|
||||
|
||||
1. **Repository → Settings → Collaboration**
|
||||
2. **"Add Collaborator"**
|
||||
3. User auswählen + **Read** Permission
|
||||
4. User kann jetzt Token mit Zugriff generieren
|
||||
|
||||
### Variante 3: Organisation-weiter Zugriff
|
||||
|
||||
1. **Organisation → Settings → Members**
|
||||
2. Mitglieder mit **Read**-Rechten auf alle Private Repos
|
||||
3. Alle Mitglieder haben automatisch Zugriff auf alle Module
|
||||
|
||||
---
|
||||
|
||||
## Composer Integration
|
||||
|
||||
Da Gitea-Tokens sowohl für Lizenzierung als auch für Composer-Installation genutzt werden können, ist die Integration nahtlos:
|
||||
|
||||
### composer.json konfigurieren
|
||||
|
||||
```bash
|
||||
composer config repositories.mycrm-billing vcs https://git.mycrm.local/mycrm/mycrm-billing-module
|
||||
```
|
||||
|
||||
### auth.json konfigurieren
|
||||
|
||||
```json
|
||||
{
|
||||
"http-basic": {
|
||||
"git.mycrm.local": {
|
||||
"username": "dein-gitea-username",
|
||||
"password": "DEIN_GITEA_TOKEN"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modul installieren
|
||||
|
||||
```bash
|
||||
composer require mycrm/billing-module
|
||||
```
|
||||
|
||||
**Ein Token für beides:**
|
||||
- ✅ Composer Installation
|
||||
- ✅ Lizenz-Validierung
|
||||
|
||||
---
|
||||
|
||||
## CLI-Befehle
|
||||
|
||||
### Lizenz registrieren
|
||||
|
||||
```bash
|
||||
php bin/console app:module:license <module-identifier> <gitea-token>
|
||||
|
||||
# Beispiel
|
||||
php bin/console app:module:license billing abc123def456ghi789
|
||||
```
|
||||
|
||||
### Alle Module auflisten
|
||||
|
||||
```bash
|
||||
php bin/console app:module:list
|
||||
|
||||
# Ausgabe:
|
||||
# ┌───────────┬────────┬─────────────────────────────────────────────┐
|
||||
# │ Modul │ Status │ Lizenz-Info │
|
||||
# ├───────────┼────────┼─────────────────────────────────────────────┤
|
||||
# │ billing │ ✓ Aktiv│ Gitea Repository-Zugriff (mycrm/mycrm-...) │
|
||||
# │ invoicing │ ✗ Inakt│ Kein Gitea Access Token vorhanden │
|
||||
# └───────────┴────────┴─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Lizenz widerrufen
|
||||
|
||||
```bash
|
||||
php bin/console app:module:revoke billing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Caching & Offline-Betrieb
|
||||
|
||||
### Cache-Strategie
|
||||
|
||||
```
|
||||
Online-Validierung → 24h Cache → 7 Tage Grace Period → Offline-Fallback
|
||||
```
|
||||
|
||||
1. **Erste Validierung:** Gitea API-Call
|
||||
2. **24 Stunden:** Gecacht, keine API-Calls
|
||||
3. **Nach 24h:** Neue Online-Validierung
|
||||
4. **Offline-Fall:** Grace Period (7 Tage ab letzter erfolgreicher Validierung)
|
||||
|
||||
### Cache-Verhalten
|
||||
|
||||
```php
|
||||
// Bei jedem Request wird geprüft:
|
||||
if (cache_valid && within_24h) {
|
||||
return cached_license;
|
||||
}
|
||||
|
||||
// Online-Validierung
|
||||
try {
|
||||
$license = validate_with_gitea();
|
||||
cache($license, 24h);
|
||||
} catch (NetworkError $e) {
|
||||
// Fallback auf Cache mit Grace Period
|
||||
if (cached_license && within_7_days) {
|
||||
return cached_license;
|
||||
}
|
||||
throw LicenseException();
|
||||
}
|
||||
```
|
||||
|
||||
### Vorteile
|
||||
|
||||
- ✅ **Performance:** Nur 1 API-Call pro 24h
|
||||
- ✅ **Offline-Betrieb:** 7 Tage funktionsfähig ohne Internet
|
||||
- ✅ **Kein Dauerbetrieb offline:** Nach 7 Tagen ist Online-Validierung erforderlich
|
||||
|
||||
---
|
||||
|
||||
## API-Endpunkte
|
||||
|
||||
Das System stellt REST-API-Endpunkte für Admin-UIs bereit:
|
||||
|
||||
### GET /api/modules
|
||||
|
||||
Liste aller Module mit Lizenz-Status
|
||||
|
||||
```json
|
||||
{
|
||||
"modules": [
|
||||
{
|
||||
"identifier": "billing",
|
||||
"name": "Billing & Invoicing",
|
||||
"version": "1.0.0",
|
||||
"license": {
|
||||
"valid": true,
|
||||
"licensedTo": "Max Mustermann",
|
||||
"expiresAt": "2025-12-31T23:59:59+00:00",
|
||||
"message": "Gitea Repository-Zugriff bestätigt (mycrm/mycrm-billing-module)",
|
||||
"features": ["invoices", "payments", "reports"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/modules/{module}/license
|
||||
|
||||
Lizenz registrieren
|
||||
|
||||
```bash
|
||||
curl -X POST https://mycrm.local/api/modules/billing/license \
|
||||
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"licenseKey": "abc123def456..."}'
|
||||
```
|
||||
|
||||
### DELETE /api/modules/{module}/license
|
||||
|
||||
Lizenz widerrufen
|
||||
|
||||
```bash
|
||||
curl -X DELETE https://mycrm.local/api/modules/billing/license \
|
||||
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### ✅ DO's
|
||||
|
||||
- **Tokens niemals committen** - Nutze `.env.local` (nicht in Git)
|
||||
- **Token-Scopes minimal halten** - Nur `repo:read` erforderlich
|
||||
- **Tokens rotieren** - Regelmäßig neue Tokens generieren
|
||||
- **Team-basierte Zugriffssteuerung** - Nutze Gitea Teams für Kunden-Gruppen
|
||||
- **HTTPS verwenden** - Gitea nur über HTTPS erreichbar machen
|
||||
- **Tokens pro Modul** - Separate Tokens für jedes Modul
|
||||
|
||||
### ❌ DONT's
|
||||
|
||||
- **Keine Tokens in composer.json** - Nur in `auth.json`
|
||||
- **Keine universellen Tokens** - Nicht einen Token für alle Module
|
||||
- **Keine öffentlichen Repositories** - Module müssen private sein
|
||||
- **Keine Shared Tokens** - Jeder Kunde eigener Token
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Problem: "Kein Zugriff auf Modul-Repository"
|
||||
|
||||
**Ursachen:**
|
||||
1. Token hat keine `repo:read` Berechtigung
|
||||
2. Repository ist nicht vorhanden
|
||||
3. User ist nicht Collaborator/Team-Mitglied
|
||||
4. Repository-Name folgt nicht Namenskonvention
|
||||
|
||||
**Lösung:**
|
||||
```bash
|
||||
# Token testen
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://git.mycrm.local/api/v1/repos/mycrm/mycrm-billing-module
|
||||
|
||||
# Sollte Status 200 zurückgeben
|
||||
```
|
||||
|
||||
### Problem: "Gitea nicht erreichbar"
|
||||
|
||||
**Symptom:** Modul läuft, zeigt aber "Offline-Modus (Grace Period)"
|
||||
|
||||
**Ursache:** Gitea-Server nicht erreichbar, aber gecachte Lizenz noch gültig
|
||||
|
||||
**Aktion:** Prüfe `GITEA_BASE_URL` in `.env.local`
|
||||
|
||||
### Problem: "Grace Period abgelaufen"
|
||||
|
||||
**Symptom:** Modul wird nicht gestartet
|
||||
|
||||
**Ursache:** Letzte erfolgreiche Validierung > 7 Tage her
|
||||
|
||||
**Lösung:**
|
||||
1. Gitea-Server erreichbar machen
|
||||
2. Cache clearen: `php bin/console cache:clear`
|
||||
3. Modul neu starten
|
||||
|
||||
---
|
||||
|
||||
## Migration: Standard-Lizenzserver → Gitea
|
||||
|
||||
### Schritt 1: Bestehende Lizenzen notieren
|
||||
|
||||
```bash
|
||||
php bin/console app:module:list
|
||||
```
|
||||
|
||||
### Schritt 2: Gitea-Repositories erstellen
|
||||
|
||||
Für jedes lizenzierte Modul:
|
||||
1. Repository anlegen: `mycrm-{modul}-module`
|
||||
2. Als Private setzen
|
||||
3. Kunden als Collaborators hinzufügen
|
||||
|
||||
### Schritt 3: .env.local aktualisieren
|
||||
|
||||
```bash
|
||||
# Alt (entfernen oder auskommentieren)
|
||||
#LICENSE_BACKEND=LicenseValidator
|
||||
#LICENSE_SERVER_URL=https://license.mycrm.local
|
||||
#LICENSE_BILLING=eyJhbGciOi...
|
||||
|
||||
# Neu
|
||||
LICENSE_BACKEND=GiteaLicenseValidator
|
||||
GITEA_BASE_URL=https://git.mycrm.local
|
||||
GITEA_ORGANIZATION=mycrm
|
||||
GITEA_TOKEN_BILLING=abc123...
|
||||
```
|
||||
|
||||
### Schritt 4: Cache leeren & testen
|
||||
|
||||
```bash
|
||||
php bin/console cache:clear
|
||||
php bin/console app:module:list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Erweiterte Konfiguration
|
||||
|
||||
### Modul-spezifische Repository-Namen
|
||||
|
||||
Standardmäßig: `mycrm-{module}-module`
|
||||
|
||||
Für custom Namen, erweitere `GiteaLicenseValidator::getRepositoryName()`:
|
||||
|
||||
```php
|
||||
private function getRepositoryName(string $moduleIdentifier): string
|
||||
{
|
||||
// Custom Mapping
|
||||
$mapping = [
|
||||
'billing' => 'premium-billing-suite',
|
||||
'invoicing' => 'invoice-pro',
|
||||
];
|
||||
|
||||
return $mapping[$moduleIdentifier] ?? 'mycrm-' . $moduleIdentifier . '-module';
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Feature-Extraction
|
||||
|
||||
Erweitere `extractFeaturesFromRepository()` für eigene Logik:
|
||||
|
||||
```php
|
||||
private function extractFeaturesFromRepository(array $repoData): array
|
||||
{
|
||||
// Beispiel: Features aus Repository-README parsen
|
||||
// Beispiel: Features aus Gitea Webhooks abrufen
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vergleich: Standard vs. Gitea
|
||||
|
||||
| Feature | Standard-Lizenzserver | Gitea-Lizenzserver |
|
||||
|---------|----------------------|-------------------|
|
||||
| **Infrastruktur** | Eigene REST-API | Bestehende Gitea-Instanz |
|
||||
| **Setup-Aufwand** | Hoch | Niedrig |
|
||||
| **Verwaltung** | Custom UI | Gitea UI |
|
||||
| **Token-Typ** | JWT | Gitea Access Token |
|
||||
| **Access Control** | Custom-Logik | Gitea Permissions |
|
||||
| **Composer-Integration** | Separat | Gleiche Tokens |
|
||||
| **Offline-Support** | ✅ 7 Tage | ✅ 7 Tage |
|
||||
| **Skalierung** | Custom | Gitea |
|
||||
|
||||
---
|
||||
|
||||
## Fazit
|
||||
|
||||
Das Gitea-basierte Lizenzierungs-System ist ideal, wenn:
|
||||
|
||||
✅ Du bereits Gitea für Versionskontrolle nutzt
|
||||
✅ Du keine separate Lizenzserver-API betreiben möchtest
|
||||
✅ Du flexible, repository-basierte Access Control benötigst
|
||||
✅ Du Composer-Installation und Lizenzierung vereinheitlichen willst
|
||||
|
||||
**Nächste Schritte:**
|
||||
|
||||
1. Gitea-Instanz einrichten (falls noch nicht vorhanden)
|
||||
2. Private Modul-Repositories erstellen
|
||||
3. `.env.local` konfigurieren
|
||||
4. Erste Lizenz registrieren
|
||||
5. Testen!
|
||||
|
||||
**Fragen? Siehe auch:**
|
||||
- `PLUGIN_SYSTEM.md` - Vollständige Plugin-System-Dokumentation
|
||||
- `PLUGIN_QUICKSTART.md` - 5-Minuten-Setup
|
||||
238
docs/GITEA_QUICKSTART.md
Normal file
238
docs/GITEA_QUICKSTART.md
Normal file
@ -0,0 +1,238 @@
|
||||
# Gitea-Lizenzierung: 5-Minuten Quickstart
|
||||
|
||||
## Schritt 1: Repository in Gitea erstellen (1 Min)
|
||||
|
||||
```bash
|
||||
# In Gitea UI:
|
||||
1. Klicke auf "+" → "New Repository"
|
||||
2. Repository Name: mycrm-billing-module
|
||||
3. Owner: mycrm (oder dein User)
|
||||
4. ✅ Make Repository Private
|
||||
5. Klicke "Create Repository"
|
||||
```
|
||||
|
||||
**Repository-URL:** `https://git.mycrm.local/mycrm/mycrm-billing-module`
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Gitea Access Token generieren (1 Min)
|
||||
|
||||
```bash
|
||||
# In Gitea UI:
|
||||
1. Klicke auf dein Avatar → "Settings"
|
||||
2. Linke Sidebar → "Applications"
|
||||
3. Section "Generate New Token"
|
||||
- Token Name: "myCRM License - Billing Module"
|
||||
- ✅ Select scopes: repo
|
||||
4. Klicke "Generate Token"
|
||||
5. 📋 Kopiere den Token (wird nur einmal angezeigt!)
|
||||
```
|
||||
|
||||
**Beispiel-Token:** `abc123def456ghi789jkl012mno345`
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: myCRM konfigurieren (1 Min)
|
||||
|
||||
```bash
|
||||
# .env.local erstellen/bearbeiten
|
||||
cp .env.plugin.example .env.local
|
||||
nano .env.local
|
||||
```
|
||||
|
||||
**Konfiguration:**
|
||||
```bash
|
||||
###> Plugin-System ###
|
||||
LICENSE_BACKEND=GiteaLicenseValidator
|
||||
GITEA_BASE_URL=https://git.mycrm.local
|
||||
GITEA_ORGANIZATION=mycrm
|
||||
INSTANCE_ID=meine-crm-instance-1
|
||||
|
||||
###> Modul-Lizenzen ###
|
||||
GITEA_TOKEN_BILLING=abc123def456ghi789jkl012mno345
|
||||
```
|
||||
|
||||
**Speichern:** `Ctrl+O` → `Enter` → `Ctrl+X`
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: Modul installieren (1 Min)
|
||||
|
||||
```bash
|
||||
# Composer Repository konfigurieren
|
||||
composer config repositories.mycrm-billing vcs https://git.mycrm.local/mycrm/mycrm-billing-module
|
||||
|
||||
# Composer Auth konfigurieren (gleicher Token!)
|
||||
composer config http-basic.git.mycrm.local dein-username abc123def456ghi789jkl012mno345
|
||||
|
||||
# Modul installieren
|
||||
composer require mycrm/billing-module
|
||||
|
||||
# Migration ausführen
|
||||
php bin/console doctrine:migrations:migrate -n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: Verifizieren (1 Min)
|
||||
|
||||
```bash
|
||||
# Cache leeren
|
||||
php bin/console cache:clear
|
||||
|
||||
# Module auflisten
|
||||
php bin/console app:module:list
|
||||
```
|
||||
|
||||
**Erwartete Ausgabe:**
|
||||
```
|
||||
┌──────────┬─────────┬────────────────────────────────────────────┐
|
||||
│ Modul │ Status │ Lizenz-Info │
|
||||
├──────────┼─────────┼────────────────────────────────────────────┤
|
||||
│ billing │ ✓ Aktiv │ Gitea Repository-Zugriff bestätigt │
|
||||
│ │ │ (mycrm/mycrm-billing-module) │
|
||||
└──────────┴─────────┴────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Fertig!
|
||||
|
||||
Dein Modul ist jetzt lizenziert und einsatzbereit.
|
||||
|
||||
**Testen:**
|
||||
1. Gehe zu `https://mycrm.local/billing`
|
||||
2. Das Modul sollte geladen sein
|
||||
3. Bei Problemen: Logs prüfen in `var/log/dev.log`
|
||||
|
||||
---
|
||||
|
||||
## Weitere Benutzer lizenzieren
|
||||
|
||||
### Variante A: Gitea Collaborator hinzufügen
|
||||
|
||||
```bash
|
||||
# In Gitea UI:
|
||||
1. Gehe zu Repository: mycrm-billing-module
|
||||
2. Settings → Collaboration
|
||||
3. Klicke "Add Collaborator"
|
||||
4. User suchen und auswählen
|
||||
5. Permission: Read
|
||||
6. Klicke "Add Collaborator"
|
||||
```
|
||||
|
||||
Der neue User kann jetzt:
|
||||
1. Eigenen Token generieren
|
||||
2. In seiner myCRM-Instanz den Token eintragen
|
||||
3. Modul nutzen
|
||||
|
||||
### Variante B: Gitea Team erstellen (für mehrere User)
|
||||
|
||||
```bash
|
||||
# In Gitea UI:
|
||||
1. Gehe zu Organisation "mycrm"
|
||||
2. Teams → "Create Team"
|
||||
3. Team Name: "Premium Customers"
|
||||
4. Permissions: Read
|
||||
5. Klicke "Create Team"
|
||||
|
||||
# Dann: Team-Mitglieder hinzufügen
|
||||
1. Team → Members
|
||||
2. "Add Team Member"
|
||||
3. User auswählen
|
||||
|
||||
# Dann: Repository dem Team zuweisen
|
||||
1. Gehe zu Repository: mycrm-billing-module
|
||||
2. Settings → Collaboration
|
||||
3. "Add Team"
|
||||
4. Team: Premium Customers
|
||||
5. Permission: Read
|
||||
```
|
||||
|
||||
Alle Team-Mitglieder haben automatisch Zugriff!
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### ❌ "Kein Zugriff auf Modul-Repository"
|
||||
|
||||
**Test:**
|
||||
```bash
|
||||
curl -H "Authorization: token abc123def456..." \
|
||||
https://git.mycrm.local/api/v1/repos/mycrm/mycrm-billing-module
|
||||
```
|
||||
|
||||
**Sollte zurückgeben:** Status 200 + JSON
|
||||
|
||||
**Falls Status 404:**
|
||||
- Repository existiert nicht oder ist falsch benannt
|
||||
- Prüfe: https://git.mycrm.local/mycrm/mycrm-billing-module
|
||||
|
||||
**Falls Status 401:**
|
||||
- Token ist ungültig oder hat keine `repo` Berechtigung
|
||||
- Generiere neuen Token mit korrekten Scopes
|
||||
|
||||
### ❌ Modul erscheint nicht in Liste
|
||||
|
||||
```bash
|
||||
# Cache leeren
|
||||
php bin/console cache:clear
|
||||
|
||||
# Plugin-System neu laden
|
||||
php bin/console cache:warmup
|
||||
|
||||
# Prüfen ob .env.local geladen wird
|
||||
php bin/console debug:container --env-vars | grep GITEA
|
||||
```
|
||||
|
||||
### ❌ Composer kann nicht installieren
|
||||
|
||||
```bash
|
||||
# Auth prüfen
|
||||
cat auth.json
|
||||
|
||||
# Sollte enthalten:
|
||||
{
|
||||
"http-basic": {
|
||||
"git.mycrm.local": {
|
||||
"username": "dein-username",
|
||||
"password": "dein-token"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Neu versuchen
|
||||
composer install -vvv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- **Weitere Module lizenzieren:** Wiederhole Schritte 1-5
|
||||
- **Admin-UI nutzen:** Gehe zu `/admin/modules` für grafische Verwaltung
|
||||
- **Lizenz-Metadaten:** Lies `GITEA_LICENSE_SYSTEM.md` für Features & Ablaufdatum
|
||||
- **Vollständige Doku:** Siehe `PLUGIN_SYSTEM.md`
|
||||
|
||||
---
|
||||
|
||||
## Cheat Sheet
|
||||
|
||||
```bash
|
||||
# Token registrieren
|
||||
php bin/console app:module:license billing YOUR_TOKEN
|
||||
|
||||
# Status prüfen
|
||||
php bin/console app:module:list
|
||||
|
||||
# Lizenz widerrufen
|
||||
php bin/console app:module:revoke billing
|
||||
|
||||
# Cache leeren
|
||||
php bin/console cache:clear
|
||||
|
||||
# Gitea API testen
|
||||
curl -H "Authorization: token YOUR_TOKEN" \
|
||||
https://git.mycrm.local/api/v1/user
|
||||
```
|
||||
@ -10,7 +10,23 @@ Dieses Plugin-System ermöglicht die Installation und Verwaltung von **optionale
|
||||
|-------------|--------|--------|
|
||||
| **Leicht nachträglich installierbar** | ✅ | Composer-basierte Installation ohne Core-Änderungen |
|
||||
| **Keine Core-Abhängigkeiten** | ✅ | Interface-basierte Architektur, lose Kopplung |
|
||||
| **Lizenzvalidierung erforderlich** | ✅ | REST-API Validierung mit Offline-Fallback (7 Tage Grace Period) |
|
||||
| **Lizenzvalidierung erforderlich** | ✅ | Zwei Backends: REST-API oder **Gitea Repository-Access** (mit Offline-Fallback) |
|
||||
|
||||
### 🔐 Lizenzierungs-Backends
|
||||
|
||||
Das System unterstützt **zwei verschiedene Lizenzierungs-Backends**:
|
||||
|
||||
1. **Standard-Lizenzserver** (`LicenseValidator`)
|
||||
- REST-API basiert
|
||||
- JWT-Tokens als Lizenzen
|
||||
- Eigener Lizenzserver erforderlich
|
||||
|
||||
2. **Gitea-Lizenzserver** (`GiteaLicenseValidator`) ⭐ **NEU**
|
||||
- Nutzt Gitea Repository-Zugriff als Lizenzierung
|
||||
- Gitea Access Token = Lizenzschlüssel
|
||||
- Keine separate Lizenzserver-API nötig
|
||||
- Ideal wenn du bereits Gitea nutzt
|
||||
- **Siehe:** `GITEA_LICENSE_SYSTEM.md`
|
||||
|
||||
## 📁 Neu erstellte Dateien
|
||||
|
||||
@ -26,7 +42,8 @@ Dieses Plugin-System ermöglicht die Installation und Verwaltung von **optionale
|
||||
│ │ └── ModuleRegistry.php # ⭐ Plugin-Registry mit Auto-Discovery
|
||||
│ │
|
||||
│ ├── Service/
|
||||
│ │ └── LicenseValidator.php # ⭐ Lizenzvalidierung (Online + Cache)
|
||||
│ │ ├── LicenseValidator.php # ⭐ Standard Lizenzvalidierung (REST-API)
|
||||
│ │ └── GiteaLicenseValidator.php # ⭐ Gitea-basierte Lizenzvalidierung
|
||||
│ │
|
||||
│ ├── EventListener/
|
||||
│ │ └── ModuleBootListener.php # ⭐ Auto-Boot beim Request
|
||||
@ -47,7 +64,9 @@ Dieses Plugin-System ermöglicht die Installation und Verwaltung von **optionale
|
||||
├── .env.plugin.example # 📝 Environment-Template
|
||||
│
|
||||
└── docs/
|
||||
├── PLUGIN_SYSTEM.md # 📖 Vollständige Anleitung
|
||||
├── PLUGIN_SYSTEM.md # 📖 Vollständige Anleitung (Standard)
|
||||
├── GITEA_LICENSE_SYSTEM.md # 📖 Gitea-Lizenzierung (NEU)
|
||||
├── GITEA_QUICKSTART.md # 🚀 Gitea 5-Min Setup
|
||||
├── PLUGIN_SYSTEM_SUMMARY.md # 📊 Architektur-Zusammenfassung
|
||||
├── PLUGIN_QUICKSTART.md # 🚀 5-Minuten-Schnellstart
|
||||
├── EXAMPLE_MODULE_STRUCTURE.md # 📦 Modul-Template-Übersicht
|
||||
|
||||
@ -35,7 +35,7 @@ class ModuleLicenseCommand extends Command
|
||||
->addArgument('module', InputArgument::REQUIRED, 'Modul-Identifier')
|
||||
->addArgument('license-key', InputArgument::OPTIONAL, 'Lizenzschlüssel')
|
||||
->addOption('revoke', 'r', InputOption::VALUE_NONE, 'Lizenz widerrufen')
|
||||
->addOption('validate', 'v', InputOption::VALUE_NONE, 'Lizenz validieren (Force-Refresh)')
|
||||
->addOption('validate', null, InputOption::VALUE_NONE, 'Lizenz validieren (Force-Refresh)')
|
||||
;
|
||||
}
|
||||
|
||||
@ -48,11 +48,14 @@ class ModuleLicenseCommand extends Command
|
||||
$revoke = $input->getOption('revoke');
|
||||
$validate = $input->getOption('validate');
|
||||
|
||||
// Modul prüfen
|
||||
// Modul prüfen (optional - funktioniert auch ohne installiertes Modul)
|
||||
$module = $this->moduleRegistry->getModule($moduleIdentifier);
|
||||
|
||||
if (!$module) {
|
||||
$io->error(sprintf('Modul "%s" nicht gefunden.', $moduleIdentifier));
|
||||
return Command::FAILURE;
|
||||
$io->note(sprintf(
|
||||
'Hinweis: Modul "%s" ist nicht als Composer-Package installiert. Lizenz wird trotzdem registriert.',
|
||||
$moduleIdentifier
|
||||
));
|
||||
}
|
||||
|
||||
// Lizenz widerrufen
|
||||
@ -62,19 +65,24 @@ class ModuleLicenseCommand extends Command
|
||||
|
||||
// Lizenz validieren
|
||||
if ($validate) {
|
||||
return $this->validateLicense($io, $module);
|
||||
if ($module) {
|
||||
return $this->validateLicense($io, $module);
|
||||
} else {
|
||||
// Fallback: Direktvalidierung ohne Modul
|
||||
return $this->validateLicenseDirect($io, $moduleIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Lizenz registrieren
|
||||
if ($licenseKey) {
|
||||
return $this->registerLicense($io, $moduleIdentifier, $licenseKey);
|
||||
return $this->registerLicense($io, $moduleIdentifier, $licenseKey, $module);
|
||||
}
|
||||
|
||||
// Interaktive Eingabe
|
||||
return $this->interactiveRegister($io, $moduleIdentifier);
|
||||
}
|
||||
|
||||
private function registerLicense(SymfonyStyle $io, string $moduleIdentifier, string $licenseKey): int
|
||||
private function registerLicense(SymfonyStyle $io, string $moduleIdentifier, string $licenseKey, $module = null): int
|
||||
{
|
||||
$io->section(sprintf('Registriere Lizenz für Modul "%s"', $moduleIdentifier));
|
||||
|
||||
@ -88,9 +96,12 @@ class ModuleLicenseCommand extends Command
|
||||
$io->success('Lizenz erfolgreich registriert!');
|
||||
|
||||
// Lizenz-Info anzeigen
|
||||
$module = $this->moduleRegistry->getModule($moduleIdentifier);
|
||||
if ($module) {
|
||||
$this->displayLicenseInfo($io, $module->getLicenseInfo());
|
||||
} else {
|
||||
// Fallback: Direkte Validierung ohne Modul
|
||||
$licenseInfo = $this->licenseValidator->validate($moduleIdentifier, $licenseKey);
|
||||
$this->displayLicenseInfo($io, $licenseInfo);
|
||||
}
|
||||
|
||||
$io->note('Bitte leeren Sie den Cache, damit das Modul aktiviert wird: php bin/console cache:clear');
|
||||
@ -132,23 +143,37 @@ class ModuleLicenseCommand extends Command
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function validateLicenseDirect(SymfonyStyle $io, string $moduleIdentifier): int
|
||||
{
|
||||
$io->section(sprintf('Validiere Lizenz für Modul "%s"', $moduleIdentifier));
|
||||
|
||||
// Direkte Validierung über LicenseValidator
|
||||
$licenseInfo = $this->licenseValidator->validate($moduleIdentifier);
|
||||
|
||||
if (!$licenseInfo['valid']) {
|
||||
$io->error(sprintf('Lizenz ungültig: %s', $licenseInfo['message'] ?? 'Unbekannter Fehler'));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->success('Lizenz gültig!');
|
||||
$this->displayLicenseInfo($io, $licenseInfo);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function interactiveRegister(SymfonyStyle $io, string $moduleIdentifier): int
|
||||
{
|
||||
$io->title(sprintf('Lizenzregistrierung für Modul "%s"', $moduleIdentifier));
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question('Bitte geben Sie den Lizenzschlüssel ein: ');
|
||||
$question->setHidden(true);
|
||||
$question->setHiddenFallback(false);
|
||||
|
||||
$licenseKey = $helper->ask($input ?? null, $output ?? null, $question);
|
||||
$licenseKey = $io->askHidden('Bitte geben Sie den Lizenzschlüssel ein');
|
||||
|
||||
if (!$licenseKey) {
|
||||
$io->error('Kein Lizenzschlüssel angegeben.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return $this->registerLicense($io, $moduleIdentifier, $licenseKey);
|
||||
$module = $this->moduleRegistry->getModule($moduleIdentifier);
|
||||
return $this->registerLicense($io, $moduleIdentifier, $licenseKey, $module);
|
||||
}
|
||||
|
||||
private function displayLicenseInfo(SymfonyStyle $io, array $licenseInfo): void
|
||||
|
||||
125
src/Command/RegisterLicenseCommand.php
Normal file
125
src/Command/RegisterLicenseCommand.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Plugin\LicenseValidatorInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Registriert Lizenzen direkt im LicenseValidator
|
||||
* Benötigt KEIN installiertes Modul
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'app:license:register',
|
||||
description: 'Registriert eine Lizenz (ohne installiertes Modul)',
|
||||
)]
|
||||
class RegisterLicenseCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LicenseValidatorInterface $licenseValidator
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('module-id', InputArgument::REQUIRED, 'Modul-Identifier (z.B. "test", "billing")')
|
||||
->addArgument('token', InputArgument::REQUIRED, 'Gitea Access Token')
|
||||
->setHelp(<<<'HELP'
|
||||
Registriert eine Lizenz für ein Modul.
|
||||
|
||||
Das Modul muss NICHT installiert sein - dieser Command registriert nur die Lizenz
|
||||
in .env.local, damit sie bei zukünftigen Validierungen verwendet wird.
|
||||
|
||||
Beispiel:
|
||||
php bin/console app:license:register test abc123def456...
|
||||
|
||||
Gitea Token generieren:
|
||||
Gitea → Settings → Applications → Generate Token (Scope: repo)
|
||||
|
||||
Die Lizenz wird gespeichert als:
|
||||
GITEA_TOKEN_TEST=abc123def456...
|
||||
HELP
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$moduleId = $input->getArgument('module-id');
|
||||
$token = $input->getArgument('token');
|
||||
|
||||
$io->title(sprintf('Lizenz-Registrierung für "%s"', $moduleId));
|
||||
|
||||
// Schritt 1: Validierung durchführen
|
||||
$io->section('1. Validiere Token...');
|
||||
|
||||
try {
|
||||
$result = $this->licenseValidator->validate($moduleId, $token);
|
||||
|
||||
if (!$result['valid']) {
|
||||
$io->error('Token ungültig!');
|
||||
$io->writeln(sprintf('Grund: %s', $result['message'] ?? 'Unbekannt'));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->success('✓ Token gültig!');
|
||||
|
||||
// Details anzeigen
|
||||
$rows = [
|
||||
['Status', '✓ Gültig'],
|
||||
];
|
||||
|
||||
if (isset($result['licensedTo'])) {
|
||||
$rows[] = ['Lizenziert an', $result['licensedTo']];
|
||||
}
|
||||
|
||||
if (isset($result['message'])) {
|
||||
$rows[] = ['Repository', $result['message']];
|
||||
}
|
||||
|
||||
if (isset($result['metadata']['repository_url'])) {
|
||||
$rows[] = ['URL', $result['metadata']['repository_url']];
|
||||
}
|
||||
|
||||
$io->table(['Eigenschaft', 'Wert'], $rows);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$io->error('Validierung fehlgeschlagen');
|
||||
$io->writeln($e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Schritt 2: In .env.local speichern
|
||||
$io->section('2. Speichere Token in .env.local...');
|
||||
|
||||
$success = $this->licenseValidator->registerLicense($moduleId, $token);
|
||||
|
||||
if (!$success) {
|
||||
$io->error('Speichern fehlgeschlagen');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->success('✓ Token gespeichert!');
|
||||
|
||||
$envKey = 'GITEA_TOKEN_' . strtoupper($moduleId);
|
||||
$io->writeln(sprintf('Gespeichert als: <info>%s</info>', $envKey));
|
||||
|
||||
// Schritt 3: Cache leeren empfehlen
|
||||
$io->section('3. Nächste Schritte');
|
||||
$io->listing([
|
||||
'Cache leeren: php bin/console cache:clear',
|
||||
sprintf('Testen: php bin/console app:test:gitea-license %s', $moduleId),
|
||||
'Oder: Modul als Composer-Package installieren',
|
||||
]);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
110
src/Command/TestGiteaLicenseCommand.php
Normal file
110
src/Command/TestGiteaLicenseCommand.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Plugin\LicenseValidatorInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Test-Command für Gitea-Lizenzvalidierung
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'app:test:gitea-license',
|
||||
description: 'Testet die Gitea-Lizenzvalidierung direkt',
|
||||
)]
|
||||
class TestGiteaLicenseCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LicenseValidatorInterface $licenseValidator
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('module-id', InputArgument::REQUIRED, 'Modul-Identifier (z.B. "test")')
|
||||
->addArgument('token', InputArgument::OPTIONAL, 'Gitea Access Token (optional, nutzt ENV)')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$moduleId = $input->getArgument('module-id');
|
||||
$token = $input->getArgument('token');
|
||||
|
||||
$io->title('Gitea-Lizenzvalidierung Test');
|
||||
$io->info(sprintf('Teste Modul: %s', $moduleId));
|
||||
|
||||
if ($token) {
|
||||
$io->info('Verwende übergebenen Token');
|
||||
} else {
|
||||
$io->info(sprintf('Verwende Token aus ENV: GITEA_TOKEN_%s', strtoupper($moduleId)));
|
||||
}
|
||||
|
||||
// Validierung durchführen
|
||||
try {
|
||||
$result = $this->licenseValidator->validate($moduleId, $token);
|
||||
|
||||
if ($result['valid']) {
|
||||
$io->success('✓ Lizenz gültig!');
|
||||
|
||||
// Details anzeigen
|
||||
$rows = [
|
||||
['Status', '✓ Gültig'],
|
||||
];
|
||||
|
||||
if (isset($result['licensedTo'])) {
|
||||
$rows[] = ['Lizenziert an', $result['licensedTo']];
|
||||
}
|
||||
|
||||
if (isset($result['message'])) {
|
||||
$rows[] = ['Nachricht', $result['message']];
|
||||
}
|
||||
|
||||
if (isset($result['expiresAt']) && $result['expiresAt'] instanceof \DateTimeInterface) {
|
||||
$rows[] = ['Läuft ab', $result['expiresAt']->format('d.m.Y H:i:s')];
|
||||
}
|
||||
|
||||
if (!empty($result['features'])) {
|
||||
$rows[] = ['Features', implode(', ', $result['features'])];
|
||||
}
|
||||
|
||||
if (isset($result['metadata'])) {
|
||||
foreach ($result['metadata'] as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$rows[] = [ucfirst($key), $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$io->table(['Eigenschaft', 'Wert'], $rows);
|
||||
|
||||
return Command::SUCCESS;
|
||||
} else {
|
||||
$io->error('✗ Lizenz ungültig!');
|
||||
$io->writeln(sprintf('Grund: %s', $result['message'] ?? 'Unbekannt'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$io->error('Fehler bei der Validierung');
|
||||
$io->writeln($e->getMessage());
|
||||
|
||||
if ($output->isVerbose()) {
|
||||
$io->section('Stack Trace');
|
||||
$io->writeln($e->getTraceAsString());
|
||||
}
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
397
src/Service/GiteaLicenseValidator.php
Normal file
397
src/Service/GiteaLicenseValidator.php
Normal file
@ -0,0 +1,397 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Plugin\LicenseValidatorInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* Gitea-basierte Lizenzvalidierung
|
||||
*
|
||||
* Nutzt Gitea Repository Access als Lizenzierung:
|
||||
* - Zugriff auf privates Modul-Repository = gültige Lizenz
|
||||
* - Gitea Access Token = Lizenzschlüssel
|
||||
* - Repository-Metadaten = Lizenzinformationen (Tags, Releases)
|
||||
*
|
||||
* Vorteile:
|
||||
* - Keine separate Lizenzserver-API nötig
|
||||
* - Nutzt bestehende Gitea-Infrastruktur
|
||||
* - Access Control über Gitea Repository-Permissions
|
||||
* - Einfache Verwaltung über Gitea UI
|
||||
*/
|
||||
class GiteaLicenseValidator implements LicenseValidatorInterface
|
||||
{
|
||||
private const CACHE_PREFIX = 'gitea_license_';
|
||||
private const CACHE_TTL = 86400; // 24 Stunden
|
||||
private const GRACE_PERIOD = 604800; // 7 Tage
|
||||
|
||||
public function __construct(
|
||||
private readonly HttpClientInterface $httpClient,
|
||||
private readonly CacheItemPoolInterface $cache,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly string $giteaBaseUrl,
|
||||
private readonly string $giteaOrganization,
|
||||
private readonly string $instanceId
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(string $moduleIdentifier, ?string $licenseKey = null): array
|
||||
{
|
||||
// Gitea Access Token als Lizenzschlüssel
|
||||
if (!$licenseKey) {
|
||||
$licenseKey = $this->getStoredLicenseKey($moduleIdentifier);
|
||||
}
|
||||
|
||||
if (!$licenseKey) {
|
||||
return $this->createInvalidResponse('Kein Gitea Access Token vorhanden');
|
||||
}
|
||||
|
||||
// Cache prüfen
|
||||
$cacheKey = $this->getCacheKey($moduleIdentifier);
|
||||
$cachedItem = $this->cache->getItem($cacheKey);
|
||||
|
||||
if ($cachedItem->isHit()) {
|
||||
$cachedData = $cachedItem->get();
|
||||
|
||||
// Prüfen, ob Grace Period noch gültig ist
|
||||
if ($this->isInGracePeriod($cachedData)) {
|
||||
$this->logger->debug(sprintf(
|
||||
'Lizenz für Modul "%s" aus Cache geladen (Grace Period aktiv)',
|
||||
$moduleIdentifier
|
||||
));
|
||||
return $cachedData;
|
||||
}
|
||||
}
|
||||
|
||||
// Online-Validierung durchführen: Gitea Repository Access prüfen
|
||||
try {
|
||||
// 1. Repository-Informationen abrufen
|
||||
$repoName = $this->getRepositoryName($moduleIdentifier);
|
||||
$repoUrl = sprintf(
|
||||
'%s/api/v1/repos/%s/%s',
|
||||
rtrim($this->giteaBaseUrl, '/'),
|
||||
$this->giteaOrganization,
|
||||
$repoName
|
||||
);
|
||||
|
||||
$response = $this->httpClient->request('GET', $repoUrl, [
|
||||
'headers' => [
|
||||
'Authorization' => 'token ' . $licenseKey,
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
if ($statusCode !== 200) {
|
||||
$this->logger->warning(sprintf(
|
||||
'Kein Zugriff auf Gitea-Repository "%s" (Status %d)',
|
||||
$repoName,
|
||||
$statusCode
|
||||
));
|
||||
|
||||
return $this->createInvalidResponse('Kein Zugriff auf Modul-Repository');
|
||||
}
|
||||
|
||||
$repoData = $response->toArray();
|
||||
|
||||
// 2. Optional: User-Informationen abrufen für "Licensed To"
|
||||
// (benötigt read:user Scope, daher optional)
|
||||
$userData = [];
|
||||
try {
|
||||
$userResponse = $this->httpClient->request('GET',
|
||||
rtrim($this->giteaBaseUrl, '/') . '/api/v1/user',
|
||||
[
|
||||
'headers' => [
|
||||
'Authorization' => 'token ' . $licenseKey,
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
'timeout' => 10,
|
||||
]
|
||||
);
|
||||
|
||||
if ($userResponse->getStatusCode() === 200) {
|
||||
$userData = $userResponse->toArray();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// User-API-Call ist optional, ignorieren wenn fehlgeschlagen
|
||||
$this->logger->debug('User-API-Call fehlgeschlagen (optional): ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 3. Optional: Repository-Tags/Releases für Features und Ablaufdatum
|
||||
$features = $this->extractFeaturesFromRepository($repoData);
|
||||
$expiresAt = $this->extractExpirationFromRepository($repoData);
|
||||
|
||||
// Lizenz gültig
|
||||
$licensedTo = $userData['full_name'] ?? $userData['username'] ?? null;
|
||||
if (!$licensedTo && isset($repoData['owner']['full_name'])) {
|
||||
$licensedTo = $repoData['owner']['full_name'];
|
||||
} elseif (!$licensedTo && isset($repoData['owner']['username'])) {
|
||||
$licensedTo = $repoData['owner']['username'];
|
||||
} else {
|
||||
$licensedTo = $licensedTo ?? 'Repository Owner';
|
||||
}
|
||||
|
||||
$validationResult = [
|
||||
'valid' => true,
|
||||
'expiresAt' => $expiresAt,
|
||||
'licensedTo' => $licensedTo,
|
||||
'features' => $features,
|
||||
'message' => sprintf(
|
||||
'Gitea Repository-Zugriff bestätigt (%s)',
|
||||
$repoData['full_name'] ?? $repoName
|
||||
),
|
||||
'cachedUntil' => new \DateTimeImmutable('+' . self::CACHE_TTL . ' seconds'),
|
||||
'validatedAt' => new \DateTimeImmutable(),
|
||||
'metadata' => [
|
||||
'repository' => $repoData['full_name'] ?? null,
|
||||
'repository_url' => $repoData['html_url'] ?? null,
|
||||
'gitea_user' => $userData['username'] ?? ($repoData['owner']['username'] ?? null),
|
||||
],
|
||||
];
|
||||
|
||||
// In Cache speichern
|
||||
$cachedItem->set($validationResult);
|
||||
$cachedItem->expiresAfter(self::CACHE_TTL);
|
||||
$this->cache->save($cachedItem);
|
||||
|
||||
$this->logger->info(sprintf(
|
||||
'Gitea-Lizenz für Modul "%s" erfolgreich validiert (User: %s)',
|
||||
$moduleIdentifier,
|
||||
$userData['username'] ?? 'unbekannt'
|
||||
));
|
||||
|
||||
return $validationResult;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
// Netzwerkfehler oder Access Denied - Grace Period prüfen
|
||||
$this->logger->error(sprintf(
|
||||
'Fehler bei Gitea-Lizenzvalidierung für Modul "%s": %s',
|
||||
$moduleIdentifier,
|
||||
$e->getMessage()
|
||||
));
|
||||
|
||||
// Wenn gecachte Lizenz vorhanden und in Grace Period, verwenden
|
||||
if ($cachedItem->isHit()) {
|
||||
$cachedData = $cachedItem->get();
|
||||
|
||||
if ($this->isInGracePeriod($cachedData)) {
|
||||
$this->logger->info(sprintf(
|
||||
'Verwende gecachte Gitea-Lizenz für Modul "%s" (Offline-Modus, Grace Period)',
|
||||
$moduleIdentifier
|
||||
));
|
||||
|
||||
$cachedData['message'] = 'Gitea-Lizenz gecacht (Offline-Modus)';
|
||||
return $cachedData;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createInvalidResponse(
|
||||
'Gitea nicht erreichbar und keine gecachte Lizenz verfügbar'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function registerLicense(string $moduleIdentifier, string $licenseKey): bool
|
||||
{
|
||||
try {
|
||||
// Gitea Token validieren
|
||||
$validationResult = $this->validate($moduleIdentifier, $licenseKey);
|
||||
|
||||
if (!$validationResult['valid']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// In .env.local speichern
|
||||
$envFile = dirname(__DIR__, 2) . '/.env.local';
|
||||
$envKey = 'GITEA_TOKEN_' . strtoupper($moduleIdentifier);
|
||||
|
||||
$content = file_exists($envFile) ? file_get_contents($envFile) : '';
|
||||
$pattern = '/^' . preg_quote($envKey, '/') . '=.*$/m';
|
||||
|
||||
if (preg_match($pattern, $content)) {
|
||||
// Bestehenden Eintrag ersetzen
|
||||
$content = preg_replace($pattern, "$envKey=$licenseKey", $content);
|
||||
} else {
|
||||
// Neuen Eintrag hinzufügen
|
||||
$content .= "\n$envKey=$licenseKey\n";
|
||||
}
|
||||
|
||||
file_put_contents($envFile, $content);
|
||||
|
||||
$this->logger->info(sprintf(
|
||||
'Gitea-Token für Modul "%s" registriert',
|
||||
$moduleIdentifier
|
||||
));
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(sprintf(
|
||||
'Fehler beim Registrieren des Gitea-Tokens für Modul "%s": %s',
|
||||
$moduleIdentifier,
|
||||
$e->getMessage()
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function revokeLicense(string $moduleIdentifier): void
|
||||
{
|
||||
// Aus Cache löschen
|
||||
$cacheKey = $this->getCacheKey($moduleIdentifier);
|
||||
$this->cache->deleteItem($cacheKey);
|
||||
|
||||
// Aus Environment entfernen
|
||||
$envFile = dirname(__DIR__, 2) . '/.env.local';
|
||||
$envKey = 'GITEA_TOKEN_' . strtoupper($moduleIdentifier);
|
||||
|
||||
if (file_exists($envFile)) {
|
||||
$content = file_get_contents($envFile);
|
||||
$pattern = '/^' . preg_quote($envKey, '/') . '=.*$\n?/m';
|
||||
$content = preg_replace($pattern, '', $content);
|
||||
file_put_contents($envFile, $content);
|
||||
}
|
||||
|
||||
$this->logger->info(sprintf(
|
||||
'Gitea-Token für Modul "%s" widerrufen',
|
||||
$moduleIdentifier
|
||||
));
|
||||
}
|
||||
|
||||
public function getRegisteredLicenses(): array
|
||||
{
|
||||
$licenses = [];
|
||||
|
||||
// Alle GITEA_TOKEN_* Environment-Variablen durchsuchen
|
||||
foreach ($_ENV as $key => $value) {
|
||||
if (str_starts_with($key, 'GITEA_TOKEN_')) {
|
||||
$moduleIdentifier = strtolower(substr($key, 12));
|
||||
$licenses[$moduleIdentifier] = $this->validate($moduleIdentifier, $value);
|
||||
}
|
||||
}
|
||||
|
||||
return $licenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt einen gespeicherten Gitea Access Token
|
||||
*/
|
||||
private function getStoredLicenseKey(string $moduleIdentifier): ?string
|
||||
{
|
||||
$envKey = 'GITEA_TOKEN_' . strtoupper($moduleIdentifier);
|
||||
return $_ENV[$envKey] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Repository-Namen aus Modul-Identifier
|
||||
*
|
||||
* Beispiel: "billing" -> "mycrm-billing-module"
|
||||
*/
|
||||
private function getRepositoryName(string $moduleIdentifier): string
|
||||
{
|
||||
return 'mycrm-' . $moduleIdentifier . '-module';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert Features aus Repository-Metadaten
|
||||
*
|
||||
* Features können in Repository-Topics oder Tags definiert werden
|
||||
*/
|
||||
private function extractFeaturesFromRepository(array $repoData): array
|
||||
{
|
||||
$features = [];
|
||||
|
||||
// Features aus Topics (Gitea Repository Topics)
|
||||
if (isset($repoData['topics']) && is_array($repoData['topics'])) {
|
||||
foreach ($repoData['topics'] as $topic) {
|
||||
if (str_starts_with($topic, 'feature-')) {
|
||||
$features[] = substr($topic, 8); // "feature-" entfernen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: Alle Topics als Features
|
||||
if (empty($features) && isset($repoData['topics'])) {
|
||||
$features = $repoData['topics'];
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert Ablaufdatum aus Repository-Metadaten
|
||||
*
|
||||
* Kann z.B. aus Repository-Description geparst werden:
|
||||
* "License expires: 2025-12-31"
|
||||
*/
|
||||
private function extractExpirationFromRepository(array $repoData): ?\DateTimeImmutable
|
||||
{
|
||||
if (!isset($repoData['description'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$description = $repoData['description'];
|
||||
|
||||
// Pattern: "expires: YYYY-MM-DD" oder "valid until: YYYY-MM-DD"
|
||||
if (preg_match('/(?:expires?|valid until):\s*(\d{4}-\d{2}-\d{2})/i', $description, $matches)) {
|
||||
try {
|
||||
return new \DateTimeImmutable($matches[1]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->warning(sprintf(
|
||||
'Ungültiges Ablaufdatum in Repository-Description: %s',
|
||||
$matches[1]
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft, ob eine gecachte Lizenz noch in der Grace Period ist
|
||||
*/
|
||||
private function isInGracePeriod(array $cachedData): bool
|
||||
{
|
||||
if (!isset($cachedData['validatedAt']) || !$cachedData['valid']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$validatedAt = $cachedData['validatedAt'];
|
||||
if (!$validatedAt instanceof \DateTimeInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
$gracePeriodEnd = $validatedAt->modify('+' . self::GRACE_PERIOD . ' seconds');
|
||||
|
||||
return $now <= $gracePeriodEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine "ungültig" Response
|
||||
*/
|
||||
private function createInvalidResponse(string $message): array
|
||||
{
|
||||
return [
|
||||
'valid' => false,
|
||||
'expiresAt' => null,
|
||||
'licensedTo' => null,
|
||||
'features' => [],
|
||||
'message' => $message,
|
||||
'cachedUntil' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert Cache-Key für ein Modul
|
||||
*/
|
||||
private function getCacheKey(string $moduleIdentifier): string
|
||||
{
|
||||
return self::CACHE_PREFIX . $moduleIdentifier;
|
||||
}
|
||||
}
|
||||
@ -73,6 +73,9 @@
|
||||
"config/packages/knpu_oauth2_client.yaml"
|
||||
]
|
||||
},
|
||||
"mycrm/test-module": {
|
||||
"version": "dev-main"
|
||||
},
|
||||
"nelmio/cors-bundle": {
|
||||
"version": "2.6",
|
||||
"recipe": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user