enterprise: add support for license flags (#10842)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-08-09 22:20:01 +02:00
committed by GitHub
parent 25a06716ff
commit a073b7a5b1
6 changed files with 64 additions and 38 deletions

View File

@ -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"])

View File

@ -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

View File

@ -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",

View File

@ -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:

View File

@ -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>`
: "-"} : "-"}

View File

@ -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``;
} }
} }