Compare commits
11 Commits
trustchain
...
version-20
Author | SHA1 | Date |
---|---|---|
Jens L | fb0a88f2cf | |
Jens L | 4d8d405e70 | |
Jens L | 1d5f399b61 | |
Jens L | bb575fcc10 | |
Jens L | 13fd1afbb9 | |
Jens Langhammer | f059b998cc | |
Jens L | 3f48202dfe | |
Jens L | 2a3ebb616b | |
Jens L | ceab1f732d | |
Jens Langhammer | 01d2cce9ca | |
Jens Langhammer | 72f85defb8 |
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2023.3.0
|
current_version = 2023.3.1
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
|
||||||
|
|
|
@ -12,3 +12,6 @@ indent_size = 2
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.{yaml,yml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
from os import environ
|
from os import environ
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__version__ = "2023.3.0"
|
__version__ = "2023.3.1"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,8 @@ class Command(BaseCommand):
|
||||||
for blueprint_path in options.get("blueprints", []):
|
for blueprint_path in options.get("blueprints", []):
|
||||||
content = BlueprintInstance(path=blueprint_path).retrieve()
|
content = BlueprintInstance(path=blueprint_path).retrieve()
|
||||||
importer = Importer(content)
|
importer = Importer(content)
|
||||||
valid, logs = importer.validate()
|
valid, _ = importer.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
for log in logs:
|
|
||||||
getattr(LOGGER, log.pop("log_level"))(**log)
|
|
||||||
self.stderr.write("blueprint invalid")
|
self.stderr.write("blueprint invalid")
|
||||||
sys_exit(1)
|
sys_exit(1)
|
||||||
importer.apply()
|
importer.apply()
|
||||||
|
|
|
@ -40,6 +40,10 @@ from authentik.lib.models import SerializerModel
|
||||||
from authentik.outposts.models import OutpostServiceConnection
|
from authentik.outposts.models import OutpostServiceConnection
|
||||||
from authentik.policies.models import Policy, PolicyBindingModel
|
from authentik.policies.models import Policy, PolicyBindingModel
|
||||||
|
|
||||||
|
# Context set when the serializer is created in a blueprint context
|
||||||
|
# Update website/developer-docs/blueprints/v1/models.md when used
|
||||||
|
SERIALIZER_CONTEXT_BLUEPRINT = "blueprint_entry"
|
||||||
|
|
||||||
|
|
||||||
def is_model_allowed(model: type[Model]) -> bool:
|
def is_model_allowed(model: type[Model]) -> bool:
|
||||||
"""Check if model is allowed"""
|
"""Check if model is allowed"""
|
||||||
|
@ -158,7 +162,12 @@ class Importer:
|
||||||
raise EntryInvalidError(f"Model {model} not allowed")
|
raise EntryInvalidError(f"Model {model} not allowed")
|
||||||
if issubclass(model, BaseMetaModel):
|
if issubclass(model, BaseMetaModel):
|
||||||
serializer_class: type[Serializer] = model.serializer()
|
serializer_class: type[Serializer] = model.serializer()
|
||||||
serializer = serializer_class(data=entry.get_attrs(self.__import))
|
serializer = serializer_class(
|
||||||
|
data=entry.get_attrs(self.__import),
|
||||||
|
context={
|
||||||
|
SERIALIZER_CONTEXT_BLUEPRINT: entry,
|
||||||
|
},
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
|
@ -217,7 +226,12 @@ class Importer:
|
||||||
always_merger.merge(full_data, updated_identifiers)
|
always_merger.merge(full_data, updated_identifiers)
|
||||||
serializer_kwargs["data"] = full_data
|
serializer_kwargs["data"] = full_data
|
||||||
|
|
||||||
serializer: Serializer = model().serializer(**serializer_kwargs)
|
serializer: Serializer = model().serializer(
|
||||||
|
context={
|
||||||
|
SERIALIZER_CONTEXT_BLUEPRINT: entry,
|
||||||
|
},
|
||||||
|
**serializer_kwargs,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
except ValidationError as exc:
|
except ValidationError as exc:
|
||||||
|
|
|
@ -16,6 +16,7 @@ from rest_framework.viewsets import ModelViewSet
|
||||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
from authentik.api.authorization import OwnerSuperuserPermissions
|
||||||
from authentik.api.decorators import permission_required
|
from authentik.api.decorators import permission_required
|
||||||
from authentik.blueprints.api import ManagedSerializer
|
from authentik.blueprints.api import ManagedSerializer
|
||||||
|
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.users import UserSerializer
|
from authentik.core.api.users import UserSerializer
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
@ -29,6 +30,11 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
|
||||||
|
|
||||||
user_obj = UserSerializer(required=False, source="user", read_only=True)
|
user_obj = UserSerializer(required=False, source="user", read_only=True)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
|
||||||
|
self.fields["key"] = CharField()
|
||||||
|
|
||||||
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
|
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
|
||||||
"""Ensure only API or App password tokens are created."""
|
"""Ensure only API or App password tokens are created."""
|
||||||
request: Request = self.context.get("request")
|
request: Request = self.context.get("request")
|
||||||
|
|
|
@ -355,6 +355,62 @@ class TestAuthorize(OAuthTestCase):
|
||||||
delta=5,
|
delta=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_full_fragment_code(self):
|
||||||
|
"""Test full authorization"""
|
||||||
|
flow = create_test_flow()
|
||||||
|
provider: OAuth2Provider = OAuth2Provider.objects.create(
|
||||||
|
name=generate_id(),
|
||||||
|
client_id="test",
|
||||||
|
client_secret=generate_key(),
|
||||||
|
authorization_flow=flow,
|
||||||
|
redirect_uris="http://localhost",
|
||||||
|
signing_key=self.keypair,
|
||||||
|
)
|
||||||
|
Application.objects.create(name="app", slug="app", provider=provider)
|
||||||
|
state = generate_id()
|
||||||
|
user = create_test_admin_user()
|
||||||
|
self.client.force_login(user)
|
||||||
|
with patch(
|
||||||
|
"authentik.providers.oauth2.id_token.get_login_event",
|
||||||
|
MagicMock(
|
||||||
|
return_value=Event(
|
||||||
|
action=EventAction.LOGIN,
|
||||||
|
context={PLAN_CONTEXT_METHOD: "password"},
|
||||||
|
created=now(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
):
|
||||||
|
# Step 1, initiate params and get redirect to flow
|
||||||
|
self.client.get(
|
||||||
|
reverse("authentik_providers_oauth2:authorize"),
|
||||||
|
data={
|
||||||
|
"response_type": "code",
|
||||||
|
"response_mode": "fragment",
|
||||||
|
"client_id": "test",
|
||||||
|
"state": state,
|
||||||
|
"scope": "openid",
|
||||||
|
"redirect_uri": "http://localhost",
|
||||||
|
"nonce": generate_id(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
|
||||||
|
)
|
||||||
|
code: AuthorizationCode = AuthorizationCode.objects.filter(user=user).first()
|
||||||
|
self.assertJSONEqual(
|
||||||
|
response.content.decode(),
|
||||||
|
{
|
||||||
|
"component": "xak-flow-redirect",
|
||||||
|
"type": ChallengeTypes.REDIRECT.value,
|
||||||
|
"to": (f"http://localhost#code={code.code}" f"&state={state}"),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertAlmostEqual(
|
||||||
|
code.expires.timestamp() - now().timestamp(),
|
||||||
|
timedelta_from_string(provider.access_code_validity).total_seconds(),
|
||||||
|
delta=5,
|
||||||
|
)
|
||||||
|
|
||||||
def test_full_form_post_id_token(self):
|
def test_full_form_post_id_token(self):
|
||||||
"""Test full authorization (form_post response)"""
|
"""Test full authorization (form_post response)"""
|
||||||
flow = create_test_flow()
|
flow = create_test_flow()
|
||||||
|
|
|
@ -514,7 +514,12 @@ class OAuthFulfillmentStage(StageView):
|
||||||
return urlunsplit(uri)
|
return urlunsplit(uri)
|
||||||
|
|
||||||
if self.params.response_mode == ResponseMode.FRAGMENT:
|
if self.params.response_mode == ResponseMode.FRAGMENT:
|
||||||
query_fragment = self.create_implicit_response(code)
|
query_fragment = {}
|
||||||
|
if self.params.grant_type in [GrantTypes.AUTHORIZATION_CODE]:
|
||||||
|
query_fragment["code"] = code.code
|
||||||
|
query_fragment["state"] = [str(self.params.state) if self.params.state else ""]
|
||||||
|
else:
|
||||||
|
query_fragment = self.create_implicit_response(code)
|
||||||
|
|
||||||
uri = uri._replace(
|
uri = uri._replace(
|
||||||
fragment=uri.fragment + urlencode(query_fragment, doseq=True),
|
fragment=uri.fragment + urlencode(query_fragment, doseq=True),
|
||||||
|
|
|
@ -32,7 +32,7 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
server:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.0}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
|
@ -50,7 +50,7 @@ services:
|
||||||
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
- "${AUTHENTIK_PORT_HTTP:-9000}:9000"
|
||||||
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
- "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
|
||||||
worker:
|
worker:
|
||||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.0}
|
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.3.1}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2023.3.0"
|
const VERSION = "2023.3.1"
|
||||||
|
|
|
@ -30,11 +30,15 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
|
||||||
// Only append attributes that don't already exist
|
// Only append attributes that don't already exist
|
||||||
// TODO: Remove in 2023.3
|
// TODO: Remove in 2023.3
|
||||||
for _, rawAttr := range rawAttrs {
|
for _, rawAttr := range rawAttrs {
|
||||||
|
exists := false
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
if !strings.EqualFold(attr.Name, rawAttr.Name) {
|
if strings.EqualFold(attr.Name, rawAttr.Name) {
|
||||||
attrs = append(attrs, rawAttr)
|
exists = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !exists {
|
||||||
|
attrs = append(attrs, rawAttr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if u.IsActive == nil {
|
if u.IsActive == nil {
|
||||||
|
|
|
@ -36,11 +36,15 @@ func (lg *LDAPGroup) Entry() *ldap.Entry {
|
||||||
// Only append attributes that don't already exist
|
// Only append attributes that don't already exist
|
||||||
// TODO: Remove in 2023.3
|
// TODO: Remove in 2023.3
|
||||||
for _, rawAttr := range rawAttrs {
|
for _, rawAttr := range rawAttrs {
|
||||||
|
exists := false
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
if !strings.EqualFold(attr.Name, rawAttr.Name) {
|
if strings.EqualFold(attr.Name, rawAttr.Name) {
|
||||||
attrs = append(attrs, rawAttr)
|
exists = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !exists {
|
||||||
|
attrs = append(attrs, rawAttr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objectClass := []string{constants.OCGroup, constants.OCGroupOfUniqueNames, constants.OCGroupOfNames, constants.OCAKGroup, constants.OCPosixGroup}
|
objectClass := []string{constants.OCGroup, constants.OCGroupOfUniqueNames, constants.OCGroupOfNames, constants.OCAKGroup, constants.OCPosixGroup}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"goauthentik.io/api/v3"
|
"goauthentik.io/api/v3"
|
||||||
|
@ -31,40 +30,55 @@ func updateURL(rawUrl string, scheme string, host string) string {
|
||||||
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint {
|
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string, embedded bool) OIDCEndpoint {
|
||||||
authUrl := p.OidcConfiguration.AuthorizationEndpoint
|
authUrl := p.OidcConfiguration.AuthorizationEndpoint
|
||||||
endUrl := p.OidcConfiguration.EndSessionEndpoint
|
endUrl := p.OidcConfiguration.EndSessionEndpoint
|
||||||
tokenUrl := p.OidcConfiguration.TokenEndpoint
|
|
||||||
jwksUrl := p.OidcConfiguration.JwksUri
|
|
||||||
issuer := p.OidcConfiguration.Issuer
|
issuer := p.OidcConfiguration.Issuer
|
||||||
if config.Get().AuthentikHostBrowser != "" {
|
|
||||||
authUrl = strings.ReplaceAll(authUrl, authentikHost, config.Get().AuthentikHostBrowser)
|
|
||||||
endUrl = strings.ReplaceAll(endUrl, authentikHost, config.Get().AuthentikHostBrowser)
|
|
||||||
jwksUrl = strings.ReplaceAll(jwksUrl, authentikHost, config.Get().AuthentikHostBrowser)
|
|
||||||
issuer = strings.ReplaceAll(issuer, authentikHost, config.Get().AuthentikHostBrowser)
|
|
||||||
}
|
|
||||||
ep := OIDCEndpoint{
|
ep := OIDCEndpoint{
|
||||||
Endpoint: oauth2.Endpoint{
|
Endpoint: oauth2.Endpoint{
|
||||||
AuthURL: authUrl,
|
AuthURL: authUrl,
|
||||||
TokenURL: tokenUrl,
|
TokenURL: p.OidcConfiguration.TokenEndpoint,
|
||||||
AuthStyle: oauth2.AuthStyleInParams,
|
AuthStyle: oauth2.AuthStyleInParams,
|
||||||
},
|
},
|
||||||
EndSessionEndpoint: endUrl,
|
EndSessionEndpoint: endUrl,
|
||||||
JwksUri: jwksUrl,
|
JwksUri: p.OidcConfiguration.JwksUri,
|
||||||
TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint,
|
TokenIntrospection: p.OidcConfiguration.IntrospectionEndpoint,
|
||||||
Issuer: issuer,
|
Issuer: issuer,
|
||||||
}
|
}
|
||||||
if !embedded {
|
// For the embedded outpost, we use the configure `authentik_host` for the browser URLs
|
||||||
|
// and localhost (which is what we've got from the API) for backchannel URLs
|
||||||
|
//
|
||||||
|
// For other outposts, when `AUTHENTIK_HOST_BROWSER` is set, we use that for the browser URLs
|
||||||
|
// and use what we got from the API for backchannel
|
||||||
|
hostBrowser := config.Get().AuthentikHostBrowser
|
||||||
|
if !embedded && hostBrowser == "" {
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
if authentikHost == "" {
|
var newHost *url.URL
|
||||||
log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.")
|
if embedded {
|
||||||
return ep
|
if authentikHost == "" {
|
||||||
|
log.Warning("Outpost has localhost/blank API Connection but no authentik_host is configured.")
|
||||||
|
return ep
|
||||||
|
}
|
||||||
|
aku, err := url.Parse(authentikHost)
|
||||||
|
if err != nil {
|
||||||
|
return ep
|
||||||
|
}
|
||||||
|
newHost = aku
|
||||||
|
} else if hostBrowser != "" {
|
||||||
|
aku, err := url.Parse(hostBrowser)
|
||||||
|
if err != nil {
|
||||||
|
return ep
|
||||||
|
}
|
||||||
|
newHost = aku
|
||||||
}
|
}
|
||||||
aku, err := url.Parse(authentikHost)
|
// Update all browser-accessed URLs to use the new host and scheme
|
||||||
if err != nil {
|
ep.AuthURL = updateURL(authUrl, newHost.Scheme, newHost.Host)
|
||||||
return ep
|
ep.EndSessionEndpoint = updateURL(endUrl, newHost.Scheme, newHost.Host)
|
||||||
|
// Update issuer to use the same host and scheme, which would normally break as we don't
|
||||||
|
// change the token URL here, but the token HTTP transport overwrites the Host header
|
||||||
|
//
|
||||||
|
// This is only used in embedded outposts as there we can guarantee that the request
|
||||||
|
// is routed correctly
|
||||||
|
if embedded {
|
||||||
|
ep.Issuer = updateURL(ep.Issuer, newHost.Scheme, newHost.Host)
|
||||||
}
|
}
|
||||||
ep.AuthURL = updateURL(authUrl, aku.Scheme, aku.Host)
|
|
||||||
ep.EndSessionEndpoint = updateURL(endUrl, aku.Scheme, aku.Host)
|
|
||||||
ep.JwksUri = updateURL(jwksUrl, aku.Scheme, aku.Host)
|
|
||||||
ep.Issuer = updateURL(ep.Issuer, aku.Scheme, aku.Host)
|
|
||||||
return ep
|
return ep
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
package application
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"goauthentik.io/api/v3"
|
||||||
|
"goauthentik.io/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEndpointDefault(t *testing.T) {
|
||||||
|
pc := api.ProxyOutpostConfig{
|
||||||
|
OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{
|
||||||
|
AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/",
|
||||||
|
EndSessionEndpoint: "https://test.goauthentik.io/application/o/test-app/end-session/",
|
||||||
|
IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/",
|
||||||
|
Issuer: "https://test.goauthentik.io/application/o/test-app/",
|
||||||
|
JwksUri: "https://test.goauthentik.io/application/o/test-app/jwks/",
|
||||||
|
TokenEndpoint: "https://test.goauthentik.io/application/o/token/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false)
|
||||||
|
// Standard outpost, non embedded
|
||||||
|
// All URLs should use the host that they get from the config
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpointAuthentikHostBrowser(t *testing.T) {
|
||||||
|
c := config.Get()
|
||||||
|
c.AuthentikHostBrowser = "https://browser.test.goauthentik.io"
|
||||||
|
defer func() {
|
||||||
|
c.AuthentikHostBrowser = ""
|
||||||
|
}()
|
||||||
|
pc := api.ProxyOutpostConfig{
|
||||||
|
OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{
|
||||||
|
AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/",
|
||||||
|
EndSessionEndpoint: "https://test.goauthentik.io/application/o/test-app/end-session/",
|
||||||
|
IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/",
|
||||||
|
Issuer: "https://test.goauthentik.io/application/o/test-app/",
|
||||||
|
JwksUri: "https://test.goauthentik.io/application/o/test-app/jwks/",
|
||||||
|
TokenEndpoint: "https://test.goauthentik.io/application/o/token/",
|
||||||
|
UserinfoEndpoint: "https://test.goauthentik.io/application/o/userinfo/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", false)
|
||||||
|
// Standard outpost, with AUTHENTIK_HOST_BROWSER set
|
||||||
|
// Only the authorize/end session URLs should be changed
|
||||||
|
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
||||||
|
assert.Equal(t, "https://browser.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpointEmbedded(t *testing.T) {
|
||||||
|
pc := api.ProxyOutpostConfig{
|
||||||
|
OidcConfiguration: api.ProxyOutpostConfigOidcConfiguration{
|
||||||
|
AuthorizationEndpoint: "https://test.goauthentik.io/application/o/authorize/",
|
||||||
|
EndSessionEndpoint: "https://test.goauthentik.io/application/o/test-app/end-session/",
|
||||||
|
IntrospectionEndpoint: "https://test.goauthentik.io/application/o/introspect/",
|
||||||
|
Issuer: "https://test.goauthentik.io/application/o/test-app/",
|
||||||
|
JwksUri: "https://test.goauthentik.io/application/o/test-app/jwks/",
|
||||||
|
TokenEndpoint: "https://test.goauthentik.io/application/o/token/",
|
||||||
|
UserinfoEndpoint: "https://test.goauthentik.io/application/o/userinfo/",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ep := GetOIDCEndpoint(pc, "https://authentik-host.test.goauthentik.io", true)
|
||||||
|
// Embedded outpost
|
||||||
|
// Browser URLs should use the config of "authentik_host", everything else can use what's
|
||||||
|
// received from the API endpoint
|
||||||
|
// Token URL is an exception since it's sent via a special HTTP transport that overrides the
|
||||||
|
// HTTP Host header, to make sure it's the same value as the issuer
|
||||||
|
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/authorize/", ep.AuthURL)
|
||||||
|
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/", ep.Issuer)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/token/", ep.TokenURL)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/test-app/jwks/", ep.JwksUri)
|
||||||
|
assert.Equal(t, "https://authentik-host.test.goauthentik.io/application/o/test-app/end-session/", ep.EndSessionEndpoint)
|
||||||
|
assert.Equal(t, "https://test.goauthentik.io/application/o/introspect/", ep.TokenIntrospection)
|
||||||
|
}
|
|
@ -105,7 +105,7 @@ filterwarnings = [
|
||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2023.3.0"
|
version = "2023.3.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2023.3.0
|
version: 2023.3.1
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -74,8 +74,6 @@
|
||||||
"@lingui/detect-locale": "^3.17.2",
|
"@lingui/detect-locale": "^3.17.2",
|
||||||
"@lingui/macro": "^3.17.2",
|
"@lingui/macro": "^3.17.2",
|
||||||
"@patternfly/patternfly": "^4.224.2",
|
"@patternfly/patternfly": "^4.224.2",
|
||||||
"@polymer/iron-form": "^3.0.1",
|
|
||||||
"@polymer/paper-input": "^3.2.1",
|
|
||||||
"@rollup/plugin-babel": "^6.0.3",
|
"@rollup/plugin-babel": "^6.0.3",
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
|
|
@ -61,6 +61,9 @@ export class LDAPSyncStatusChart extends AKChart<SyncStatus[]> {
|
||||||
metrics.healthy += 1;
|
metrics.healthy += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (health.length < 1) {
|
||||||
|
metrics.unsynced += 1;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
metrics.unsynced += 1;
|
metrics.unsynced += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
|
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -60,7 +61,7 @@ export class TypeOAuthCodeApplicationWizardPage extends WizardFormPage {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { FlowDesignationEnum, LayoutEnum } from "@goauthentik/api";
|
import { Flow, FlowDesignationEnum, LayoutEnum } from "@goauthentik/api";
|
||||||
|
|
||||||
|
export function RenderFlowOption(flow: Flow): string {
|
||||||
|
return `${flow.slug} (${flow.name})`;
|
||||||
|
}
|
||||||
|
|
||||||
export function DesignationToLabel(designation: FlowDesignationEnum): string {
|
export function DesignationToLabel(designation: FlowDesignationEnum): string {
|
||||||
switch (designation) {
|
switch (designation) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -9,9 +10,8 @@ import "@goauthentik/elements/forms/SearchSelect";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
|
|
||||||
import { TemplateResult, html } from "lit";
|
import { TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators.js";
|
import { customElement, state } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { until } from "lit/directives/until.js";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CertificateKeyPair,
|
CertificateKeyPair,
|
||||||
|
@ -19,6 +19,7 @@ import {
|
||||||
CoreGroupsListRequest,
|
CoreGroupsListRequest,
|
||||||
CryptoApi,
|
CryptoApi,
|
||||||
CryptoCertificatekeypairsListRequest,
|
CryptoCertificatekeypairsListRequest,
|
||||||
|
CurrentTenant,
|
||||||
Flow,
|
Flow,
|
||||||
FlowsApi,
|
FlowsApi,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
|
@ -31,10 +32,14 @@ import {
|
||||||
|
|
||||||
@customElement("ak-provider-ldap-form")
|
@customElement("ak-provider-ldap-form")
|
||||||
export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
|
export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
|
||||||
loadInstance(pk: number): Promise<LDAPProvider> {
|
@state()
|
||||||
return new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
|
tenant?: CurrentTenant;
|
||||||
|
async loadInstance(pk: number): Promise<LDAPProvider> {
|
||||||
|
const provider = await new ProvidersApi(DEFAULT_CONFIG).providersLdapRetrieve({
|
||||||
id: pk,
|
id: pk,
|
||||||
});
|
});
|
||||||
|
this.tenant = await tenant();
|
||||||
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
|
@ -74,46 +79,36 @@ export class LDAPProviderFormPage extends ModelForm<LDAPProvider, number> {
|
||||||
?required=${true}
|
?required=${true}
|
||||||
name="authorizationFlow"
|
name="authorizationFlow"
|
||||||
>
|
>
|
||||||
${until(
|
<ak-search-select
|
||||||
tenant().then((t) => {
|
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
||||||
return html`
|
const args: FlowsInstancesListRequest = {
|
||||||
<ak-search-select
|
ordering: "slug",
|
||||||
.fetchObjects=${async (query?: string): Promise<Flow[]> => {
|
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||||
const args: FlowsInstancesListRequest = {
|
};
|
||||||
ordering: "slug",
|
if (query !== undefined) {
|
||||||
designation:
|
args.search = query;
|
||||||
FlowsInstancesListDesignationEnum.Authentication,
|
}
|
||||||
};
|
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
|
||||||
if (query !== undefined) {
|
return flows.results;
|
||||||
args.search = query;
|
}}
|
||||||
}
|
.renderElement=${(flow: Flow): string => {
|
||||||
const flows = await new FlowsApi(
|
return RenderFlowOption(flow);
|
||||||
DEFAULT_CONFIG,
|
}}
|
||||||
).flowsInstancesList(args);
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return flows.results;
|
return html`${flow.slug}`;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.value=${(flow: Flow | undefined): string | undefined => {
|
||||||
return flow.name;
|
return flow?.pk;
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.selected=${(flow: Flow): boolean => {
|
||||||
return html`${flow.slug}`;
|
let selected = flow.pk === this.tenant?.flowAuthentication;
|
||||||
}}
|
if (this.instance?.authorizationFlow === flow.pk) {
|
||||||
.value=${(flow: Flow | undefined): string | undefined => {
|
selected = true;
|
||||||
return flow?.pk;
|
}
|
||||||
}}
|
return selected;
|
||||||
.selected=${(flow: Flow): boolean => {
|
}}
|
||||||
let selected = flow.pk === t.flowAuthentication;
|
>
|
||||||
if (this.instance?.authorizationFlow === flow.pk) {
|
</ak-search-select>
|
||||||
selected = true;
|
|
||||||
}
|
|
||||||
return selected;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
</ak-search-select>
|
|
||||||
`;
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}
|
${t`Flow used for users to authenticate. Currently only identification and password stages are supported.`}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first, randomString } from "@goauthentik/common/utils";
|
import { first, randomString } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -96,7 +97,7 @@ export class OAuth2ProviderFormPage extends ModelForm<OAuth2Provider, number> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -318,7 +319,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -88,7 +89,7 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
import { SentryIgnoredError } from "@goauthentik/common/errors";
|
||||||
import { Form } from "@goauthentik/elements/forms/Form";
|
import { Form } from "@goauthentik/elements/forms/Form";
|
||||||
|
@ -59,7 +60,7 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
@ -431,7 +432,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -477,7 +478,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
import { PlexAPIClient, PlexResource, popupCenterScreen } from "@goauthentik/common/helpers/plex";
|
||||||
|
@ -364,7 +365,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -410,7 +411,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
import { UserMatchingModeToLabel } from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
|
@ -496,7 +497,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -540,7 +541,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -586,7 +587,7 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -146,7 +147,7 @@ export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage,
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -292,7 +293,7 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -93,7 +94,7 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
@ -98,7 +99,7 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||||
|
@ -162,7 +163,7 @@ export class AuthenticateWebAuthnStageForm extends ModelForm<AuthenticateWebAuth
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first, groupBy } from "@goauthentik/common/utils";
|
import { first, groupBy } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -265,7 +266,7 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { dateTimeLocal, first } from "@goauthentik/common/utils";
|
import { dateTimeLocal, first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
|
@ -88,7 +89,7 @@ export class InvitationForm extends ModelForm<Invitation, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/forms/FormGroup";
|
import "@goauthentik/elements/forms/FormGroup";
|
||||||
|
@ -136,7 +137,7 @@ export class PasswordStageForm extends ModelForm<PasswordStage, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class PromptStageForm extends ModelForm<PromptStage, string> {
|
||||||
value=${ifDefined(prompt.pk)}
|
value=${ifDefined(prompt.pk)}
|
||||||
?selected=${selected}
|
?selected=${selected}
|
||||||
>
|
>
|
||||||
${t`${prompt.fieldKey} ("${prompt.label}", of type ${prompt.type})`}
|
${t`${prompt.name} ("${prompt.fieldKey}", of type ${prompt.type})`}
|
||||||
</option>`;
|
</option>`;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
|
@ -165,7 +166,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -202,7 +203,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -237,7 +238,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -274,7 +275,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -312,7 +313,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
@ -347,7 +348,7 @@ export class TenantForm extends ModelForm<Tenant, string> {
|
||||||
return flows.results;
|
return flows.results;
|
||||||
}}
|
}}
|
||||||
.renderElement=${(flow: Flow): string => {
|
.renderElement=${(flow: Flow): string => {
|
||||||
return flow.slug;
|
return RenderFlowOption(flow);
|
||||||
}}
|
}}
|
||||||
.renderDescription=${(flow: Flow): TemplateResult => {
|
.renderDescription=${(flow: Flow): TemplateResult => {
|
||||||
return html`${flow.name}`;
|
return html`${flow.name}`;
|
||||||
|
|
|
@ -7,19 +7,9 @@ import { EVENT_REFRESH, VERSION } from "@goauthentik/common/constants";
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { globalAK } from "@goauthentik/common/global";
|
||||||
import { activateLocale } from "@goauthentik/common/ui/locale";
|
import { activateLocale } from "@goauthentik/common/ui/locale";
|
||||||
|
|
||||||
import {
|
import { Config, Configuration, CoreApi, CurrentTenant, RootApi } from "@goauthentik/api";
|
||||||
Config,
|
|
||||||
ConfigFromJSON,
|
|
||||||
Configuration,
|
|
||||||
CoreApi,
|
|
||||||
CurrentTenant,
|
|
||||||
CurrentTenantFromJSON,
|
|
||||||
RootApi,
|
|
||||||
} from "@goauthentik/api";
|
|
||||||
|
|
||||||
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(
|
let globalConfigPromise: Promise<Config> | undefined = Promise.resolve(globalAK().config);
|
||||||
ConfigFromJSON(globalAK()?.config),
|
|
||||||
);
|
|
||||||
export function config(): Promise<Config> {
|
export function config(): Promise<Config> {
|
||||||
if (!globalConfigPromise) {
|
if (!globalConfigPromise) {
|
||||||
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
|
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
|
||||||
|
@ -52,9 +42,7 @@ export function tenantSetLocale(tenant: CurrentTenant) {
|
||||||
activateLocale(tenant.defaultLocale);
|
activateLocale(tenant.defaultLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(
|
let globalTenantPromise: Promise<CurrentTenant> | undefined = Promise.resolve(globalAK().tenant);
|
||||||
CurrentTenantFromJSON(globalAK()?.tenant),
|
|
||||||
);
|
|
||||||
export function tenant(): Promise<CurrentTenant> {
|
export function tenant(): Promise<CurrentTenant> {
|
||||||
if (!globalTenantPromise) {
|
if (!globalTenantPromise) {
|
||||||
globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
|
globalTenantPromise = new CoreApi(DEFAULT_CONFIG)
|
||||||
|
@ -82,7 +70,7 @@ export const DEFAULT_CONFIG = new Configuration({
|
||||||
middleware: [
|
middleware: [
|
||||||
new CSRFMiddleware(),
|
new CSRFMiddleware(),
|
||||||
new EventMiddleware(),
|
new EventMiddleware(),
|
||||||
new LoggingMiddleware(CurrentTenantFromJSON(globalAK()?.tenant)),
|
new LoggingMiddleware(globalAK().tenant),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||||
export const ERROR_CLASS = "pf-m-danger";
|
export const ERROR_CLASS = "pf-m-danger";
|
||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||||
export const CURRENT_CLASS = "pf-m-current";
|
export const CURRENT_CLASS = "pf-m-current";
|
||||||
export const VERSION = "2023.3.0";
|
export const VERSION = "2023.3.1";
|
||||||
export const TITLE_DEFAULT = "authentik";
|
export const TITLE_DEFAULT = "authentik";
|
||||||
export const ROUTE_SEPARATOR = ";";
|
export const ROUTE_SEPARATOR = ";";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Config, CurrentTenant } from "@goauthentik/api";
|
import { Config, ConfigFromJSON, CurrentTenant, CurrentTenantFromJSON } from "@goauthentik/api";
|
||||||
|
|
||||||
export interface GlobalAuthentik {
|
export interface GlobalAuthentik {
|
||||||
|
_converted?: boolean;
|
||||||
locale?: string;
|
locale?: string;
|
||||||
flow?: {
|
flow?: {
|
||||||
layout: string;
|
layout: string;
|
||||||
|
@ -13,11 +14,17 @@ export interface GlobalAuthentik {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthentikWindow {
|
export interface AuthentikWindow {
|
||||||
authentik?: GlobalAuthentik;
|
authentik: GlobalAuthentik;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function globalAK(): GlobalAuthentik | undefined {
|
export function globalAK(): GlobalAuthentik {
|
||||||
return (window as unknown as AuthentikWindow).authentik;
|
const ak = (window as unknown as AuthentikWindow).authentik;
|
||||||
|
if (ak && !ak._converted) {
|
||||||
|
ak._converted = true;
|
||||||
|
ak.tenant = CurrentTenantFromJSON(ak.tenant);
|
||||||
|
ak.config = ConfigFromJSON(ak.config);
|
||||||
|
}
|
||||||
|
return ak;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function docLink(path: string): string {
|
export function docLink(path: string): string {
|
||||||
|
|
|
@ -172,6 +172,6 @@ export class Interface extends AKElement {
|
||||||
|
|
||||||
async getTheme(): Promise<UiThemeEnum> {
|
async getTheme(): Promise<UiThemeEnum> {
|
||||||
const config = await uiConfig();
|
const config = await uiConfig();
|
||||||
return config.theme.base;
|
return config.theme?.base || UiThemeEnum.Automatic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
|
||||||
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||||
import "@polymer/iron-form/iron-form";
|
|
||||||
import { IronFormElement } from "@polymer/iron-form/iron-form";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
|
|
||||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
@ -110,63 +107,76 @@ export class Form<T> extends AKElement {
|
||||||
* Reset the inner iron-form
|
* Reset the inner iron-form
|
||||||
*/
|
*/
|
||||||
resetForm(): void {
|
resetForm(): void {
|
||||||
const ironForm = this.shadowRoot?.querySelector("iron-form");
|
const form = this.shadowRoot?.querySelector<HTMLFormElement>("form");
|
||||||
ironForm?.reset();
|
form?.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormFiles(): { [key: string]: File } {
|
getFormFiles(): { [key: string]: File } {
|
||||||
const ironForm = this.shadowRoot?.querySelector("iron-form");
|
|
||||||
const files: { [key: string]: File } = {};
|
const files: { [key: string]: File } = {};
|
||||||
if (!ironForm) {
|
const elements =
|
||||||
return files;
|
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
|
||||||
}
|
"ak-form-element-horizontal",
|
||||||
const elements = ironForm._getSubmittableElements();
|
) || [];
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
const element = elements[i] as HTMLInputElement;
|
const element = elements[i];
|
||||||
if (element.tagName.toLowerCase() === "input" && element.type === "file") {
|
element.requestUpdate();
|
||||||
if ((element.files || []).length < 1) {
|
const inputElement = element.querySelector<HTMLInputElement>("[name]");
|
||||||
|
if (!inputElement) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inputElement.tagName.toLowerCase() === "input" && inputElement.type === "file") {
|
||||||
|
if ((inputElement.files || []).length < 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
files[element.name] = (element.files || [])[0];
|
files[element.name] = (inputElement.files || [])[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeForm(): T | undefined {
|
serializeForm(): T | undefined {
|
||||||
const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form");
|
const elements =
|
||||||
if (!form) {
|
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
|
||||||
console.warn("authentik/forms: failed to find iron-form");
|
"ak-form-element-horizontal",
|
||||||
return;
|
) || [];
|
||||||
}
|
|
||||||
const elements: HTMLInputElement[] = form._getSubmittableElements();
|
|
||||||
const json: { [key: string]: unknown } = {};
|
const json: { [key: string]: unknown } = {};
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
const values = form._serializeElementValues(element);
|
element.requestUpdate();
|
||||||
if (element.hidden) {
|
const inputElement = element.querySelector<HTMLInputElement>("[name]");
|
||||||
|
if (element.hidden || !inputElement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (element.tagName.toLowerCase() === "select" && "multiple" in element.attributes) {
|
if (
|
||||||
json[element.name] = values;
|
inputElement.tagName.toLowerCase() === "select" &&
|
||||||
} else if (element.tagName.toLowerCase() === "input" && element.type === "date") {
|
"multiple" in inputElement.attributes
|
||||||
json[element.name] = element.valueAsDate;
|
|
||||||
} else if (
|
|
||||||
element.tagName.toLowerCase() === "input" &&
|
|
||||||
element.type === "datetime-local"
|
|
||||||
) {
|
) {
|
||||||
json[element.name] = new Date(element.valueAsNumber);
|
const selectElement = inputElement as unknown as HTMLSelectElement;
|
||||||
|
json[element.name] = Array.from(selectElement.selectedOptions).map((v) => v.value);
|
||||||
} else if (
|
} else if (
|
||||||
element.tagName.toLowerCase() === "input" &&
|
inputElement.tagName.toLowerCase() === "input" &&
|
||||||
"type" in element.dataset &&
|
inputElement.type === "date"
|
||||||
element.dataset["type"] === "datetime-local"
|
) {
|
||||||
|
json[element.name] = inputElement.valueAsDate;
|
||||||
|
} else if (
|
||||||
|
inputElement.tagName.toLowerCase() === "input" &&
|
||||||
|
inputElement.type === "datetime-local"
|
||||||
|
) {
|
||||||
|
json[element.name] = new Date(inputElement.valueAsNumber);
|
||||||
|
} else if (
|
||||||
|
inputElement.tagName.toLowerCase() === "input" &&
|
||||||
|
"type" in inputElement.dataset &&
|
||||||
|
inputElement.dataset["type"] === "datetime-local"
|
||||||
) {
|
) {
|
||||||
// Workaround for Firefox <93, since 92 and older don't support
|
// Workaround for Firefox <93, since 92 and older don't support
|
||||||
// datetime-local fields
|
// datetime-local fields
|
||||||
json[element.name] = new Date(element.value);
|
json[element.name] = new Date(inputElement.value);
|
||||||
} else if (element.tagName.toLowerCase() === "input" && element.type === "checkbox") {
|
} else if (
|
||||||
json[element.name] = element.checked;
|
inputElement.tagName.toLowerCase() === "input" &&
|
||||||
} else if (element.tagName.toLowerCase() === "ak-search-select") {
|
inputElement.type === "checkbox"
|
||||||
const select = element as unknown as SearchSelect<unknown>;
|
) {
|
||||||
|
json[element.name] = inputElement.checked;
|
||||||
|
} else if (inputElement.tagName.toLowerCase() === "ak-search-select") {
|
||||||
|
const select = inputElement as unknown as SearchSelect<unknown>;
|
||||||
let value: unknown;
|
let value: unknown;
|
||||||
try {
|
try {
|
||||||
value = select.toForm();
|
value = select.toForm();
|
||||||
|
@ -179,9 +189,7 @@ export class Form<T> extends AKElement {
|
||||||
}
|
}
|
||||||
json[element.name] = value;
|
json[element.name] = value;
|
||||||
} else {
|
} else {
|
||||||
for (let v = 0; v < values.length; v++) {
|
this.serializeFieldRecursive(inputElement, inputElement.value, json);
|
||||||
this.serializeFieldRecursive(element, values[v], json);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return json as unknown as T;
|
return json as unknown as T;
|
||||||
|
@ -213,11 +221,6 @@ export class Form<T> extends AKElement {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const form = this.shadowRoot?.querySelector<IronFormElement>("iron-form");
|
|
||||||
if (!form) {
|
|
||||||
console.warn("authentik/forms: failed to find iron-form");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return this.send(data)
|
return this.send(data)
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
showMessage({
|
showMessage({
|
||||||
|
@ -244,8 +247,12 @@ export class Form<T> extends AKElement {
|
||||||
throw errorMessage;
|
throw errorMessage;
|
||||||
}
|
}
|
||||||
// assign all input-related errors to their elements
|
// assign all input-related errors to their elements
|
||||||
const elements: HorizontalFormElement[] = form._getSubmittableElements();
|
const elements =
|
||||||
|
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
|
||||||
|
"ak-form-element-horizontal",
|
||||||
|
) || [];
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
|
element.requestUpdate();
|
||||||
const elementName = element.name;
|
const elementName = element.name;
|
||||||
if (!elementName) return;
|
if (!elementName) return;
|
||||||
if (camelToSnake(elementName) in errorMessage) {
|
if (camelToSnake(elementName) in errorMessage) {
|
||||||
|
@ -296,13 +303,7 @@ export class Form<T> extends AKElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderVisible(): TemplateResult {
|
renderVisible(): TemplateResult {
|
||||||
return html`<iron-form
|
return html` ${this.renderNonFieldErrors()} ${this.renderForm()}`;
|
||||||
@iron-form-presubmit=${(ev: Event) => {
|
|
||||||
this.submit(ev);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
${this.renderNonFieldErrors()} ${this.renderForm()}
|
|
||||||
</iron-form>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
|
|
|
@ -69,6 +69,10 @@ export class HorizontalFormElement extends AKElement {
|
||||||
@property()
|
@property()
|
||||||
name = "";
|
name = "";
|
||||||
|
|
||||||
|
firstUpdated(): void {
|
||||||
|
this.updated();
|
||||||
|
}
|
||||||
|
|
||||||
updated(): void {
|
updated(): void {
|
||||||
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => {
|
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach((input) => {
|
||||||
input.focus();
|
input.focus();
|
||||||
|
@ -89,7 +93,7 @@ export class HorizontalFormElement extends AKElement {
|
||||||
case "ak-chip-group":
|
case "ak-chip-group":
|
||||||
case "ak-search-select":
|
case "ak-search-select":
|
||||||
case "ak-radio":
|
case "ak-radio":
|
||||||
(input as HTMLInputElement).name = this.name;
|
input.setAttribute("name", this.name);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
|
@ -108,6 +112,7 @@ export class HorizontalFormElement extends AKElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
|
this.updated();
|
||||||
return html`<div class="pf-c-form__group">
|
return html`<div class="pf-c-form__group">
|
||||||
<div class="pf-c-form__group-label">
|
<div class="pf-c-form__group-label">
|
||||||
<label class="pf-c-form__label">
|
<label class="pf-c-form__label">
|
||||||
|
|
|
@ -70,6 +70,7 @@ export class SearchSelect<T> extends AKElement {
|
||||||
observer: IntersectionObserver;
|
observer: IntersectionObserver;
|
||||||
dropdownUID: string;
|
dropdownUID: string;
|
||||||
dropdownContainer: HTMLDivElement;
|
dropdownContainer: HTMLDivElement;
|
||||||
|
isFetchingData = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -103,13 +104,18 @@ export class SearchSelect<T> extends AKElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData(): void {
|
updateData(): void {
|
||||||
|
if (this.isFetchingData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isFetchingData = true;
|
||||||
this.fetchObjects(this.query).then((objects) => {
|
this.fetchObjects(this.query).then((objects) => {
|
||||||
this.objects = objects;
|
objects.forEach((obj) => {
|
||||||
this.objects.forEach((obj) => {
|
|
||||||
if (this.selected && this.selected(obj, this.objects || [])) {
|
if (this.selected && this.selected(obj, this.objects || [])) {
|
||||||
this.selectedObject = obj;
|
this.selectedObject = obj;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.objects = objects;
|
||||||
|
this.isFetchingData = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,9 +206,10 @@ export class SearchSelect<T> extends AKElement {
|
||||||
render(
|
render(
|
||||||
html`<div
|
html`<div
|
||||||
class="pf-c-dropdown pf-m-expanded"
|
class="pf-c-dropdown pf-m-expanded"
|
||||||
?hidden=${!this.open}
|
|
||||||
style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y +
|
style="position: fixed; inset: 0px auto auto 0px; z-index: 9999; transform: translate(${pos.x}px, ${pos.y +
|
||||||
this.offsetHeight}px); width: ${pos.width}px;"
|
this.offsetHeight}px); width: ${pos.width}px; ${this.open
|
||||||
|
? ""
|
||||||
|
: "visibility: hidden;"}"
|
||||||
>
|
>
|
||||||
<ul
|
<ul
|
||||||
class="pf-c-dropdown__menu pf-m-static"
|
class="pf-c-dropdown__menu pf-m-static"
|
||||||
|
@ -249,6 +256,14 @@ export class SearchSelect<T> extends AKElement {
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
this.renderMenu();
|
this.renderMenu();
|
||||||
|
let value = "";
|
||||||
|
if (!this.objects) {
|
||||||
|
value = t`Loading...`;
|
||||||
|
} else if (this.selectedObject) {
|
||||||
|
value = this.renderElement(this.selectedObject);
|
||||||
|
} else if (this.blankable) {
|
||||||
|
value = this.emptyOption;
|
||||||
|
}
|
||||||
return html`<div class="pf-c-select">
|
return html`<div class="pf-c-select">
|
||||||
<div class="pf-c-select__toggle pf-m-typeahead">
|
<div class="pf-c-select__toggle pf-m-typeahead">
|
||||||
<div class="pf-c-select__toggle-wrapper">
|
<div class="pf-c-select__toggle-wrapper">
|
||||||
|
@ -256,6 +271,7 @@ export class SearchSelect<T> extends AKElement {
|
||||||
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
class="pf-c-form-control pf-c-select__toggle-typeahead"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder=${this.placeholder}
|
placeholder=${this.placeholder}
|
||||||
|
spellcheck="false"
|
||||||
@input=${(ev: InputEvent) => {
|
@input=${(ev: InputEvent) => {
|
||||||
this.query = (ev.target as HTMLInputElement).value;
|
this.query = (ev.target as HTMLInputElement).value;
|
||||||
this.updateData();
|
this.updateData();
|
||||||
|
@ -285,11 +301,7 @@ export class SearchSelect<T> extends AKElement {
|
||||||
this.open = false;
|
this.open = false;
|
||||||
this.renderMenu();
|
this.renderMenu();
|
||||||
}}
|
}}
|
||||||
.value=${this.selectedObject
|
.value=${value}
|
||||||
? this.renderElement(this.selectedObject)
|
|
||||||
: this.blankable
|
|
||||||
? this.emptyOption
|
|
||||||
: ""}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -68,6 +68,7 @@ export class PasswordStage extends BaseStage<PasswordChallenge, PasswordChalleng
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
console.debug("authentik/stages/password: cleared focus timer");
|
console.debug("authentik/stages/password: cleared focus timer");
|
||||||
window.clearInterval(this.timer);
|
window.clearInterval(this.timer);
|
||||||
|
this.timer = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// @ts-ignore
|
|
||||||
window["polymerSkipLoadingFontRoboto"] = true;
|
|
||||||
import "construct-style-sheets-polyfill";
|
import "construct-style-sheets-polyfill";
|
||||||
import "@webcomponents/webcomponentsjs";
|
import "@webcomponents/webcomponentsjs";
|
||||||
import "lit/polyfill-support.js";
|
import "lit/polyfill-support.js";
|
||||||
|
|
|
@ -18,7 +18,7 @@ When authenticating with a flow, you'll get an authenticated Session cookie, tha
|
||||||
|
|
||||||
### API Token
|
### API Token
|
||||||
|
|
||||||
Superusers can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate.
|
Users can create tokens to authenticate as any user with a static key, which can optionally be expiring and auto-rotate.
|
||||||
|
|
||||||
### JWT Token
|
### JWT Token
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Models
|
||||||
|
|
||||||
|
Some models behave differently and allow for access to different API fields when created via blueprint.
|
||||||
|
|
||||||
|
### `authentik_core.token`
|
||||||
|
|
||||||
|
:::info
|
||||||
|
Requires authentik 2023.4
|
||||||
|
:::
|
||||||
|
|
||||||
|
Via the standard API, a token's key cannot be changed, it can only be rotated. This is to ensure a high entropy in it's key, and to prevent insecure data from being used. However, when provisioning tokens via a blueprint, it may be required to set a token to an existing value.
|
||||||
|
|
||||||
|
With blueprints, the field `key` can be set, to set the token's key to any value.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# [...]
|
||||||
|
- model: authentik_core.token
|
||||||
|
state: present
|
||||||
|
identifiers:
|
||||||
|
identifier: my-token
|
||||||
|
attrs:
|
||||||
|
key: this-should-be-a-long-value
|
||||||
|
user: !KeyOf my-user
|
||||||
|
intent: api
|
||||||
|
```
|
|
@ -16,6 +16,7 @@ module.exports = {
|
||||||
"blueprints/v1/structure",
|
"blueprints/v1/structure",
|
||||||
"blueprints/v1/tags",
|
"blueprints/v1/tags",
|
||||||
"blueprints/v1/example",
|
"blueprints/v1/example",
|
||||||
|
"blueprints/v1/models",
|
||||||
"blueprints/v1/meta",
|
"blueprints/v1/meta",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Reference in New Issue