enterprise: add support for license flags (#10842)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
"""Enterprise API Views"""
|
"""Enterprise API Views"""
|
||||||
|
|
||||||
from dataclasses import asdict
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@ -104,8 +103,7 @@ class LicenseViewSet(UsedByMixin, ModelViewSet):
|
|||||||
@action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
|
@action(detail=False, methods=["GET"], permission_classes=[IsAuthenticated])
|
||||||
def summary(self, request: Request) -> Response:
|
def summary(self, request: Request) -> Response:
|
||||||
"""Get the total license status"""
|
"""Get the total license status"""
|
||||||
response = LicenseSummarySerializer(data=asdict(LicenseKey.cached_summary()))
|
response = LicenseSummarySerializer(instance=LicenseKey.cached_summary())
|
||||||
response.is_valid(raise_exception=True)
|
|
||||||
return Response(response.data)
|
return Response(response.data)
|
||||||
|
|
||||||
@permission_required(None, ["authentik_enterprise.view_license"])
|
@permission_required(None, ["authentik_enterprise.view_license"])
|
||||||
|
@ -20,6 +20,7 @@ from rest_framework.fields import (
|
|||||||
ChoiceField,
|
ChoiceField,
|
||||||
DateTimeField,
|
DateTimeField,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
|
ListField,
|
||||||
)
|
)
|
||||||
|
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
@ -55,6 +56,7 @@ class LicenseFlags(Enum):
|
|||||||
"""License flags"""
|
"""License flags"""
|
||||||
|
|
||||||
TRIAL = "trial"
|
TRIAL = "trial"
|
||||||
|
NON_PRODUCTION = "non_production"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -65,6 +67,7 @@ class LicenseSummary:
|
|||||||
external_users: int
|
external_users: int
|
||||||
status: LicenseUsageStatus
|
status: LicenseUsageStatus
|
||||||
latest_valid: datetime
|
latest_valid: datetime
|
||||||
|
license_flags: list[LicenseFlags]
|
||||||
|
|
||||||
|
|
||||||
class LicenseSummarySerializer(PassiveSerializer):
|
class LicenseSummarySerializer(PassiveSerializer):
|
||||||
@ -74,6 +77,7 @@ class LicenseSummarySerializer(PassiveSerializer):
|
|||||||
external_users = IntegerField(required=True)
|
external_users = IntegerField(required=True)
|
||||||
status = ChoiceField(choices=LicenseUsageStatus.choices)
|
status = ChoiceField(choices=LicenseUsageStatus.choices)
|
||||||
latest_valid = DateTimeField()
|
latest_valid = DateTimeField()
|
||||||
|
license_flags = ListField(child=ChoiceField(choices=tuple(x.value for x in LicenseFlags)))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -86,7 +90,7 @@ class LicenseKey:
|
|||||||
name: str
|
name: str
|
||||||
internal_users: int = 0
|
internal_users: int = 0
|
||||||
external_users: int = 0
|
external_users: int = 0
|
||||||
flags: list[LicenseFlags] = field(default_factory=list)
|
license_flags: list[LicenseFlags] = field(default_factory=list)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate(jwt: str, check_expiry=True) -> "LicenseKey":
|
def validate(jwt: str, check_expiry=True) -> "LicenseKey":
|
||||||
@ -132,7 +136,7 @@ class LicenseKey:
|
|||||||
total.exp = exp_ts
|
total.exp = exp_ts
|
||||||
if exp_ts <= total.exp:
|
if exp_ts <= total.exp:
|
||||||
total.exp = exp_ts
|
total.exp = exp_ts
|
||||||
total.flags.extend(lic.status.flags)
|
total.license_flags.extend(lic.status.license_flags)
|
||||||
return total
|
return total
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -216,6 +220,7 @@ class LicenseKey:
|
|||||||
internal_users=self.internal_users,
|
internal_users=self.internal_users,
|
||||||
external_users=self.external_users,
|
external_users=self.external_users,
|
||||||
status=status,
|
status=status,
|
||||||
|
license_flags=self.license_flags,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -6357,13 +6357,9 @@
|
|||||||
"authentik_sources_plex.change_plexsourcepropertymapping",
|
"authentik_sources_plex.change_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.delete_plexsourcepropertymapping",
|
"authentik_sources_plex.delete_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.view_plexsourcepropertymapping",
|
"authentik_sources_plex.view_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.add_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.add_userplexsourceconnection",
|
"authentik_sources_plex.add_userplexsourceconnection",
|
||||||
"authentik_sources_plex.change_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.change_userplexsourceconnection",
|
"authentik_sources_plex.change_userplexsourceconnection",
|
||||||
"authentik_sources_plex.delete_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.delete_userplexsourceconnection",
|
"authentik_sources_plex.delete_userplexsourceconnection",
|
||||||
"authentik_sources_plex.view_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.view_userplexsourceconnection",
|
"authentik_sources_plex.view_userplexsourceconnection",
|
||||||
"authentik_sources_saml.add_groupsamlsourceconnection",
|
"authentik_sources_saml.add_groupsamlsourceconnection",
|
||||||
"authentik_sources_saml.change_groupsamlsourceconnection",
|
"authentik_sources_saml.change_groupsamlsourceconnection",
|
||||||
@ -12016,13 +12012,9 @@
|
|||||||
"authentik_sources_plex.change_plexsourcepropertymapping",
|
"authentik_sources_plex.change_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.delete_plexsourcepropertymapping",
|
"authentik_sources_plex.delete_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.view_plexsourcepropertymapping",
|
"authentik_sources_plex.view_plexsourcepropertymapping",
|
||||||
"authentik_sources_plex.add_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.add_userplexsourceconnection",
|
"authentik_sources_plex.add_userplexsourceconnection",
|
||||||
"authentik_sources_plex.change_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.change_userplexsourceconnection",
|
"authentik_sources_plex.change_userplexsourceconnection",
|
||||||
"authentik_sources_plex.delete_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.delete_userplexsourceconnection",
|
"authentik_sources_plex.delete_userplexsourceconnection",
|
||||||
"authentik_sources_plex.view_plexsourceconnection",
|
|
||||||
"authentik_sources_plex.view_userplexsourceconnection",
|
"authentik_sources_plex.view_userplexsourceconnection",
|
||||||
"authentik_sources_saml.add_groupsamlsourceconnection",
|
"authentik_sources_saml.add_groupsamlsourceconnection",
|
||||||
"authentik_sources_saml.change_groupsamlsourceconnection",
|
"authentik_sources_saml.change_groupsamlsourceconnection",
|
||||||
|
10
schema.yml
10
schema.yml
@ -41352,6 +41352,11 @@ components:
|
|||||||
- key
|
- key
|
||||||
- license_uuid
|
- license_uuid
|
||||||
- name
|
- name
|
||||||
|
LicenseFlagsEnum:
|
||||||
|
enum:
|
||||||
|
- trial
|
||||||
|
- non_production
|
||||||
|
type: string
|
||||||
LicenseForecast:
|
LicenseForecast:
|
||||||
type: object
|
type: object
|
||||||
description: Serializer for license forecast
|
description: Serializer for license forecast
|
||||||
@ -41391,10 +41396,15 @@ components:
|
|||||||
latest_valid:
|
latest_valid:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
license_flags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/LicenseFlagsEnum'
|
||||||
required:
|
required:
|
||||||
- external_users
|
- external_users
|
||||||
- internal_users
|
- internal_users
|
||||||
- latest_valid
|
- latest_valid
|
||||||
|
- license_flags
|
||||||
- status
|
- status
|
||||||
LicenseSummaryStatusEnum:
|
LicenseSummaryStatusEnum:
|
||||||
enum:
|
enum:
|
||||||
|
@ -183,7 +183,8 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||||||
header=${msg("Expiry")}
|
header=${msg("Expiry")}
|
||||||
subtext=${msg("Cumulative license expiry")}
|
subtext=${msg("Cumulative license expiry")}
|
||||||
>
|
>
|
||||||
${this.summary?.status === LicenseSummaryStatusEnum.Unlicensed
|
${this.summary &&
|
||||||
|
this.summary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
||||||
? html`<div>${getRelativeTime(this.summary.latestValid)}</div>
|
? html`<div>${getRelativeTime(this.summary.latestValid)}</div>
|
||||||
<small>${this.summary.latestValid.toLocaleString()}</small>`
|
<small>${this.summary.latestValid.toLocaleString()}</small>`
|
||||||
: "-"}
|
: "-"}
|
||||||
|
@ -2,23 +2,45 @@ import { AKElement } from "@goauthentik/elements/Base";
|
|||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||||
|
|
||||||
import { msg } from "@lit/localize";
|
import { msg } from "@lit/localize";
|
||||||
import { CSSResult, TemplateResult, html } from "lit";
|
import { CSSResult, TemplateResult, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
|
||||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||||
|
|
||||||
import { LicenseSummaryStatusEnum } from "@goauthentik/api";
|
import { LicenseFlagsEnum, LicenseSummaryStatusEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
@customElement("ak-enterprise-status")
|
@customElement("ak-enterprise-status")
|
||||||
export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
||||||
@property()
|
@property()
|
||||||
interface: "admin" | "user" | "" = "";
|
interface: "admin" | "user" | "flow" | "" = "";
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [PFBanner];
|
return [PFBanner];
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBanner(): TemplateResult {
|
renderStatusBanner() {
|
||||||
|
// Check if we're in the correct interface to render a banner
|
||||||
|
switch (this.licenseSummary.status) {
|
||||||
|
// user warning is both on admin interface and user interface
|
||||||
|
case LicenseSummaryStatusEnum.LimitExceededUser:
|
||||||
|
if (
|
||||||
|
this.interface.toLowerCase() !== "user" &&
|
||||||
|
this.interface.toLowerCase() !== "admin"
|
||||||
|
) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LicenseSummaryStatusEnum.ExpirySoon:
|
||||||
|
case LicenseSummaryStatusEnum.Expired:
|
||||||
|
case LicenseSummaryStatusEnum.LimitExceededAdmin:
|
||||||
|
if (this.interface.toLowerCase() !== "admin") {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LicenseSummaryStatusEnum.ReadOnly:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
let message = "";
|
let message = "";
|
||||||
switch (this.licenseSummary.status) {
|
switch (this.licenseSummary.status) {
|
||||||
case LicenseSummaryStatusEnum.LimitExceededAdmin:
|
case LicenseSummaryStatusEnum.LimitExceededAdmin:
|
||||||
@ -44,7 +66,8 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return html`<div
|
return html`<div
|
||||||
class="pf-c-banner ${this.licenseSummary?.status === LicenseSummaryStatusEnum.ReadOnly
|
class="pf-c-banner pf-m-sticky ${this.licenseSummary?.status ===
|
||||||
|
LicenseSummaryStatusEnum.ReadOnly
|
||||||
? "pf-m-red"
|
? "pf-m-red"
|
||||||
: "pf-m-gold"}"
|
: "pf-m-gold"}"
|
||||||
>
|
>
|
||||||
@ -53,26 +76,23 @@ export class EnterpriseStatusBanner extends WithLicenseSummary(AKElement) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderFlagBanner(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.licenseSummary.licenseFlags.includes(LicenseFlagsEnum.Trial)
|
||||||
|
? html`<div class="pf-c-banner pf-m-sticky pf-m-gold">
|
||||||
|
${msg("This authentik instance uses a Trial license.")}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
${this.licenseSummary.licenseFlags.includes(LicenseFlagsEnum.NonProduction)
|
||||||
|
? html`<div class="pf-c-banner pf-m-sticky pf-m-gold">
|
||||||
|
${msg("This authentik instance uses a Non-production license.")}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
switch (this.licenseSummary.status) {
|
return html`${this.renderFlagBanner()}${this.renderStatusBanner()}`;
|
||||||
case LicenseSummaryStatusEnum.LimitExceededUser:
|
|
||||||
if (this.interface.toLowerCase() === "user") {
|
|
||||||
return this.renderBanner();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case LicenseSummaryStatusEnum.ExpirySoon:
|
|
||||||
case LicenseSummaryStatusEnum.Expired:
|
|
||||||
case LicenseSummaryStatusEnum.LimitExceededAdmin:
|
|
||||||
if (this.interface.toLowerCase() === "admin") {
|
|
||||||
return this.renderBanner();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case LicenseSummaryStatusEnum.ReadOnly:
|
|
||||||
return this.renderBanner();
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return html``;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user