lizenzserver
Dieser Commit ist enthalten in:
1
v2_lizenzserver/app/api/__init__.py
Normale Datei
1
v2_lizenzserver/app/api/__init__.py
Normale Datei
@@ -0,0 +1 @@
|
||||
from . import license, version
|
||||
209
v2_lizenzserver/app/api/license.py
Normale Datei
209
v2_lizenzserver/app/api/license.py
Normale Datei
@@ -0,0 +1,209 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any
|
||||
|
||||
from app.db.database import get_db
|
||||
from app.models.models import License, Activation, Version
|
||||
from app.schemas.license import (
|
||||
LicenseActivationRequest,
|
||||
LicenseActivationResponse,
|
||||
LicenseVerificationRequest,
|
||||
LicenseVerificationResponse
|
||||
)
|
||||
from app.core.security import get_api_key
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/activate", response_model=LicenseActivationResponse)
|
||||
async def activate_license(
|
||||
request: LicenseActivationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
api_key = Depends(get_api_key)
|
||||
):
|
||||
license = db.query(License).filter(
|
||||
License.license_key == request.license_key,
|
||||
License.is_active == True
|
||||
).first()
|
||||
|
||||
if not license:
|
||||
return LicenseActivationResponse(
|
||||
success=False,
|
||||
message="Invalid license key"
|
||||
)
|
||||
|
||||
if license.expires_at and license.expires_at < datetime.utcnow():
|
||||
return LicenseActivationResponse(
|
||||
success=False,
|
||||
message="License has expired"
|
||||
)
|
||||
|
||||
existing_activations = db.query(Activation).filter(
|
||||
Activation.license_id == license.id,
|
||||
Activation.is_active == True
|
||||
).all()
|
||||
|
||||
existing_machine = next(
|
||||
(a for a in existing_activations if a.machine_id == request.machine_id),
|
||||
None
|
||||
)
|
||||
|
||||
if existing_machine:
|
||||
if existing_machine.hardware_hash != request.hardware_hash:
|
||||
return LicenseActivationResponse(
|
||||
success=False,
|
||||
message="Hardware mismatch for this machine"
|
||||
)
|
||||
|
||||
existing_machine.last_heartbeat = datetime.utcnow()
|
||||
existing_machine.app_version = request.app_version
|
||||
existing_machine.os_info = request.os_info
|
||||
db.commit()
|
||||
|
||||
return LicenseActivationResponse(
|
||||
success=True,
|
||||
message="License reactivated successfully",
|
||||
activation_id=existing_machine.id,
|
||||
expires_at=license.expires_at,
|
||||
features={"all_features": True}
|
||||
)
|
||||
|
||||
if len(existing_activations) >= license.max_activations:
|
||||
return LicenseActivationResponse(
|
||||
success=False,
|
||||
message=f"Maximum activations ({license.max_activations}) reached"
|
||||
)
|
||||
|
||||
new_activation = Activation(
|
||||
license_id=license.id,
|
||||
machine_id=request.machine_id,
|
||||
hardware_hash=request.hardware_hash,
|
||||
os_info=request.os_info,
|
||||
app_version=request.app_version
|
||||
)
|
||||
|
||||
db.add(new_activation)
|
||||
db.commit()
|
||||
db.refresh(new_activation)
|
||||
|
||||
return LicenseActivationResponse(
|
||||
success=True,
|
||||
message="License activated successfully",
|
||||
activation_id=new_activation.id,
|
||||
expires_at=license.expires_at,
|
||||
features={"all_features": True}
|
||||
)
|
||||
|
||||
@router.post("/verify", response_model=LicenseVerificationResponse)
|
||||
async def verify_license(
|
||||
request: LicenseVerificationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
api_key = Depends(get_api_key)
|
||||
):
|
||||
activation = db.query(Activation).filter(
|
||||
Activation.id == request.activation_id,
|
||||
Activation.machine_id == request.machine_id,
|
||||
Activation.is_active == True
|
||||
).first()
|
||||
|
||||
if not activation:
|
||||
return LicenseVerificationResponse(
|
||||
valid=False,
|
||||
message="Invalid activation"
|
||||
)
|
||||
|
||||
license = activation.license
|
||||
|
||||
if not license.is_active:
|
||||
return LicenseVerificationResponse(
|
||||
valid=False,
|
||||
message="License is no longer active"
|
||||
)
|
||||
|
||||
if license.license_key != request.license_key:
|
||||
return LicenseVerificationResponse(
|
||||
valid=False,
|
||||
message="License key mismatch"
|
||||
)
|
||||
|
||||
if activation.hardware_hash != request.hardware_hash:
|
||||
grace_period = datetime.utcnow() - timedelta(days=settings.OFFLINE_GRACE_PERIOD_DAYS)
|
||||
if activation.last_heartbeat < grace_period:
|
||||
return LicenseVerificationResponse(
|
||||
valid=False,
|
||||
message="Hardware mismatch and grace period expired"
|
||||
)
|
||||
else:
|
||||
return LicenseVerificationResponse(
|
||||
valid=True,
|
||||
message="Hardware mismatch but within grace period",
|
||||
expires_at=license.expires_at,
|
||||
features={"all_features": True}
|
||||
)
|
||||
|
||||
if license.expires_at and license.expires_at < datetime.utcnow():
|
||||
return LicenseVerificationResponse(
|
||||
valid=False,
|
||||
message="License has expired"
|
||||
)
|
||||
|
||||
activation.last_heartbeat = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
|
||||
requires_update = False
|
||||
update_url = None
|
||||
|
||||
if latest_version and activation.app_version:
|
||||
if latest_version.version_number > activation.app_version:
|
||||
requires_update = True
|
||||
update_url = latest_version.download_url
|
||||
|
||||
return LicenseVerificationResponse(
|
||||
valid=True,
|
||||
message="License is valid",
|
||||
expires_at=license.expires_at,
|
||||
features={"all_features": True},
|
||||
requires_update=requires_update,
|
||||
update_url=update_url
|
||||
)
|
||||
|
||||
@router.get("/info/{license_key}")
|
||||
async def get_license_info(
|
||||
license_key: str,
|
||||
db: Session = Depends(get_db),
|
||||
api_key = Depends(get_api_key)
|
||||
):
|
||||
license = db.query(License).filter(
|
||||
License.license_key == license_key
|
||||
).first()
|
||||
|
||||
if not license:
|
||||
raise HTTPException(status_code=404, detail="License not found")
|
||||
|
||||
activations = db.query(Activation).filter(
|
||||
Activation.license_id == license.id,
|
||||
Activation.is_active == True
|
||||
).all()
|
||||
|
||||
return {
|
||||
"license_key": license.license_key,
|
||||
"product_id": license.product_id,
|
||||
"customer_email": license.customer_email,
|
||||
"customer_name": license.customer_name,
|
||||
"is_active": license.is_active,
|
||||
"expires_at": license.expires_at,
|
||||
"max_activations": license.max_activations,
|
||||
"current_activations": len(activations),
|
||||
"activations": [
|
||||
{
|
||||
"id": a.id,
|
||||
"machine_id": a.machine_id,
|
||||
"activation_date": a.activation_date,
|
||||
"last_heartbeat": a.last_heartbeat,
|
||||
"app_version": a.app_version
|
||||
}
|
||||
for a in activations
|
||||
]
|
||||
}
|
||||
84
v2_lizenzserver/app/api/version.py
Normale Datei
84
v2_lizenzserver/app/api/version.py
Normale Datei
@@ -0,0 +1,84 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from packaging import version
|
||||
|
||||
from app.db.database import get_db
|
||||
from app.models.models import Version, License
|
||||
from app.schemas.license import VersionCheckRequest, VersionCheckResponse
|
||||
from app.core.security import get_api_key
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/check", response_model=VersionCheckResponse)
|
||||
async def check_version(
|
||||
request: VersionCheckRequest,
|
||||
db: Session = Depends(get_db),
|
||||
api_key = Depends(get_api_key)
|
||||
):
|
||||
license = db.query(License).filter(
|
||||
License.license_key == request.license_key,
|
||||
License.is_active == True
|
||||
).first()
|
||||
|
||||
if not license:
|
||||
return VersionCheckResponse(
|
||||
latest_version=request.current_version,
|
||||
current_version=request.current_version,
|
||||
update_available=False,
|
||||
is_mandatory=False
|
||||
)
|
||||
|
||||
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
|
||||
|
||||
if not latest_version:
|
||||
return VersionCheckResponse(
|
||||
latest_version=request.current_version,
|
||||
current_version=request.current_version,
|
||||
update_available=False,
|
||||
is_mandatory=False
|
||||
)
|
||||
|
||||
current_ver = version.parse(request.current_version)
|
||||
latest_ver = version.parse(latest_version.version_number)
|
||||
|
||||
update_available = latest_ver > current_ver
|
||||
is_mandatory = False
|
||||
|
||||
if update_available and latest_version.is_mandatory:
|
||||
if latest_version.min_version:
|
||||
min_ver = version.parse(latest_version.min_version)
|
||||
is_mandatory = current_ver < min_ver
|
||||
else:
|
||||
is_mandatory = True
|
||||
|
||||
return VersionCheckResponse(
|
||||
latest_version=latest_version.version_number,
|
||||
current_version=request.current_version,
|
||||
update_available=update_available,
|
||||
is_mandatory=is_mandatory,
|
||||
download_url=latest_version.download_url if update_available else None,
|
||||
release_notes=latest_version.release_notes if update_available else None
|
||||
)
|
||||
|
||||
@router.get("/latest")
|
||||
async def get_latest_version(
|
||||
db: Session = Depends(get_db),
|
||||
api_key = Depends(get_api_key)
|
||||
):
|
||||
latest_version = db.query(Version).order_by(Version.release_date.desc()).first()
|
||||
|
||||
if not latest_version:
|
||||
return {
|
||||
"version": "1.0.0",
|
||||
"release_date": None,
|
||||
"release_notes": "Initial release"
|
||||
}
|
||||
|
||||
return {
|
||||
"version": latest_version.version_number,
|
||||
"release_date": latest_version.release_date,
|
||||
"is_mandatory": latest_version.is_mandatory,
|
||||
"min_version": latest_version.min_version,
|
||||
"download_url": latest_version.download_url,
|
||||
"release_notes": latest_version.release_notes
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren