diff --git a/README.md b/README.md
index f4138083ea..fe7778212b 100644
--- a/README.md
+++ b/README.md
@@ -38,3 +38,23 @@ See [Development Documentation](https://goauthentik.io/developer-docs/?utm_sourc
## Security
See [SECURITY.md](SECURITY.md)
+
+## Sponsors
+
+This project is proudly sponsored by:
+
+
+
+
+
+
+
+DigitalOcean provides development and testing resources for authentik.
+
+
+
+
+
+
+
+Netlify hosts the [goauthentik.io](goauthentik.io) site.
diff --git a/authentik/core/models.py b/authentik/core/models.py
index 136db1c149..33508aac81 100644
--- a/authentik/core/models.py
+++ b/authentik/core/models.py
@@ -278,7 +278,13 @@ class Application(PolicyBindingModel):
"""Get casted provider instance"""
if not self.provider:
return None
- return Provider.objects.get_subclass(pk=self.provider.pk)
+ # if the Application class has been cache, self.provider is set
+ # but doing a direct query lookup will fail.
+ # In that case, just return None
+ try:
+ return Provider.objects.get_subclass(pk=self.provider.pk)
+ except Provider.DoesNotExist:
+ return None
def __str__(self):
return self.name
diff --git a/authentik/events/models.py b/authentik/events/models.py
index d08ab1e997..c104034882 100644
--- a/authentik/events/models.py
+++ b/authentik/events/models.py
@@ -2,7 +2,7 @@
import time
from collections import Counter
from datetime import timedelta
-from inspect import getmodule, stack
+from inspect import currentframe
from smtplib import SMTPException
from typing import TYPE_CHECKING, Optional, Type, Union
from uuid import uuid4
@@ -192,14 +192,15 @@ class Event(ExpiringModel):
def new(
action: Union[str, EventAction],
app: Optional[str] = None,
- _inspect_offset: int = 1,
**kwargs,
) -> "Event":
"""Create new Event instance from arguments. Instance is NOT saved."""
if not isinstance(action, EventAction):
action = EventAction.CUSTOM_PREFIX + action
if not app:
- app = getmodule(stack()[_inspect_offset][0]).__name__
+ current = currentframe()
+ parent = current.f_back
+ app = parent.f_globals["__name__"]
cleaned_kwargs = cleanse_dict(sanitize_dict(kwargs))
event = Event(action=action, app=app, context=cleaned_kwargs)
return event
diff --git a/authentik/root/settings.py b/authentik/root/settings.py
index e1bd798eae..e22134f7c2 100644
--- a/authentik/root/settings.py
+++ b/authentik/root/settings.py
@@ -28,6 +28,7 @@ from sentry_sdk.integrations.boto3 import Boto3Integration
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration
+from sentry_sdk.integrations.threading import ThreadingIntegration
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.core.middleware import structlog_add_request_id
@@ -424,6 +425,7 @@ if _ERROR_REPORTING:
CeleryIntegration(),
RedisIntegration(),
Boto3Integration(),
+ ThreadingIntegration(propagate_hub=True),
],
before_send=before_send,
release=f"authentik@{__version__}",
diff --git a/internal/outpost/flow.go b/internal/outpost/flow.go
index 830a6ed62f..f19d22b0ad 100644
--- a/internal/outpost/flow.go
+++ b/internal/outpost/flow.go
@@ -60,6 +60,7 @@ type FlowExecutor struct {
func NewFlowExecutor(ctx context.Context, flowSlug string, refConfig *api.Configuration, logFields log.Fields) *FlowExecutor {
rsp := sentry.StartSpan(ctx, "authentik.outposts.flow_executor")
+ rsp.Description = flowSlug
l := log.WithField("flow", flowSlug).WithFields(logFields)
jar, err := cookiejar.New(nil)
@@ -153,8 +154,8 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
}
ch := challenge.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got challenge")
- gcsp.SetTag("ak_challenge", string(ch.GetType()))
- gcsp.SetTag("ak_component", ch.GetComponent())
+ gcsp.SetTag("authentik.flow.challenge", string(ch.GetType()))
+ gcsp.SetTag("authentik.flow.component", ch.GetComponent())
gcsp.Finish()
FlowTimingGet.With(prometheus.Labels{
"stage": ch.GetComponent(),
@@ -202,8 +203,8 @@ func (fe *FlowExecutor) solveFlowChallenge(depth int) (bool, error) {
response, _, err := responseReq.Execute()
ch = response.GetActualInstance().(ChallengeInt)
fe.log.WithField("component", ch.GetComponent()).WithField("type", ch.GetType()).Debug("Got response")
- scsp.SetTag("ak_challenge", string(ch.GetType()))
- scsp.SetTag("ak_component", ch.GetComponent())
+ scsp.SetTag("authentik.flow.challenge", string(ch.GetType()))
+ scsp.SetTag("authentik.flow.component", ch.GetComponent())
scsp.Finish()
switch ch.GetComponent() {
diff --git a/internal/outpost/ldap/bind/request.go b/internal/outpost/ldap/bind/request.go
index 43d3792825..59827105d2 100644
--- a/internal/outpost/ldap/bind/request.go
+++ b/internal/outpost/ldap/bind/request.go
@@ -23,9 +23,18 @@ type Request struct {
func NewRequest(bindDN string, bindPW string, conn net.Conn) (*Request, *sentry.Span) {
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.bind",
sentry.TransactionName("authentik.providers.ldap.bind"))
+ span.Description = bindDN
rid := uuid.New().String()
span.SetTag("request_uid", rid)
- span.SetTag("user.username", bindDN)
+ hub := sentry.GetHubFromContext(span.Context())
+ if hub == nil {
+ hub = sentry.CurrentHub()
+ }
+ hub.Scope().SetUser(sentry.User{
+ Username: bindDN,
+ ID: bindDN,
+ IPAddress: utils.GetIP(conn.RemoteAddr()),
+ })
bindDN = strings.ToLower(bindDN)
return &Request{
diff --git a/internal/outpost/ldap/search/request.go b/internal/outpost/ldap/search/request.go
index 183dba226b..5f7e9af908 100644
--- a/internal/outpost/ldap/search/request.go
+++ b/internal/outpost/ldap/search/request.go
@@ -2,6 +2,7 @@ package search
import (
"context"
+ "fmt"
"net"
"strings"
@@ -27,10 +28,19 @@ func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Re
bindDN = strings.ToLower(bindDN)
searchReq.BaseDN = strings.ToLower(searchReq.BaseDN)
span := sentry.StartSpan(context.TODO(), "authentik.providers.ldap.search", sentry.TransactionName("authentik.providers.ldap.search"))
+ span.Description = fmt.Sprintf("%s (%s)", searchReq.BaseDN, ldap.ScopeMap[searchReq.Scope])
span.SetTag("request_uid", rid)
- span.SetTag("user.username", bindDN)
- span.SetTag("ak_filter", searchReq.Filter)
- span.SetTag("ak_base_dn", searchReq.BaseDN)
+ hub := sentry.GetHubFromContext(span.Context())
+ if hub == nil {
+ hub = sentry.CurrentHub()
+ }
+ hub.Scope().SetUser(sentry.User{
+ Username: bindDN,
+ ID: bindDN,
+ IPAddress: utils.GetIP(conn.RemoteAddr()),
+ })
+ span.SetTag("ldap_filter", searchReq.Filter)
+ span.SetTag("ldap_base_dn", searchReq.BaseDN)
return &Request{
SearchRequest: searchReq,
BindDN: bindDN,
diff --git a/internal/outpost/proxyv2/application/application.go b/internal/outpost/proxyv2/application/application.go
index b500a5773b..f6d48fcc8b 100644
--- a/internal/outpost/proxyv2/application/application.go
+++ b/internal/outpost/proxyv2/application/application.go
@@ -12,6 +12,8 @@ import (
"time"
"github.com/coreos/go-oidc"
+ "github.com/getsentry/sentry-go"
+ sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/pkg/errors"
@@ -109,6 +111,15 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
user := ""
if c != nil {
user = c.PreferredUsername
+ hub := sentry.GetHubFromContext(r.Context())
+ if hub == nil {
+ hub = sentry.CurrentHub()
+ }
+ hub.Scope().SetUser(sentry.User{
+ Username: user,
+ ID: c.Sub,
+ IPAddress: r.RemoteAddr,
+ })
}
before := time.Now()
inner.ServeHTTP(rw, r)
@@ -124,6 +135,7 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, cs *ak.CryptoStore
}).Observe(float64(after))
})
})
+ mux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
// Support /start and /sign_in for backwards compatibility
mux.HandleFunc("/akprox/start", a.handleRedirect)
diff --git a/internal/outpost/proxyv2/proxyv2.go b/internal/outpost/proxyv2/proxyv2.go
index f6cd68ff1f..dbf4818010 100644
--- a/internal/outpost/proxyv2/proxyv2.go
+++ b/internal/outpost/proxyv2/proxyv2.go
@@ -10,6 +10,7 @@ import (
"sync"
"time"
+ sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/gorilla/mux"
"github.com/pires/go-proxyproto"
log "github.com/sirupsen/logrus"
@@ -52,6 +53,7 @@ func NewProxyServer(ac *ak.APIController, portOffset int) *ProxyServer {
globalMux := rootMux.NewRoute().Subrouter()
globalMux.Use(web.NewLoggingHandler(l.WithField("logger", "authentik.outpost.proxyv2.http"), nil))
+ globalMux.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
s := &ProxyServer{
Listen: "0.0.0.0:%d",
PortOffset: portOffset,
diff --git a/internal/utils/web/middleware.go b/internal/utils/web/middleware.go
index 0c5f94f828..2b66e0d05c 100644
--- a/internal/utils/web/middleware.go
+++ b/internal/utils/web/middleware.go
@@ -99,8 +99,8 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.handler.ServeHTTP(responseLogger, req)
duration := float64(time.Since(t)) / float64(time.Millisecond)
h.afterHandler(h.logger.WithFields(log.Fields{
- "host": req.RemoteAddr,
- "vhost": GetHost(req),
+ "remote": req.RemoteAddr,
+ "host": GetHost(req),
"request_protocol": req.Proto,
"runtime": fmt.Sprintf("%0.3f", duration),
"method": req.Method,
diff --git a/internal/web/middleware_log.go b/internal/web/middleware_log.go
deleted file mode 100644
index 246bbf452f..0000000000
--- a/internal/web/middleware_log.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package web
-
-import (
- "net/http"
- "time"
-
- "github.com/getsentry/sentry-go"
- log "github.com/sirupsen/logrus"
- "goauthentik.io/internal/utils/web"
-)
-
-func loggingMiddleware(l *log.Entry) func(next http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- span := sentry.StartSpan(r.Context(), "authentik.go.request")
- before := time.Now()
- // Call the next handler, which can be another middleware in the chain, or the final handler.
- next.ServeHTTP(w, r)
- after := time.Now()
- l.WithFields(log.Fields{
- "remote": r.RemoteAddr,
- "method": r.Method,
- "took": after.Sub(before),
- "host": web.GetHost(r),
- }).Info(r.RequestURI)
- span.Finish()
- })
- }
-}
diff --git a/internal/web/middleware_sentry.go b/internal/web/middleware_sentry.go
deleted file mode 100644
index 88329e66c1..0000000000
--- a/internal/web/middleware_sentry.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package web
-
-import (
- "encoding/json"
- "net/http"
-
- sentryhttp "github.com/getsentry/sentry-go/http"
- log "github.com/sirupsen/logrus"
-)
-
-func recoveryMiddleware() func(next http.Handler) http.Handler {
- sentryHandler := sentryhttp.New(sentryhttp.Options{})
- l := log.WithField("logger", "authentik.router.sentry")
- return func(next http.Handler) http.Handler {
- sentryHandler.Handle(next)
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- next.ServeHTTP(w, r)
- defer func() {
- re := recover()
- if re == nil {
- return
- }
- err := re.(error)
- if err != nil {
- l.WithError(err).Warning("global panic handler")
- jsonBody, _ := json.Marshal(struct {
- Successful bool
- Error string
- }{
- Successful: false,
- Error: err.Error(),
- })
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusInternalServerError)
- _, err := w.Write(jsonBody)
- if err != nil {
- l.WithError(err).Warning("Failed to write sentry error body")
- }
- }
- }()
- })
- }
-}
diff --git a/internal/web/web.go b/internal/web/web.go
index 4096f91dc8..5ddd21f728 100644
--- a/internal/web/web.go
+++ b/internal/web/web.go
@@ -6,6 +6,7 @@ import (
"net"
"net/http"
+ sentryhttp "github.com/getsentry/sentry-go/http"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pires/go-proxyproto"
@@ -13,6 +14,7 @@ import (
"goauthentik.io/internal/config"
"goauthentik.io/internal/gounicorn"
"goauthentik.io/internal/outpost/proxyv2"
+ "goauthentik.io/internal/utils/web"
)
type WebServer struct {
@@ -34,13 +36,11 @@ type WebServer struct {
func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
l := log.WithField("logger", "authentik.router")
mainHandler := mux.NewRouter()
- if config.G.ErrorReporting.Enabled {
- mainHandler.Use(recoveryMiddleware())
- }
+ mainHandler.Use(sentryhttp.New(sentryhttp.Options{}).Handle)
mainHandler.Use(handlers.ProxyHeaders)
mainHandler.Use(handlers.CompressHandler)
logginRouter := mainHandler.NewRoute().Subrouter()
- logginRouter.Use(loggingMiddleware(l))
+ logginRouter.Use(web.NewLoggingHandler(l, nil))
ws := &WebServer{
LegacyProxy: true,
@@ -72,7 +72,7 @@ func (ws *WebServer) Shutdown() {
func (ws *WebServer) listenPlain() {
ln, err := net.Listen("tcp", config.G.Web.Listen)
if err != nil {
- ws.log.WithError(err).Fatalf("failed to listen")
+ ws.log.WithError(err).Fatal("failed to listen")
}
ws.log.WithField("listen", config.G.Web.Listen).Info("Listening")
@@ -83,7 +83,7 @@ func (ws *WebServer) listenPlain() {
err = http.ListenAndServe(config.G.Web.Listen, ws.m)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
- ws.log.Errorf("ERROR: http.Serve() - %s", err)
+ ws.log.WithError(err).Error("failed to listen")
}
}
@@ -100,14 +100,14 @@ func (ws *WebServer) serve(listener net.Listener) {
// We received an interrupt signal, shut down.
if err := srv.Shutdown(context.Background()); err != nil {
// Error from closing listeners, or context timeout:
- ws.log.Printf("HTTP server Shutdown: %v", err)
+ ws.log.WithError(err).Warning("HTTP server Shutdown")
}
close(idleConnsClosed)
}()
err := srv.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
- ws.log.Errorf("ERROR: http.Serve() - %s", err)
+ ws.log.WithError(err).Error("ERROR: http.Serve()")
}
<-idleConnsClosed
}
diff --git a/lifecycle/ak b/lifecycle/ak
index 03759d0e57..b857c588ef 100755
--- a/lifecycle/ak
+++ b/lifecycle/ak
@@ -67,7 +67,7 @@ if [[ "$1" == "server" ]]; then
/authentik-proxy
elif [[ "$1" == "worker" ]]; then
echo "worker" > $MODE_FILE
- check_if_root "celery -A authentik.root.celery worker --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events"
+ check_if_root "celery -A authentik.root.celery worker -Ofair --autoscale 3,1 -E -B -s /tmp/celerybeat-schedule -Q authentik,authentik_scheduled,authentik_events"
elif [[ "$1" == "flower" ]]; then
echo "flower" > $MODE_FILE
celery -A authentik.root.celery flower
diff --git a/web/src/elements/Markdown.ts b/web/src/elements/Markdown.ts
new file mode 100644
index 0000000000..d0ff3279ac
--- /dev/null
+++ b/web/src/elements/Markdown.ts
@@ -0,0 +1,32 @@
+import { CSSResult, LitElement, TemplateResult, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import { unsafeHTML } from "lit/directives/unsafe-html.js";
+
+import AKGlobal from "../authentik.css";
+import PFContent from "@patternfly/patternfly/components/Content/content.css";
+import PFList from "@patternfly/patternfly/components/List/list.css";
+
+export interface MarkdownDocument {
+ html: string;
+ metadata: { [key: string]: string };
+ filename: string;
+}
+
+@customElement("ak-markdown")
+export class Markdown extends LitElement {
+ @property({ attribute: false })
+ md?: MarkdownDocument;
+
+ static get styles(): CSSResult[] {
+ return [PFList, PFContent, AKGlobal];
+ }
+
+ render(): TemplateResult {
+ if (!this.md) {
+ return html``;
+ }
+ const finalHTML = this.md?.html.replace("", "");
+ return html`${this.md?.metadata.title ? html`${this.md.metadata.title}
` : html``}
+ ${unsafeHTML(finalHTML)}`;
+ }
+}
diff --git a/web/src/elements/PageHeader.ts b/web/src/elements/PageHeader.ts
index 17042ff613..9ead252816 100644
--- a/web/src/elements/PageHeader.ts
+++ b/web/src/elements/PageHeader.ts
@@ -81,7 +81,7 @@ export class PageHeader extends LitElement {
font-size: 24px;
}
.notification-trigger.has-notifications {
- color: #2b9af3;
+ color: var(--pf-global--active-color--100);
}
`,
];
diff --git a/web/src/elements/notifications/NotificationDrawer.ts b/web/src/elements/notifications/NotificationDrawer.ts
index 175a5ae448..e619a5fbd4 100644
--- a/web/src/elements/notifications/NotificationDrawer.ts
+++ b/web/src/elements/notifications/NotificationDrawer.ts
@@ -183,9 +183,15 @@ export class NotificationDrawer extends LitElement {
composed: true,
}),
);
+ this.dispatchEvent(
+ new CustomEvent(EVENT_NOTIFICATION_DRAWER_TOGGLE, {
+ bubbles: true,
+ composed: true,
+ }),
+ );
});
}}
- class="pf-c-button pf-m-secondary pf-m-block"
+ class="pf-c-button pf-m-primary pf-m-block"
type="button"
aria-label=${t`Clear all`}
>
diff --git a/web/src/elements/table/TablePage.ts b/web/src/elements/table/TablePage.ts
index a52416a09e..8ef5462cc5 100644
--- a/web/src/elements/table/TablePage.ts
+++ b/web/src/elements/table/TablePage.ts
@@ -4,6 +4,7 @@ import { ifDefined } from "lit/directives/if-defined.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
+import PFSidebar from "@patternfly/patternfly/components/Sidebar/sidebar.css";
import "../../elements/PageHeader";
import { Table } from "./Table";
@@ -14,7 +15,15 @@ export abstract class TablePage extends Table {
abstract pageIcon(): string;
static get styles(): CSSResult[] {
- return super.styles.concat(PFPage, PFContent);
+ return super.styles.concat(PFPage, PFContent, PFSidebar);
+ }
+
+ renderSidebarBefore(): TemplateResult {
+ return html``;
+ }
+
+ renderSidebarAfter(): TemplateResult {
+ return html``;
}
render(): TemplateResult {
@@ -25,7 +34,15 @@ export abstract class TablePage extends Table {
>
- ${this.renderTable()}
+
`;
}
}
diff --git a/web/src/global.d.ts b/web/src/global.d.ts
index 203b51ec27..b7b3dfdf8b 100644
--- a/web/src/global.d.ts
+++ b/web/src/global.d.ts
@@ -1,7 +1,7 @@
declare module "*.css";
declare module "*.md" {
const html: string;
- const metadata: object;
+ const metadata: { [key: string]: string };
const filename: string;
}
diff --git a/web/src/locales/en.po b/web/src/locales/en.po
index 7ef86c5317..5a9f61641e 100644
--- a/web/src/locales/en.po
+++ b/web/src/locales/en.po
@@ -122,6 +122,10 @@ msgstr "API Token (can be used to access the API programmatically)"
msgid "API request failed"
msgstr "API request failed"
+#: src/pages/applications/ApplicationListPage.ts
+msgid "About applications"
+msgstr "About applications"
+
#: src/pages/sources/oauth/OAuthSourceViewPage.ts
msgid "Access Key"
msgstr "Access Key"
diff --git a/web/src/locales/fr_FR.po b/web/src/locales/fr_FR.po
index 7838b5efb7..93ca08a807 100644
--- a/web/src/locales/fr_FR.po
+++ b/web/src/locales/fr_FR.po
@@ -128,6 +128,10 @@ msgstr "Jeton d'API (peut être utilisé pour accéder à l'API via un programme
msgid "API request failed"
msgstr "Requête d'API échouée"
+#: src/pages/applications/ApplicationListPage.ts
+msgid "About applications"
+msgstr ""
+
#: src/pages/sources/oauth/OAuthSourceViewPage.ts
msgid "Access Key"
msgstr "Clé d'accès"
diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po
index b024d7e92c..521ef15f62 100644
--- a/web/src/locales/pseudo-LOCALE.po
+++ b/web/src/locales/pseudo-LOCALE.po
@@ -122,6 +122,10 @@ msgstr ""
msgid "API request failed"
msgstr ""
+#: src/pages/applications/ApplicationListPage.ts
+msgid "About applications"
+msgstr ""
+
#: src/pages/sources/oauth/OAuthSourceViewPage.ts
msgid "Access Key"
msgstr ""
diff --git a/web/src/pages/applications/ApplicationListPage.ts b/web/src/pages/applications/ApplicationListPage.ts
index b408ffd54b..007f03810b 100644
--- a/web/src/pages/applications/ApplicationListPage.ts
+++ b/web/src/pages/applications/ApplicationListPage.ts
@@ -5,12 +5,15 @@ import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
+import PFCard from "@patternfly/patternfly/components/Card/card.css";
import { Application, CoreApi } from "@goauthentik/api";
+import MDApplication from "../../../../website/docs/core/applications.md";
import { AKResponse } from "../../api/Client";
import { DEFAULT_CONFIG } from "../../api/Config";
import { uiConfig } from "../../common/config";
+import "../../elements/Markdown";
import "../../elements/buttons/SpinnerButton";
import "../../elements/forms/DeleteBulkForm";
import "../../elements/forms/ModalForm";
@@ -52,6 +55,7 @@ export class ApplicationListPage extends TablePage {
static get styles(): CSSResult[] {
return super.styles.concat(
PFAvatar,
+ PFCard,
css`
tr td:first-child {
width: auto;
@@ -74,6 +78,17 @@ export class ApplicationListPage extends TablePage {
];
}
+ renderSidebarAfter(): TemplateResult {
+ return html``;
+ }
+
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`
- ${this.renderConfigTemplate(MDNginxIngress.html)}
+
- ${this.renderConfigTemplate(MDNginxPM.html)}
+
- ${this.renderConfigTemplate(MDNginxStandalone.html)}
+
- ${this.renderConfigTemplate(MDTraefikIngres.html)}
+
- ${this.renderConfigTemplate(MDTraefikCompose.html)}
+
- ${this.renderConfigTemplate(MDTraefikStandalone.html)}
+
`
diff --git a/web/src/pages/stages/StageListPage.ts b/web/src/pages/stages/StageListPage.ts
index 7ebe27a6e9..a02a6c9eb2 100644
--- a/web/src/pages/stages/StageListPage.ts
+++ b/web/src/pages/stages/StageListPage.ts
@@ -107,11 +107,15 @@ export class StageListPage extends TablePage {
${item.name}
${item.verboseName}
`,
- html`${item.flowSet?.map((flow) => {
- return html`
- ${flow.slug}
- `;
- })}`,
+ html`
+ ${item.flowSet?.map((flow) => {
+ return html`-
+
+
${flow.slug}
+
+ `;
+ })}
+
`,
html`
${t`Update`}
${t`Update ${item.verboseName}`}
diff --git a/website/docs/core/applications.md b/website/docs/core/applications.md
index ab6ffec2eb..f0d5e09052 100644
--- a/website/docs/core/applications.md
+++ b/website/docs/core/applications.md
@@ -3,7 +3,7 @@ title: Applications
slug: /applications
---
-Applications in authentik are the counterpart of providers. They exist in a 1-to-1 relationship, each application needs a provider and every provider can be used with one application.
+Applications in authentik are the other half of providers. They exist in a 1-to-1 relationship, each application needs a provider and every provider can be used with one application.
Applications are used to configure and separate the authorization / access control and the appearance in the Library page.
@@ -15,23 +15,23 @@ By default, all users can access applications when no policies are bound.
When multiple policies/groups/users are attached, you can configure the *Policy engine mode* to either
- - Require users to pass all bindings/be member of all groups (ALL), or
- - Require users to pass either binding/be member of either group (ANY)
+- Require users to pass all bindings/be member of all groups (ALL), or
+- Require users to pass either binding/be member of either group (ANY)
## Appearance
The following aspects can be configured:
- - *Name*: This is the name shown for the application card
- - *Launch URL*: The URL that is opened when a user clicks on the application. When left empty, authentik tries to guess it based on the provider
- - *Icon (URL)*: Optionally configure an Icon for the application
- - *Publisher*: Text shown below the application
- - *Description*: Subtext shown on the application card below the publisher
+- *Name*: This is the name shown for the application card
+- *Launch URL*: The URL that is opened when a user clicks on the application. When left empty, authentik tries to guess it based on the provider
+- *Icon (URL)*: Optionally configure an Icon for the application
+- *Publisher*: Text shown below the application
+- *Description*: Subtext shown on the application card below the publisher
Applications are shown to users when
- - The user has access defined via policies (or the application has no policies bound)
- - A Valid Launch URL is configured/could be guessed, this consists of URLs starting with http:// and https://
+- The user has access defined via policies (or the application has no policies bound)
+- A Valid Launch URL is configured/could be guessed, this consists of URLs starting with http:// and https://
#### Hiding applications
diff --git a/website/docs/releases/v2021.12.md b/website/docs/releases/v2021.12.md
index 9854a18959..3a384b1d25 100644
--- a/website/docs/releases/v2021.12.md
+++ b/website/docs/releases/v2021.12.md
@@ -151,6 +151,19 @@ This release does not have any headline features, and mostly fixes bugs.
- web/admin: update overview page
- web/flows: fix error when attempting to enroll new webauthn device
+## Fixed in 2021.12.1
+
+- core: fix error when attempting to provider from cached application
+- events: improve app lookup for event creation
+- internal: cleanup duplicate and redundant code, properly set sentry SDK scope settings
+- lifecycle: add -Ofair to celery
+- web/admin: add sidebar to applications
+- web/admin: fix notification unread colours not matching on user and admin interface
+- web/admin: fix stage related flows not being shown in a list
+- web/elements: add Markdown component to improve rendering
+- web/elements: add support for sidebar on table page
+- web/elements: close notification drawer when clearing all notifications
+
## Upgrading
This release does not introduce any new requirements.
diff --git a/website/integrations/services/bookstack/index.md b/website/integrations/services/bookstack/index.md
index e6466df043..be0923836c 100644
--- a/website/integrations/services/bookstack/index.md
+++ b/website/integrations/services/bookstack/index.md
@@ -101,3 +101,9 @@ BookStack will attempt to match the SAML user to an existing BookStack user base
:::note
SAML Group Sync is supported by Bookstack. Review the BookStack documentation on the required Environment variables. https://www.bookstackapp.com/docs/admin/saml2-auth/
:::
+
+:::note
+In some cases you might need to define the full SAML property name.
+i.e.: `SAML2_GROUP_ATTRIBUTE="http://schemas.xmlsoap.org/claims/Group"`
+See https://github.com/BookStackApp/BookStack/issues/3109 for more details.
+:::