enterprise/providers: Add RAC [AUTH-15] (#7291)

* add basic guacamole

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make everything mostly work

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add rac build to CI

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix resize, fix web lint, sendSize correctly

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* pre-send connection from client, format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* improve throughput

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleanup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework TokenOutpostConsumer into middleware

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix some layout issues

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add outpost controllers

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start testing audio things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix a bunch of things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add deps

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix to work with outpost group

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add simple loadbalancing

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add simple reconnect

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* show reconnecting text

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix error when checking ports

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* move to providers

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add flow check to interface

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix go lint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix rac app label

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix audio

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add logging

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* cleanup

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* allow overriding all settings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix duplicate keyboard, debug high DPI

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add deps

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix lint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing __init__.py breaking model loading

I love python

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* bump successful ws connection to info

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* hide cursor since guac draws that

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add clipboard support (bidirectional)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make codespell not want to break the code

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* run pr comment in separate task

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start endpoint and property mapping stuff

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more endpoint things

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* unrelated: fix event model_pk filtering with ints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* unrelated: improve event display for changelog

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rebuild endpoint stuff again

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* idk special url

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* more stuff, connect token with session

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add disconnect

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework disconnect

cleanly disconnect from guacd instead of just letting the connection timeout

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* clear cache when creating outpost

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* support host:port and fix protocol

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* center smaller viewport

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rework connection to wait more and stop after some time

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add policy control to endpoints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove provider protocol

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* don't switch to different outpost connection when already chosen

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start using property mappings, add static settings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add some RAC mapping settings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix lint

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* start adding tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests for event changes

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests and fix issues found by said tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add preview banner, move endpoints to main page

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add locale

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* auto-select endpoint if only one is available

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* backport https://github.com/goauthentik/authentik/pull/7831 to rac

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* dont select property mappings on endpoints

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make table modal only load when opened

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* only auto-redirect when open

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix web deps

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* check for token expiry and terminate session

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* re-add endpoint name to title

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* disconnect connection when token is manually deleted

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add initial RAC docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add connection expiry setting to provider

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix flaky tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-12-30 21:33:14 +01:00
committed by GitHub
parent a365ec81f3
commit 240cf6dd94
95 changed files with 6398 additions and 144 deletions

View File

@ -21,6 +21,7 @@ import {
OutpostsServiceConnectionsAllListRequest,
PaginatedLDAPProviderList,
PaginatedProxyProviderList,
PaginatedRACProviderList,
PaginatedRadiusProviderList,
ProvidersApi,
ServiceConnection,
@ -38,7 +39,8 @@ export class OutpostForm extends ModelForm<Outpost, string> {
providers?:
| PaginatedProxyProviderList
| PaginatedLDAPProviderList
| PaginatedRadiusProviderList;
| PaginatedRadiusProviderList
| PaginatedRACProviderList;
defaultConfig?: OutpostDefaultConfig;
@ -73,6 +75,12 @@ export class OutpostForm extends ModelForm<Outpost, string> {
applicationIsnull: false,
});
break;
case OutpostTypeEnum.Rac:
this.providers = await new ProvidersApi(DEFAULT_CONFIG).providersRacList({
ordering: "name",
applicationIsnull: false,
});
break;
case OutpostTypeEnum.UnknownDefaultOpenApi:
this.providers = undefined;
}
@ -133,6 +141,12 @@ export class OutpostForm extends ModelForm<Outpost, string> {
>
${msg("Radius")}
</option>
<option
value=${OutpostTypeEnum.Rac}
?selected=${this.instance?.type === OutpostTypeEnum.Rac}
>
${msg("RAC")}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Integration")} name="serviceConnection">

View File

@ -41,6 +41,8 @@ export function TypeToLabel(type?: OutpostTypeEnum): string {
return msg("LDAP");
case OutpostTypeEnum.Radius:
return msg("Radius");
case OutpostTypeEnum.Rac:
return msg("RAC");
case OutpostTypeEnum.UnknownDefaultOpenApi:
return msg("Unknown type");
}

View File

@ -1,5 +1,6 @@
import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm";
import "@goauthentik/admin/property-mappings/PropertyMappingNotification";
import "@goauthentik/admin/property-mappings/PropertyMappingRACForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSCIMForm";
import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm";

View File

@ -0,0 +1,195 @@
import { first } from "@goauthentik/app/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { docLink } from "@goauthentik/common/global";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PropertymappingsApi, RACPropertyMapping } from "@goauthentik/api";
@customElement("ak-property-mapping-rac-form")
export class PropertyMappingLDAPForm extends ModelForm<RACPropertyMapping, string> {
loadInstance(pk: string): Promise<RACPropertyMapping> {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacRetrieve({
pmUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return msg("Successfully updated mapping.");
} else {
return msg("Successfully created mapping.");
}
}
async send(data: RACPropertyMapping): Promise<RACPropertyMapping> {
if (this.instance) {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacUpdate({
pmUuid: this.instance.pk || "",
rACPropertyMappingRequest: data,
});
} else {
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacCreate({
rACPropertyMappingRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("General settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Username")}
?required=${true}
name="staticSettings.username"
>
<input
type="text"
value="${ifDefined(this.instance?.staticSettings.username)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Password")}
?required=${true}
name="staticSettings.password"
>
<input
type="password"
value="${ifDefined(this.instance?.staticSettings.password)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("RDP settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="staticSettings.ignore-cert">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(
this.instance?.staticSettings["ignore-cert"],
false,
)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Ignore server certificate")}</span
>
</label>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="staticSettings.enable-wallpaper">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(
this.instance?.staticSettings["enable-wallpaper"],
false,
)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Enable wallpaper")}</span>
</label>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="staticSettings.enable-font-smoothing">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(
this.instance?.staticSettings["enable-font-smoothing"],
false,
)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Enable font-smoothing")}</span>
</label>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="staticSettings.enable-full-window-drag">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(
this.instance?.staticSettings["enable-full-window-drag"],
false,
)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Enable full window dragging")}</span
>
</label>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Expression")}
?required=${true}
name="expression"
>
<ak-codemirror
mode=${CodeMirrorMode.Python}
value="${ifDefined(this.instance?.expression)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${msg("Expression using Python.")}
<a
target="_blank"
href="${docLink(
"/docs/property-mappings/expression?utm_source=authentik",
)}"
>
${msg("See documentation for a list of all variables.")}
</a>
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
`;
}
}

View File

@ -1,5 +1,6 @@
import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm";
import "@goauthentik/admin/property-mappings/PropertyMappingNotification";
import "@goauthentik/admin/property-mappings/PropertyMappingRACForm";
import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm";
import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm";
import "@goauthentik/admin/property-mappings/PropertyMappingTestForm";

View File

@ -3,6 +3,7 @@ import "@goauthentik/admin/providers/ProviderWizard";
import "@goauthentik/admin/providers/ldap/LDAPProviderForm";
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
import "@goauthentik/admin/providers/rac/RACProviderForm";
import "@goauthentik/admin/providers/radius/RadiusProviderForm";
import "@goauthentik/admin/providers/saml/SAMLProviderForm";
import "@goauthentik/admin/providers/scim/SCIMProviderForm";

View File

@ -1,6 +1,7 @@
import "@goauthentik/admin/providers/ldap/LDAPProviderViewPage";
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderViewPage";
import "@goauthentik/admin/providers/proxy/ProxyProviderViewPage";
import "@goauthentik/admin/providers/rac/RACProviderViewPage";
import "@goauthentik/admin/providers/radius/RadiusProviderViewPage";
import "@goauthentik/admin/providers/saml/SAMLProviderViewPage";
import "@goauthentik/admin/providers/scim/SCIMProviderViewPage";
@ -65,6 +66,10 @@ export class ProviderViewPage extends AKElement {
return html`<ak-provider-radius-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-radius-view>`;
case "ak-provider-rac-form":
return html`<ak-provider-rac-view
providerID=${ifDefined(this.provider.pk)}
></ak-provider-rac-view>`;
default:
return html`<p>Invalid provider type ${this.provider?.component}</p>`;
}

View File

@ -0,0 +1,146 @@
import { first } from "@goauthentik/app/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/components/ak-radio-input";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import YAML from "yaml";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
AuthModeEnum,
Endpoint,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
ProtocolEnum,
RacApi,
} from "@goauthentik/api";
@customElement("ak-rac-endpoint-form")
export class EndpointForm extends ModelForm<Endpoint, string> {
@property({ type: Number })
providerID?: number;
propertyMappings?: PaginatedRACPropertyMappingList;
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsRacList({
ordering: "name",
});
}
loadInstance(pk: string): Promise<Endpoint> {
return new RacApi(DEFAULT_CONFIG).racEndpointsRetrieve({
pbmUuid: pk,
});
}
getSuccessMessage(): string {
return this.instance
? msg("Successfully updated endpoint.")
: msg("Successfully created endpoint.");
}
async send(data: Endpoint): Promise<Endpoint> {
data.authMode = AuthModeEnum.Prompt;
if (!this.instance) {
data.provider = this.providerID || 0;
} else {
data.provider = this.instance.provider;
}
if (this.instance) {
return new RacApi(DEFAULT_CONFIG).racEndpointsPartialUpdate({
pbmUuid: this.instance.pk || "",
patchedEndpointRequest: data,
});
} else {
return new RacApi(DEFAULT_CONFIG).racEndpointsCreate({
endpointRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`
<ak-form-element-horizontal label=${msg("Name")} name="name" ?required=${true}>
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Protocol")} ?required=${true} name="protocol">
<ak-radio
.options=${[
{
label: msg("RDP"),
value: ProtocolEnum.Rdp,
},
{
label: msg("SSH"),
value: ProtocolEnum.Ssh,
},
{
label: msg("VNC"),
value: ProtocolEnum.Vnc,
},
]}
.value=${this.instance?.protocol}
>
</ak-radio>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Host")} name="host" ?required=${true}>
<input
type="text"
value="${ifDefined(this.instance?.host)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">${msg("Hostname/IP to connect to.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Property mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (this.instance?.propertyMappings) {
selected = Array.from(this.instance?.propertyMappings).some((su) => {
return su == mapping.pk;
});
}
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(first(this.instance?.settings, {}))}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${msg("Connection settings.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
`;
}
}

View File

@ -0,0 +1,142 @@
import "@goauthentik/admin/policies/BoundPoliciesList";
import "@goauthentik/app/admin/providers/rac/EndpointForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import "@goauthentik/elements/rbac/ObjectPermissionModal";
import { PaginatedResponse, Table } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import {
Endpoint,
RACProvider,
RacApi,
RbacPermissionsAssignedByUsersListModelEnum,
} from "@goauthentik/api";
@customElement("ak-rac-endpoint-list")
export class EndpointListPage extends Table<Endpoint> {
expandable = true;
checkbox = true;
searchEnabled(): boolean {
return true;
}
@property()
order = "name";
@property({ attribute: false })
provider?: RACProvider;
static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList);
}
async apiEndpoint(page: number): Promise<PaginatedResponse<Endpoint>> {
return new RacApi(DEFAULT_CONFIG).racEndpointsList({
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "",
provider: this.provider?.pk,
superuserFullList: true,
});
}
columns(): TableColumn[] {
return [
new TableColumn(msg("Name"), "name"),
new TableColumn(msg("Host"), "host"),
new TableColumn(msg("Actions")),
];
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Endpoint(s)")}
.objects=${this.selectedElements}
.metadata=${(item: Endpoint) => {
return [
{ key: msg("Name"), value: item.name },
{ key: msg("Host"), value: item.host },
];
}}
.usedBy=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racEndpointsUsedByList({
pbmUuid: item.pk,
});
}}
.delete=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racEndpointsDestroy({
pbmUuid: item.pk,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${msg("Delete")}
</button>
</ak-forms-delete-bulk>`;
}
row(item: Endpoint): TemplateResult[] {
return [
html`${item.name}`,
html`${item.host}`,
html`<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update Endpoint")} </span>
<ak-rac-endpoint-form slot="form" .instancePk=${item.pk}>
</ak-rac-endpoint-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit"></i>
</pf-tooltip>
</button>
</ak-forms-modal>
<ak-rbac-object-permission-modal
model=${RbacPermissionsAssignedByUsersListModelEnum.ProvidersRacEndpoint}
objectPk=${item.pk}
>
</ak-rbac-object-permission-modal>`,
];
}
renderExpanded(item: Endpoint): TemplateResult {
return html` <td></td>
<td role="cell" colspan="4">
<div class="pf-c-table__expandable-row-content">
<div class="pf-c-content">
<p>
${msg(
"These bindings control which users will have access to this endpoint. Users must also have access to the application.",
)}
</p>
<ak-bound-policies-list .target=${item.pk}> </ak-bound-policies-list>
</div>
</div>
</td>`;
}
renderObjectCreate(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Endpoint")} </span>
<ak-rac-endpoint-form slot="form" .providerID=${this.provider?.pk}>
</ak-rac-endpoint-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${msg("Create")}</button>
</ak-forms-modal>
`;
}
}

View File

@ -0,0 +1,158 @@
import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-tenanted-flow-search";
import { first } from "@goauthentik/app/common/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import YAML from "yaml";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
FlowsInstancesListDesignationEnum,
PaginatedEndpointList,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
ProvidersApi,
RACProvider,
RacApi,
} from "@goauthentik/api";
@customElement("ak-provider-rac-form")
export class RACProviderFormPage extends ModelForm<RACProvider, number> {
@state()
endpoints?: PaginatedEndpointList;
propertyMappings?: PaginatedRACPropertyMappingList;
async load(): Promise<void> {
this.endpoints = await new RacApi(DEFAULT_CONFIG).racEndpointsList({});
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsRacList({
ordering: "name",
});
}
async loadInstance(pk: number): Promise<RACProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersRacRetrieve({
id: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return msg("Successfully updated provider.");
} else {
return msg("Successfully created provider.");
}
}
async send(data: RACProvider): Promise<RACProvider> {
if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersRacUpdate({
id: this.instance.pk || 0,
rACProviderRequest: data,
});
} else {
return new ProvidersApi(DEFAULT_CONFIG).providersRacCreate({
rACProviderRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="authorizationFlow"
label=${msg("Authorization flow")}
?required=${true}
>
<ak-flow-search
flowType=${FlowsInstancesListDesignationEnum.Authorization}
.currentFlow=${this.instance?.authorizationFlow}
required
></ak-flow-search>
<p class="pf-c-form__helper-text">
${msg("Flow used when authorizing this provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Connection expiry")}
?required=${true}
name="connectionExpiry"
>
<input
type="text"
value="${first(this.instance?.connectionExpiry, "hours=8")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"Determines how long a session lasts before being disconnected and requiring re-authorization.",
)}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${msg("Protocol settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Property mappings")}
?required=${true}
name="propertyMappings"
>
<select class="pf-c-form-control" multiple>
${this.propertyMappings?.results.map((mapping) => {
let selected = false;
if (this.instance?.propertyMappings) {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Settings")} name="settings">
<ak-codemirror
mode="yaml"
value="${YAML.stringify(first(this.instance?.settings, {}))}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">${msg("Connection settings.")}</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
`;
}
}

View File

@ -0,0 +1,181 @@
import "@goauthentik/admin/providers/RelatedApplicationButton";
import "@goauthentik/admin/providers/rac/EndpointForm";
import "@goauthentik/admin/providers/rac/EndpointList";
import "@goauthentik/admin/providers/rac/RACProviderForm";
import "@goauthentik/app/elements/rbac/ObjectPermissionsPage";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/components/events/ObjectChangelog";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFList from "@patternfly/patternfly/components/List/list.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
ProvidersApi,
RACProvider,
RbacPermissionsAssignedByUsersListModelEnum,
} from "@goauthentik/api";
@customElement("ak-provider-rac-view")
export class RACProviderViewPage extends AKElement {
@property()
set args(value: { [key: string]: number }) {
this.providerID = value.id;
}
@property({ type: Number })
set providerID(value: number) {
new ProvidersApi(DEFAULT_CONFIG)
.providersRacRetrieve({
id: value,
})
.then((prov) => (this.provider = prov));
}
@property({ attribute: false })
provider?: RACProvider;
static get styles(): CSSResult[] {
return [
PFBase,
PFButton,
PFPage,
PFGrid,
PFContent,
PFList,
PFForm,
PFFormControl,
PFCard,
PFDescriptionList,
PFBanner,
];
}
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
if (!this.provider?.pk) return;
this.providerID = this.provider?.pk;
});
}
render(): TemplateResult {
if (!this.provider) {
return html``;
}
return html`<ak-tabs>
<section slot="page-overview" data-tab-title="${msg("Overview")}">
${this.renderTabOverview()}
</section>
<section
slot="page-changelog"
data-tab-title="${msg("Changelog")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-object-changelog
targetModelPk=${this.provider?.pk || ""}
targetModelName=${this.provider?.metaModelName || ""}
>
</ak-object-changelog>
</div>
</div>
</section>
<ak-rbac-object-permission-page
slot="page-permissions"
data-tab-title="${msg("Permissions")}"
model=${RbacPermissionsAssignedByUsersListModelEnum.ProvidersRacRacprovider}
objectPk=${this.provider.pk}
></ak-rbac-object-permission-page>
</ak-tabs>`;
}
renderTabOverview(): TemplateResult {
if (!this.provider) {
return html``;
}
return html` <div slot="header" class="pf-c-banner pf-m-info">
${msg("RAC is in preview.")}
<a href="mailto:hello+feature/rac@goauthentik.io">${msg("Send us feedback!")}</a>
</div>
${this.provider?.assignedApplicationName
? html``
: html`<div slot="header" class="pf-c-banner pf-m-warning">
${msg("Warning: Provider is not used by an Application.")}
</div>`}
${this.provider?.outpostSet.length < 1
? html`<div slot="header" class="pf-c-banner pf-m-warning">
${msg("Warning: Provider is not used by any Outpost.")}
</div>`
: html``}
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
<div class="pf-c-card__body">
<dl class="pf-c-description-list pf-m-3-col-on-lg">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${msg("Name")}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${this.provider.name}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${msg("Assigned to application")}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
.provider=${this.provider}
></ak-provider-related-application>
</div>
</dd>
</div>
</dl>
</div>
<div class="pf-c-card__footer">
<ak-forms-modal>
<span slot="submit"> ${msg("Update")} </span>
<span slot="header"> ${msg("Update RAC Provider")} </span>
<ak-provider-rac-form slot="form" .instancePk=${this.provider.pk || 0}>
</ak-provider-rac-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${msg("Edit")}
</button>
</ak-forms-modal>
</div>
</div>
<div class="pf-c-card pf-l-grid__item pf-m-12-col">
<div class="pf-c-card__title">${msg("Endpoints")}</div>
<div class="pf-c-card__body">
<ak-rac-endpoint-list .provider=${this.provider}> </ak-rac-endpoint-list>
</div>
</div>
</div>`;
}
}