From dc16a8a4c9e6a5d2dd8faa83e8cf0772a2d7e0f1 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 00:45:53 +0100 Subject: [PATCH 1/9] providers/proxy: set proxy-size for nginx for larger response --- authentik/outposts/controllers/docker.py | 1 + authentik/providers/proxy/controllers/k8s/ingress.py | 4 +++- authentik/providers/proxy/forms.py | 2 +- .../{to_0_13_events..py => to_0_14_events..py} | 0 4 files changed, 5 insertions(+), 2 deletions(-) rename lifecycle/system_migrations/{to_0_13_events..py => to_0_14_events..py} (100%) diff --git a/authentik/outposts/controllers/docker.py b/authentik/outposts/controllers/docker.py index e12d7872c..f8c625f41 100644 --- a/authentik/outposts/controllers/docker.py +++ b/authentik/outposts/controllers/docker.py @@ -154,6 +154,7 @@ class DockerController(BaseController): ), "AUTHENTIK_TOKEN": self.outpost.token.key, }, + "labels": self._get_labels(), } }, } diff --git a/authentik/providers/proxy/controllers/k8s/ingress.py b/authentik/providers/proxy/controllers/k8s/ingress.py index ea4de5acc..1b02a54bc 100644 --- a/authentik/providers/proxy/controllers/k8s/ingress.py +++ b/authentik/providers/proxy/controllers/k8s/ingress.py @@ -74,11 +74,13 @@ class IngressReconciler(KubernetesObjectReconciler[NetworkingV1beta1Ingress]): # goes to the same pod "nginx.ingress.kubernetes.io/affinity": "cookie", "traefik.ingress.kubernetes.io/affinity": "true", + "nginx.ingress.kubernetes.io/proxy-buffers-number": "4", + "nginx.ingress.kubernetes.io/proxy-buffer-size": "16k", } annotations.update( self.controller.outpost.config.kubernetes_ingress_annotations ) - return dict() + return annotations def get_reference_object(self) -> NetworkingV1beta1Ingress: """Get deployment object for outpost""" diff --git a/authentik/providers/proxy/forms.py b/authentik/providers/proxy/forms.py index a83715105..2c3277253 100644 --- a/authentik/providers/proxy/forms.py +++ b/authentik/providers/proxy/forms.py @@ -7,7 +7,7 @@ from authentik.providers.proxy.models import ProxyProvider class ProxyProviderForm(forms.ModelForm): - """Security Gateway Provider form""" + """Proxy Provider form""" instance: ProxyProvider diff --git a/lifecycle/system_migrations/to_0_13_events..py b/lifecycle/system_migrations/to_0_14_events..py similarity index 100% rename from lifecycle/system_migrations/to_0_13_events..py rename to lifecycle/system_migrations/to_0_14_events..py From 0e1587bc1abbf4a22487ce445da364e21af32464 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 01:06:36 +0100 Subject: [PATCH 2/9] providers/oauth2: don't write authorization code to event log --- authentik/providers/oauth2/models.py | 4 ++-- swagger.yaml | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/authentik/providers/oauth2/models.py b/authentik/providers/oauth2/models.py index 6dd5ab3c9..dc0511e19 100644 --- a/authentik/providers/oauth2/models.py +++ b/authentik/providers/oauth2/models.py @@ -398,7 +398,7 @@ class AuthorizationCode(ExpiringModel, BaseGrantModel): verbose_name_plural = _("Authorization Codes") def __str__(self): - return "{0} - {1}".format(self.provider, self.code) + return f"Authorization code for {self.provider} for user {self.user}" @dataclass @@ -461,7 +461,7 @@ class RefreshToken(ExpiringModel, BaseGrantModel): self._id_token = json.dumps(asdict(value)) def __str__(self): - return f"{self.provider} - {self.access_token}" + return f"Refresh Token for {self.provider} for user {self.access_token.user}" @property def at_hash(self): diff --git a/swagger.yaml b/swagger.yaml index 6aca8b15b..5da72c14d 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -8133,6 +8133,14 @@ definitions: type: string format: uuid x-nullable: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true __type__: title: 'type ' type: string @@ -8181,6 +8189,14 @@ definitions: type: string format: uuid x-nullable: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true server_uri: title: Server URI type: string @@ -8296,6 +8312,14 @@ definitions: type: string format: uuid x-nullable: true + verbose_name: + title: Verbose name + type: string + readOnly: true + verbose_name_plural: + title: Verbose name plural + type: string + readOnly: true provider_type: title: Provider type type: string From 5db38bd0b79a10bed13221b16d4fbc3a8c64e407 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 13:07:20 +0100 Subject: [PATCH 3/9] web: lazy-render expanded table --- web/src/elements/table/Table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index b9e38984e..0cf75ed82 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -178,7 +178,7 @@ export abstract class Table extends LitElement { - ${this.renderExpanded(item)} + ${this.expandedRows[idx] ? this.renderExpanded(item) : html``} `; }); From 119adb3e7b318dcd8330230fbb03b99168a66a6e Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 13:07:35 +0100 Subject: [PATCH 4/9] web: fix old default URL --- web/src/index.html | 2 +- web/src/interfaces/Interface.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/index.html b/web/src/index.html index 7b2589540..f17fe6b45 100644 --- a/web/src/index.html +++ b/web/src/index.html @@ -36,7 +36,7 @@ class="pf-c-page__main" tabindex="-1" id="main-content" - defaultUrl="/library/" + defaultUrl="/library" > diff --git a/web/src/interfaces/Interface.ts b/web/src/interfaces/Interface.ts index 5a65e103f..80ffd5189 100644 --- a/web/src/interfaces/Interface.ts +++ b/web/src/interfaces/Interface.ts @@ -36,7 +36,7 @@ export abstract class Interface extends LitElement {
- +
`; From 5f9c1e229ca69358e3c3bd04168956e9030bc178 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 13:07:49 +0100 Subject: [PATCH 5/9] root: return API dates as timestamp --- authentik/root/settings.py | 1 + web/src/utils.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 3975f7d8e..3df86786d 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -142,6 +142,7 @@ SWAGGER_SETTINGS = { REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination", "PAGE_SIZE": 100, + 'DATETIME_FORMAT': '%s', "DEFAULT_FILTER_BACKENDS": [ "rest_framework_guardian.filters.ObjectPermissionsFilter", "django_filters.rest_framework.DjangoFilterBackend", diff --git a/web/src/utils.ts b/web/src/utils.ts index 9398fffda..36d6c300b 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -50,3 +50,7 @@ export function loading(v: T, actual: TemplateResult): TemplateResult { } return actual; } + +export function time(t: string): Date { + return new Date(parseInt(t, 10) * 1000); +} From 77861b52e3d99af4ae91b8d915fab6d2680a54db Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Mon, 28 Dec 2020 13:54:56 +0100 Subject: [PATCH 6/9] web: fix search loading old results when using enter --- web/src/elements/table/Table.ts | 2 +- web/src/elements/table/TableSearch.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/elements/table/Table.ts b/web/src/elements/table/Table.ts index 0cf75ed82..379073692 100644 --- a/web/src/elements/table/Table.ts +++ b/web/src/elements/table/Table.ts @@ -144,7 +144,7 @@ export abstract class Table extends LitElement {
- ${inner ? inner : html``} + ${inner ? inner : html``}
diff --git a/web/src/elements/table/TableSearch.ts b/web/src/elements/table/TableSearch.ts index 1cc676c7b..4d6d1ab13 100644 --- a/web/src/elements/table/TableSearch.ts +++ b/web/src/elements/table/TableSearch.ts @@ -26,9 +26,9 @@ export class TableSearch extends LitElement { if (el.value === "") return; this.onSearch(el?.value); }}> - { + { if (!this.onSearch) return; - this.onSearch(""); + this.onSearch((ev.target as HTMLInputElement).value); }}> - {% include 'partials/pagination.html' %} - - - - - - - - - - - - - - {% for entry in object_list %} - - - - - - - - {% endfor %} - -
{% trans 'Action' %}{% trans 'Context' %}{% trans 'User' %}{% trans 'Creation Date' %}{% trans 'Client IP' %}
-
-
{{ entry.action }}
- {{ entry.app|default:'-' }} -
-
-
-
- {{ entry.context }} -
- {% if entry.user.on_behalf_of %} - - {% blocktrans with username=entry.user.on_behalf_of.username %} - On behalf of {{ username }} - {% endblocktrans %} - - {% endif %} -
-
-
-
{{ entry.user.username }}
- - {% blocktrans with pk=entry.user.pk %} - ID: {{ pk }} - {% endblocktrans %} - -
-
- - {{ entry.created }} - - - - {{ entry.client_ip }} - -
-
- {% include 'partials/pagination.html' %} -
- - - -{% endblock %} diff --git a/authentik/events/urls.py b/authentik/events/urls.py deleted file mode 100644 index ff208e2b8..000000000 --- a/authentik/events/urls.py +++ /dev/null @@ -1,9 +0,0 @@ -"""authentik events urls""" -from django.urls import path - -from authentik.events.views import EventListView - -urlpatterns = [ - # Event Log - path("log/", EventListView.as_view(), name="log"), -] diff --git a/authentik/events/views.py b/authentik/events/views.py deleted file mode 100644 index 0bbb83f2b..000000000 --- a/authentik/events/views.py +++ /dev/null @@ -1,30 +0,0 @@ -"""authentik Event administration""" -from django.contrib.auth.mixins import LoginRequiredMixin -from django.views.generic import ListView -from guardian.mixins import PermissionListMixin - -from authentik.admin.views.utils import SearchListMixin, UserPaginateListMixin -from authentik.events.models import Event - - -class EventListView( - PermissionListMixin, - LoginRequiredMixin, - SearchListMixin, - UserPaginateListMixin, - ListView, -): - """Show list of all invitations""" - - model = Event - template_name = "events/list.html" - permission_required = "authentik_events.view_event" - ordering = "-created" - - search_fields = [ - "user", - "action", - "app", - "context", - "client_ip", - ] diff --git a/authentik/flows/api.py b/authentik/flows/api.py index 2ac784c7d..4389f8fe9 100644 --- a/authentik/flows/api.py +++ b/authentik/flows/api.py @@ -78,6 +78,8 @@ class FlowViewSet(ModelViewSet): queryset = Flow.objects.all() serializer_class = FlowSerializer lookup_field = "slug" + search_fields = ["name", "slug", "designation", "title"] + filterset_fields = ["flow_uuid", "name", "slug", "designation"] @swagger_auto_schema(responses={200: FlowDiagramSerializer()}) @action(detail=True, methods=["get"]) diff --git a/authentik/root/settings.py b/authentik/root/settings.py index 3df86786d..d68852758 100644 --- a/authentik/root/settings.py +++ b/authentik/root/settings.py @@ -142,7 +142,7 @@ SWAGGER_SETTINGS = { REST_FRAMEWORK = { "DEFAULT_PAGINATION_CLASS": "authentik.api.pagination.Pagination", "PAGE_SIZE": 100, - 'DATETIME_FORMAT': '%s', + "DATETIME_FORMAT": "%s", "DEFAULT_FILTER_BACKENDS": [ "rest_framework_guardian.filters.ObjectPermissionsFilter", "django_filters.rest_framework.DjangoFilterBackend", diff --git a/swagger.yaml b/swagger.yaml index 5da72c14d..a577d2f9a 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -868,6 +868,11 @@ paths: operationId: events_events_list description: Event Read-Only Viewset parameters: + - name: action + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -919,6 +924,11 @@ paths: operationId: events_events_top_per_user description: Get the top_n events grouped by user count parameters: + - name: action + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. @@ -1194,6 +1204,26 @@ paths: operationId: flows_instances_list description: Flow Viewset parameters: + - name: flow_uuid + in: query + description: '' + required: false + type: string + - name: name + in: query + description: '' + required: false + type: string + - name: slug + in: query + description: '' + required: false + type: string + - name: designation + in: query + description: '' + required: false + type: string - name: ordering in: query description: Which field to use when ordering the results. diff --git a/web/src/api/Events.ts b/web/src/api/Events.ts index 408e38dec..b0b08fd65 100644 --- a/web/src/api/Events.ts +++ b/web/src/api/Events.ts @@ -1,6 +1,37 @@ -import { DefaultClient } from "./Client"; +import { DefaultClient, PBResponse, QueryArguments } from "./Client"; + +export interface EventUser { + pk: number; + email?: string; + username: string; + on_behalf_of?: EventUser; +} + +export interface EventContext { + [key: string]: EventContext | string | number; +} export class Event { + pk: string; + user: EventUser; + action: string; + app: string; + context: EventContext; + client_ip: string; + created: string; + + constructor() { + throw Error(); + } + + static get(pk: string): Promise { + return DefaultClient.fetch(["events", "events", pk]); + } + + static list(filter?: QueryArguments): Promise> { + return DefaultClient.fetch>(["events", "events"], filter); + } + // events/events/top_per_user/?filter_action=authorize_application static topForUser(action: string): Promise { return DefaultClient.fetch(["events", "events", "top_per_user"], { diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts index 463c3c00b..f4d7cedf3 100644 --- a/web/src/interfaces/AdminInterface.ts +++ b/web/src/interfaces/AdminInterface.ts @@ -9,7 +9,7 @@ export const SIDEBAR_ITEMS: SidebarItem[] = [ new SidebarItem("Monitor").children( new SidebarItem("Overview", "/administration/overview"), new SidebarItem("System Tasks", "/administration/tasks/"), - new SidebarItem("Events", "/events/log/"), + new SidebarItem("Events", "/events"), ).when((): Promise => { return User.me().then(u => u.is_superuser); }), diff --git a/web/src/pages/events/EventInfo.ts b/web/src/pages/events/EventInfo.ts new file mode 100644 index 000000000..78d555028 --- /dev/null +++ b/web/src/pages/events/EventInfo.ts @@ -0,0 +1,116 @@ +import { gettext } from "django"; +import { css, CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element"; +import { until } from "lit-html/directives/until"; +import { Event, EventContext } from "../../api/Events"; +import { Flow } from "../../api/Flows"; +import { COMMON_STYLES } from "../../common/styles"; +import "../../elements/Spinner"; +import { SpinnerSize } from "../../elements/Spinner"; + +@customElement("ak-event-info") +export class EventInfo extends LitElement { + + @property({attribute: false}) + event?: Event; + + static get styles(): CSSResult[] { + return COMMON_STYLES.concat( + css` + code { + display: block; + white-space: pre-wrap; + } + ` + ); + } + + getModelInfo(context: EventContext): TemplateResult { + return html`
    +
  • ${gettext("UID")}: ${context.pk as string}
  • +
  • ${gettext("Name")}: ${context.name as string}
  • +
  • ${gettext("App")}: ${context.app as string}
  • +
  • ${gettext("Model Name")}: ${context.model_name as string}
  • +
`; + } + + defaultResponse(): TemplateResult { + return html`
+
+

${gettext("Context")}

+ ${JSON.stringify(this.event?.context)} +
+
+

${gettext("User")}

+ ${JSON.stringify(this.event?.user)} +
+
`; + } + + render(): TemplateResult { + if (!this.event) { + return html``; + } + switch (this.event?.action) { + case "model_created": + case "model_updated": + case "model_deleted": + return html` +

${gettext("Affected model:")}


+ ${this.getModelInfo(this.event.context.model as EventContext)} + `; + case "authorize_application": + return html`
+
+

${gettext("Authorized application:")}


+ ${this.getModelInfo(this.event.context.authorized_application as EventContext)} +
+
+

${gettext("Using flow")}

+ ${until(Flow.list({ + flow_uuid: this.event.context.flow as string, + }).then(resp => { + return html`${resp.results[0].name}`; + }), html``)} +
+
`; + case "login_failed": + return html` +

${gettext(`Attempted to log in as ${this.event.context.username}`)}

+ `; + case "token_view": + return html` +

${gettext("Token:")}


+ ${this.getModelInfo(this.event.context.token as EventContext)} + `; + case "property_mapping_exception": + case "policy_exception": + return html`
+
+

${gettext("Exception")}

+ ${this.event.context.error} +
+
+

${gettext("Expression")}

+ ${this.event.context.expression} +
+
`; + case "configuration_error": + return html`

${this.event.context.message}

`; + case "update_available": + return html`

${gettext("New version available!")}

+ ${this.event.context.new_version} + `; + // Action types which typically don't record any extra context. + // If context is not empty, we fall to the default response. + case "login": + case "logout": + if (this.event.context === {}) { + return html`${gettext("No additional data available.")}`; + } + return this.defaultResponse(); + default: + return this.defaultResponse(); + } + } + +} diff --git a/web/src/pages/events/EventListPage.ts b/web/src/pages/events/EventListPage.ts new file mode 100644 index 000000000..c2da56c51 --- /dev/null +++ b/web/src/pages/events/EventListPage.ts @@ -0,0 +1,71 @@ +import { gettext } from "django"; +import { customElement, html, property, TemplateResult } from "lit-element"; +import { PBResponse } from "../../api/Client"; +import { Event } from "../../api/Events"; +import { TableColumn } from "../../elements/table/Table"; +import { TablePage } from "../../elements/table/TablePage"; +import { time } from "../../utils"; +import "./EventInfo"; + +@customElement("ak-event-list") +export class EventListPage extends TablePage { + expandable = true; + + pageTitle(): string { + return "Event Log"; + } + pageDescription(): string | undefined { + return; + } + pageIcon(): string { + return "pf-icon pf-icon-catalog"; + } + searchEnabled(): boolean { + return true; + } + + @property() + order = "-created"; + + apiEndpoint(page: number): Promise> { + return Event.list({ + ordering: this.order, + page: page, + search: this.search || "", + }); + } + + columns(): TableColumn[] { + return [ + new TableColumn("Action", "action"), + new TableColumn("User", "user"), + new TableColumn("Creation Date", "created"), + new TableColumn("Client IP", "client_ip"), + ]; + } + row(item: Event): TemplateResult[] { + return [ + html`
${item.action}
+ ${item.app}`, + html`
${item.user.username}
+ ${item.user.on_behalf_of ? html` + ${gettext(`On behalf of ${item.user.on_behalf_of.username}`)} + ` : html``}`, + html`${time(item.created).toLocaleString()}`, + html`${item.client_ip}`, + ]; + } + + renderExpanded(item: Event): TemplateResult { + return html` + +
+ +
+ + + + `; + } + +} diff --git a/web/src/routes.ts b/web/src/routes.ts index 53e36b051..7c8134c93 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -7,6 +7,7 @@ import "./pages/applications/ApplicationListPage"; import "./pages/applications/ApplicationViewPage"; import "./pages/sources/SourceViewPage"; import "./pages/flows/FlowViewPage"; +import "./pages/events/EventListPage"; export const ROUTES: Route[] = [ // Prevent infinite Shell loops @@ -24,4 +25,5 @@ export const ROUTES: Route[] = [ new Route(new RegExp(`^/flows/(?${SLUG_REGEX})$`)).then((args) => { return html``; }), + new Route(new RegExp("^/events$"), html``), ]; From a8647caca9f2cee2d0a7b3c492c8fc5096697424 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Dec 2020 14:32:57 +0100 Subject: [PATCH 9/9] build(deps): bump @types/codemirror from 0.0.102 to 0.0.103 in /web (#433) Bumps [@types/codemirror](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/codemirror) from 0.0.102 to 0.0.103. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/codemirror) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- web/package-lock.json | 6 +++--- web/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index bb7222d7d..7311fccec 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -304,9 +304,9 @@ } }, "@types/codemirror": { - "version": "0.0.102", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.102.tgz", - "integrity": "sha512-WZZW8VL8BAzzAZWkiYnM5VsYz1qWYieRqHPPtC/BB015QXd3LPXtBlbRYA8lauKgM10qWYeLH8p5LsIn2SLXfA==", + "version": "0.0.103", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.103.tgz", + "integrity": "sha512-dYQTrIcZal0pnYz/ODjpJB+yadKJhGHywylAlHKjE8VSzGiw2A+6S+hD6jfyXw02ToFR9DO52X+O1pvHn31sbg==", "requires": { "@types/tern": "*" } diff --git a/web/package.json b/web/package.json index 6f4cda537..00ace845e 100644 --- a/web/package.json +++ b/web/package.json @@ -12,7 +12,7 @@ "@sentry/browser": "^5.29.2", "@sentry/tracing": "^5.29.2", "@types/chart.js": "^2.9.29", - "@types/codemirror": "0.0.102", + "@types/codemirror": "0.0.103", "chart.js": "^2.9.4", "codemirror": "^5.59.0", "construct-style-sheets-polyfill": "^2.4.3",