sources/oauth: migrate to webcomponents
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
a085632b8e
commit
533a719914
|
@ -1,42 +0,0 @@
|
|||
{% extends "administration/base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
{% block content %}
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
{% block above_form %}
|
||||
<h1>
|
||||
{% blocktrans with object_type=object|verbose_name %}
|
||||
Disable {{ object_type }}
|
||||
{% endblocktrans %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-stack">
|
||||
<div class="pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<form action="" method="post" class="pf-c-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans with object_type=object|verbose_name name=object %}
|
||||
Are you sure you want to disable {{ object_type }} "{{ object }}"?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<div class="pf-c-form__actions">
|
||||
<input class="pf-c-button pf-m-danger" type="submit" value="{% trans 'Disable' %}" />
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -30,7 +30,7 @@
|
|||
<ak-spinner-button form="main-form">
|
||||
{% block action %}{% endblock %}
|
||||
</ak-spinner-button>
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
|
||||
<a class="pf-c-button pf-m-secondary" href="#/">{% trans "Cancel" %}</a>
|
||||
</footer>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
{% load static %}
|
||||
|
||||
{% block title %}
|
||||
authentik API Browser
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<script type="module" src="{% static 'dist/rapidoc-min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -107,7 +107,6 @@ router.register("core/applications", ApplicationViewSet)
|
|||
router.register("core/groups", GroupViewSet)
|
||||
router.register("core/users", UserViewSet)
|
||||
router.register("core/user_consent", UserConsentViewSet)
|
||||
router.register("core/source_user_connections_oauth", UserOAuthSourceConnectionViewSet)
|
||||
router.register("core/tokens", TokenViewSet)
|
||||
|
||||
router.register("outposts/outposts", OutpostViewSet)
|
||||
|
@ -129,6 +128,7 @@ router.register("events/transports", NotificationTransportViewSet)
|
|||
router.register("events/rules", NotificationRuleViewSet)
|
||||
|
||||
router.register("sources/all", SourceViewSet)
|
||||
router.register("sources/oauth_user_connections", UserOAuthSourceConnectionViewSet)
|
||||
router.register("sources/ldap", LDAPSourceViewSet)
|
||||
router.register("sources/saml", SAMLSourceViewSet)
|
||||
router.register("sources/oauth", OAuthSourceViewSet)
|
||||
|
|
|
@ -94,7 +94,7 @@ class SourceViewSet(
|
|||
if not policy_engine.passing:
|
||||
continue
|
||||
source_settings = source.ui_user_settings
|
||||
source_settings.initial_data["object_uid"] = str(source.pk)
|
||||
source_settings.initial_data["object_uid"] = source.slug
|
||||
if not source_settings.is_valid():
|
||||
LOGGER.warning(source_settings.errors)
|
||||
matching_sources.append(source_settings.validated_data)
|
||||
|
|
|
@ -25,9 +25,6 @@
|
|||
<h3>{% trans message %}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if 'back' in request.GET %}
|
||||
<a href="{% back %}" class="pf-c-button pf-m-primary pf-m-block">{% trans 'Back' %}</a>
|
||||
{% endif %}
|
||||
<a href="/" class="pf-c-button pf-m-primary pf-m-block">{% trans 'Go to home' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% load authentik_utils %}
|
||||
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
<div class="pf-c-content">
|
||||
{% block above_form %}
|
||||
<h1>
|
||||
{% blocktrans with object_type=object|verbose_name %}
|
||||
Delete {{ object_type }}
|
||||
{% endblocktrans %}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
<div class="pf-l-stack">
|
||||
<div class="pf-l-stack__item">
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-c-card__body">
|
||||
<form id="delete-form" action="" method="post" class="pf-c-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans with object_type=object|verbose_name name=object %}
|
||||
Are you sure you want to delete {{ object_type }} "{{ object }}"?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<input type="hidden" name="confirmdelete" value="yes">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<input class="pf-c-button pf-m-danger" type="submit" form="delete-form" value="{% trans 'Delete' %}" />
|
||||
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Back" %}</a>
|
||||
</footer>
|
|
@ -1,26 +0,0 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
<ak-message-container></ak-message-container>
|
||||
|
||||
<header class="pf-c-login__main-header">
|
||||
<h1 class="pf-c-title pf-m-3xl">
|
||||
{% block card_title %}
|
||||
{% trans title %}
|
||||
{% endblock %}
|
||||
</h1>
|
||||
</header>
|
||||
<div class="pf-c-login__main-body">
|
||||
{% block card %}
|
||||
<form method="POST" class="pf-c-form">
|
||||
{% include 'partials/form.html' %}
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button class="pf-c-button pf-m-primary pf-m-block" type="submit">Log in</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
</div>
|
||||
<footer class="pf-c-login__main-footer">
|
||||
<ul class="pf-c-login__main-footer-links">
|
||||
</ul>
|
||||
</footer>
|
|
@ -1,73 +0,0 @@
|
|||
{% load authentik_utils %}
|
||||
{% load i18n %}
|
||||
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div class="pf-c-form__group">
|
||||
<p class="pf-c-form__helper-text pf-m-error">
|
||||
{{ form.non_field_errors }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for field in form %}
|
||||
{% if field.field.widget|fieldtype == 'HiddenInput' %}
|
||||
{{ field }}
|
||||
{% else %}
|
||||
<div class="pf-c-form__group">
|
||||
{% if field.field.widget|fieldtype == 'RadioSelect' %}
|
||||
<label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %}
|
||||
for="{{ field.name }}-{{ forloop.counter0 }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
{% for c in field %}
|
||||
<div class="radio col-sm-10">
|
||||
<input type="radio" id="{{ field.name }}-{{ forloop.counter0 }}"
|
||||
name="{% if wizard %}{{ wizard.steps.current }}-{% endif %}{{ field.name }}" value="{{ c.data.value }}"
|
||||
{% if c.data.selected %} checked {% endif %}>
|
||||
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">{{ c.choice_label }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% elif field.field.widget|fieldtype == 'Select' %}
|
||||
<label class="pf-c-form__label" {% if field.field.required %}class="required" {% endif %}
|
||||
for="{{ field.name }}-{{ forloop.counter0 }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
<div class="select col-sm-10">
|
||||
{{ field }}
|
||||
</div>
|
||||
{% if field.help_text %}
|
||||
<span>
|
||||
{{ field.help_text }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% elif field.field.widget|fieldtype == 'CheckboxInput' %}
|
||||
<label class="checkbox-label">
|
||||
{{ field }} {{ field.label }}
|
||||
</label>
|
||||
{% if field.help_text %}
|
||||
<span>
|
||||
{{ field.help_text }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
|
||||
<span class="pf-c-form__label-text">{{ field.label }}</span>
|
||||
{% if field.field.required %}
|
||||
<span class="pf-c-form__label-required" aria-hidden="true">*</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{{ field|css_class:'pf-c-form-control' }}
|
||||
{% if field.help_text %}
|
||||
<span>
|
||||
{{ field.help_text }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<p class="pf-c-form__helper-text pf-m-error">
|
||||
{{ error }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
|
@ -87,7 +87,7 @@ class StageViewSet(
|
|||
user_settings = stage.ui_user_settings
|
||||
if not user_settings:
|
||||
continue
|
||||
user_settings.initial_data["object_uid"] = stage.pk
|
||||
user_settings.initial_data["object_uid"] = str(stage.pk)
|
||||
if not user_settings.is_valid():
|
||||
LOGGER.warning(user_settings.errors)
|
||||
matching_stages.append(user_settings.initial_data)
|
||||
|
|
|
@ -2,32 +2,12 @@
|
|||
|
||||
from django import template
|
||||
from django.db.models import Model
|
||||
from django.template import Context
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.utils.urls import is_url_absolute
|
||||
|
||||
register = template.Library()
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def back(context: Context) -> str:
|
||||
"""Return a link back (either from GET parameter or referer."""
|
||||
if "request" not in context:
|
||||
return ""
|
||||
request = context.get("request")
|
||||
url = ""
|
||||
if "HTTP_REFERER" in request.META:
|
||||
url = request.META.get("HTTP_REFERER")
|
||||
if "back" in request.GET:
|
||||
url = request.GET.get("back")
|
||||
|
||||
if not is_url_absolute(url):
|
||||
return url
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter("fieldtype")
|
||||
def fieldtype(field):
|
||||
"""Return classname"""
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
{% block card %}
|
||||
<form method="POST" class="pf-c-form">
|
||||
{% csrf_token %}
|
||||
{% include 'partials/form.html' %}
|
||||
<div class="pf-c-form__group">
|
||||
<p>
|
||||
<i class="pf-icon pf-icon-error-circle-o"></i>
|
||||
|
@ -58,8 +57,5 @@
|
|||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if 'back' in request.GET %}
|
||||
<a href="{% back %}" class="btn btn-primary btn-block btn-lg">{% trans 'Back' %}</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,10 +11,10 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer):
|
|||
class Meta:
|
||||
model = UserOAuthSourceConnection
|
||||
fields = [
|
||||
"pk",
|
||||
"user",
|
||||
"source",
|
||||
"identifier",
|
||||
"access_token",
|
||||
]
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@ class UserOAuthSourceConnectionViewSet(ModelViewSet):
|
|||
|
||||
queryset = UserOAuthSourceConnection.objects.all()
|
||||
serializer_class = UserOAuthSourceConnectionSerializer
|
||||
filterset_fields = ["source"]
|
||||
|
||||
def get_queryset(self):
|
||||
if not self.request:
|
||||
|
|
|
@ -9,8 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
|||
from rest_framework.serializers import Serializer
|
||||
|
||||
from authentik.core.models import Source, UserSourceConnection
|
||||
from authentik.core.types import UILoginButton
|
||||
from authentik.flows.challenge import Challenge, ChallengeTypes
|
||||
from authentik.core.types import UILoginButton, UserSettingSerializer
|
||||
|
||||
|
||||
class OAuthSource(Source):
|
||||
|
@ -67,13 +66,11 @@ class OAuthSource(Source):
|
|||
)
|
||||
|
||||
@property
|
||||
def ui_user_settings(self) -> Optional[Challenge]:
|
||||
view_name = "authentik_sources_oauth:oauth-client-user"
|
||||
return Challenge(
|
||||
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
|
||||
return UserSettingSerializer(
|
||||
data={
|
||||
"type": ChallengeTypes.shell.value,
|
||||
"title": self.name,
|
||||
"component": reverse(view_name, kwargs={"source_slug": self.slug}),
|
||||
"title": f"OAuth {self.name}",
|
||||
"component": "ak-user-settings-source-oauth",
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
{% load i18n %}
|
||||
|
||||
<div class="pf-c-card">
|
||||
<div class="pf-pf-c-card__title">
|
||||
{% blocktrans with source_name=source.name %}
|
||||
Source {{ source_name }}
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
{% if connections.exists %}
|
||||
<p>{% trans 'Connected.' %}</p>
|
||||
<a class="pf-c-button pf-m-danger ak-root-link"
|
||||
href="{% url 'authentik_sources_oauth:oauth-client-disconnect' source_slug=source.slug %}">
|
||||
{% trans 'Disconnect' %}
|
||||
</a>
|
||||
{% else %}
|
||||
<p>Not connected.</p>
|
||||
<a class="pf-c-button pf-m-primary ak-root-link"
|
||||
href="{% url 'authentik_sources_oauth:oauth-client-login' source_slug=source.slug %}">
|
||||
{% trans 'Connect' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
|
@ -4,7 +4,6 @@ from django.urls import path
|
|||
|
||||
from authentik.sources.oauth.types.manager import RequestKind
|
||||
from authentik.sources.oauth.views.dispatcher import DispatcherView
|
||||
from authentik.sources.oauth.views.user import DisconnectView, UserSettingsView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
|
@ -17,14 +16,4 @@ urlpatterns = [
|
|||
DispatcherView.as_view(kind=RequestKind.callback),
|
||||
name="oauth-client-callback",
|
||||
),
|
||||
path(
|
||||
"user/<slug:source_slug>/",
|
||||
UserSettingsView.as_view(),
|
||||
name="oauth-client-user",
|
||||
),
|
||||
path(
|
||||
"user/<slug:source_slug>/disconnect/",
|
||||
DisconnectView.as_view(),
|
||||
name="oauth-client-disconnect",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
"""authentik oauth_client user views"""
|
||||
from typing import Optional
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from authentik.sources.oauth.models import OAuthSource, UserOAuthSourceConnection
|
||||
|
||||
|
||||
class UserSettingsView(LoginRequiredMixin, TemplateView):
|
||||
"""Show user current connection state"""
|
||||
|
||||
template_name = "oauth_client/user.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
source = get_object_or_404(OAuthSource, slug=self.kwargs.get("source_slug"))
|
||||
connections = UserOAuthSourceConnection.objects.filter(
|
||||
user=self.request.user, source=source
|
||||
)
|
||||
kwargs["source"] = source
|
||||
kwargs["connections"] = connections
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
|
||||
class DisconnectView(LoginRequiredMixin, View):
|
||||
"""Delete connection with source"""
|
||||
|
||||
source: Optional[OAuthSource] = None
|
||||
aas: Optional[UserOAuthSourceConnection] = None
|
||||
|
||||
def dispatch(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
self.source = get_object_or_404(OAuthSource, slug=source_slug)
|
||||
self.aas = get_object_or_404(
|
||||
UserOAuthSourceConnection, source=self.source, user=request.user
|
||||
)
|
||||
return super().dispatch(request, source_slug)
|
||||
|
||||
def post(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
"""Delete connection object"""
|
||||
if "confirmdelete" in request.POST:
|
||||
# User confirmed deletion
|
||||
self.aas.delete()
|
||||
messages.success(request, _("Connection successfully deleted"))
|
||||
return redirect(
|
||||
reverse(
|
||||
"authentik_sources_oauth:oauth-client-user",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
)
|
||||
)
|
||||
return self.get(request, source_slug)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse:
|
||||
"""Show delete form"""
|
||||
return render(
|
||||
request,
|
||||
"generic/delete.html",
|
||||
{
|
||||
"object": self.source,
|
||||
"delete_url": reverse(
|
||||
"authentik_sources_oauth:oauth-client-disconnect",
|
||||
kwargs={"source_slug": self.source.slug},
|
||||
),
|
||||
},
|
||||
)
|
|
@ -9,6 +9,7 @@ from authentik.events.models import Event
|
|||
@receiver(pre_delete, sender=StaticDevice)
|
||||
# pylint: disable=unused-argument
|
||||
def pre_delete_event(sender, instance: StaticDevice, **_):
|
||||
"""Create event before deleting Static Devices"""
|
||||
# Create event with email notification
|
||||
event = Event.new(
|
||||
"static_authenticator_disable", message="User disabled Static OTP Tokens."
|
||||
|
|
|
@ -9,6 +9,7 @@ from authentik.events.models import Event
|
|||
@receiver(pre_delete, sender=TOTPDevice)
|
||||
# pylint: disable=unused-argument
|
||||
def pre_delete_event(sender, instance: TOTPDevice, **_):
|
||||
"""Create event before deleting TOTP Devices"""
|
||||
# Create event with email notification
|
||||
event = Event.new("totp_disable", message="User disabled Time-based OTP.")
|
||||
event.set_user(instance.user)
|
||||
|
|
333
swagger.yaml
333
swagger.yaml
|
@ -1188,147 +1188,6 @@ paths:
|
|||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
/core/source_user_connections_oauth/:
|
||||
get:
|
||||
operationId: core_source_user_connections_oauth_list
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
required: false
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: Page Index
|
||||
required: false
|
||||
type: integer
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Page Size
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
required:
|
||||
- results
|
||||
- pagination
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
required:
|
||||
- next
|
||||
- previous
|
||||
- count
|
||||
- current
|
||||
- total_pages
|
||||
- start_index
|
||||
- end_index
|
||||
type: object
|
||||
properties:
|
||||
next:
|
||||
type: number
|
||||
previous:
|
||||
type: number
|
||||
count:
|
||||
type: number
|
||||
current:
|
||||
type: number
|
||||
total_pages:
|
||||
type: number
|
||||
start_index:
|
||||
type: number
|
||||
end_index:
|
||||
type: number
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- core
|
||||
post:
|
||||
operationId: core_source_user_connections_oauth_create
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- core
|
||||
parameters: []
|
||||
/core/source_user_connections_oauth/{id}/:
|
||||
get:
|
||||
operationId: core_source_user_connections_oauth_read
|
||||
description: Source Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- core
|
||||
put:
|
||||
operationId: core_source_user_connections_oauth_update
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- core
|
||||
patch:
|
||||
operationId: core_source_user_connections_oauth_partial_update
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- core
|
||||
delete:
|
||||
operationId: core_source_user_connections_oauth_delete
|
||||
description: Source Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
tags:
|
||||
- core
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this User OAuth Source Connection.
|
||||
required: true
|
||||
type: integer
|
||||
/core/tokens/:
|
||||
get:
|
||||
operationId: core_tokens_list
|
||||
|
@ -7551,6 +7410,152 @@ paths:
|
|||
type: string
|
||||
format: slug
|
||||
pattern: ^[-a-zA-Z0-9_]+$
|
||||
/sources/oauth_user_connections/:
|
||||
get:
|
||||
operationId: sources_oauth_user_connections_list
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: source
|
||||
in: query
|
||||
description: ''
|
||||
required: false
|
||||
type: string
|
||||
- name: ordering
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
required: false
|
||||
type: string
|
||||
- name: search
|
||||
in: query
|
||||
description: A search term.
|
||||
required: false
|
||||
type: string
|
||||
- name: page
|
||||
in: query
|
||||
description: Page Index
|
||||
required: false
|
||||
type: integer
|
||||
- name: page_size
|
||||
in: query
|
||||
description: Page Size
|
||||
required: false
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
required:
|
||||
- results
|
||||
- pagination
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
required:
|
||||
- next
|
||||
- previous
|
||||
- count
|
||||
- current
|
||||
- total_pages
|
||||
- start_index
|
||||
- end_index
|
||||
type: object
|
||||
properties:
|
||||
next:
|
||||
type: number
|
||||
previous:
|
||||
type: number
|
||||
count:
|
||||
type: number
|
||||
current:
|
||||
type: number
|
||||
total_pages:
|
||||
type: number
|
||||
start_index:
|
||||
type: number
|
||||
end_index:
|
||||
type: number
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- sources
|
||||
post:
|
||||
operationId: sources_oauth_user_connections_create
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'201':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- sources
|
||||
parameters: []
|
||||
/sources/oauth_user_connections/{id}/:
|
||||
get:
|
||||
operationId: sources_oauth_user_connections_read
|
||||
description: Source Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- sources
|
||||
put:
|
||||
operationId: sources_oauth_user_connections_update
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- sources
|
||||
patch:
|
||||
operationId: sources_oauth_user_connections_partial_update
|
||||
description: Source Viewset
|
||||
parameters:
|
||||
- name: data
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
responses:
|
||||
'200':
|
||||
description: ''
|
||||
schema:
|
||||
$ref: '#/definitions/UserOAuthSourceConnection'
|
||||
tags:
|
||||
- sources
|
||||
delete:
|
||||
operationId: sources_oauth_user_connections_delete
|
||||
description: Source Viewset
|
||||
parameters: []
|
||||
responses:
|
||||
'204':
|
||||
description: ''
|
||||
tags:
|
||||
- sources
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
description: A unique integer value identifying this User OAuth Source Connection.
|
||||
required: true
|
||||
type: integer
|
||||
/sources/saml/:
|
||||
get:
|
||||
operationId: sources_saml_list
|
||||
|
@ -10963,29 +10968,6 @@ definitions:
|
|||
attributes:
|
||||
title: Attributes
|
||||
type: object
|
||||
UserOAuthSourceConnection:
|
||||
description: OAuth Source Serializer
|
||||
required:
|
||||
- user
|
||||
- source
|
||||
- identifier
|
||||
type: object
|
||||
properties:
|
||||
user:
|
||||
title: User
|
||||
type: integer
|
||||
source:
|
||||
title: Source
|
||||
type: string
|
||||
identifier:
|
||||
title: Identifier
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
access_token:
|
||||
title: Access token
|
||||
type: string
|
||||
x-nullable: true
|
||||
User:
|
||||
title: User
|
||||
description: User Serializer
|
||||
|
@ -13742,6 +13724,29 @@ definitions:
|
|||
title: Callback url
|
||||
type: string
|
||||
readOnly: true
|
||||
UserOAuthSourceConnection:
|
||||
description: OAuth Source Serializer
|
||||
required:
|
||||
- user
|
||||
- source
|
||||
- identifier
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
title: ID
|
||||
type: integer
|
||||
readOnly: true
|
||||
user:
|
||||
title: User
|
||||
type: integer
|
||||
source:
|
||||
title: Source
|
||||
type: string
|
||||
identifier:
|
||||
title: Identifier
|
||||
type: string
|
||||
maxLength: 255
|
||||
minLength: 1
|
||||
SAMLSource:
|
||||
description: SAMLSource Serializer
|
||||
required:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Test Enroll flow"""
|
||||
from sys import platform
|
||||
from time import sleep
|
||||
from typing import Any, Optional
|
||||
from unittest.case import skipUnless
|
||||
|
||||
|
@ -190,6 +191,7 @@ class TestFlowsEnroll(SeleniumTestCase):
|
|||
self.driver.close()
|
||||
self.driver.switch_to.window(self.driver.window_handles[0])
|
||||
|
||||
sleep(2)
|
||||
# We're now logged in
|
||||
wait = WebDriverWait(
|
||||
self.get_shadow_root("ak-interface-admin"), self.wait_timeout
|
||||
|
|
|
@ -94,6 +94,9 @@ export class AppURLManager {
|
|||
static sourceSAML(slug: string, rest: string): string {
|
||||
return `/source/saml/${slug}/${rest}`;
|
||||
}
|
||||
static sourceOAuth(slug: string, action: string): string {
|
||||
return `/source/oauth/${action}/${slug}/`;
|
||||
}
|
||||
static providerSAML(rest: string): string {
|
||||
return `/application/saml/${rest}`;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import "./settings/UserSettingsAuthenticatorTOTP";
|
|||
import "./settings/UserSettingsAuthenticatorStatic";
|
||||
import "./settings/UserSettingsAuthenticatorWebAuthnDevices";
|
||||
import "./settings/UserSettingsPassword";
|
||||
import "./settings/SourceSettingsOAuth";
|
||||
|
||||
@customElement("ak-user-settings")
|
||||
export class UserSettingsPage extends LitElement {
|
||||
|
@ -35,16 +36,16 @@ export class UserSettingsPage extends LitElement {
|
|||
renderStageSettings(stage: UserSetting): TemplateResult {
|
||||
switch (stage.component) {
|
||||
case "ak-user-settings-authenticator-webauthn":
|
||||
return html`<ak-user-settings-authenticator-webauthn stageId=${stage.objectUid}>
|
||||
return html`<ak-user-settings-authenticator-webauthn objectId=${stage.objectUid}>
|
||||
</ak-user-settings-authenticator-webauthn>`;
|
||||
case "ak-user-settings-password":
|
||||
return html`<ak-user-settings-password stageId=${stage.objectUid}>
|
||||
return html`<ak-user-settings-password objectId=${stage.objectUid}>
|
||||
</ak-user-settings-password>`;
|
||||
case "ak-user-settings-authenticator-totp":
|
||||
return html`<ak-user-settings-authenticator-totp stageId=${stage.objectUid}>
|
||||
return html`<ak-user-settings-authenticator-totp objectId=${stage.objectUid}>
|
||||
</ak-user-settings-authenticator-totp>`;
|
||||
case "ak-user-settings-authenticator-static":
|
||||
return html`<ak-user-settings-authenticator-static stageId=${stage.objectUid}>
|
||||
return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}>
|
||||
</ak-user-settings-authenticator-static>`;
|
||||
default:
|
||||
return html`<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
|
@ -57,6 +58,22 @@ export class UserSettingsPage extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
renderSourceSettings(source: UserSetting): TemplateResult {
|
||||
switch (source.component) {
|
||||
case "ak-user-settings-source-oauth":
|
||||
return html`<ak-user-settings-source-oauth objectId=${source.objectUid}>
|
||||
</ak-user-settings-source-oauth>`;
|
||||
default:
|
||||
return html`<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<ak-site-shell url="${ifDefined(source.component)}">
|
||||
<div slot="body"></div>
|
||||
</ak-site-shell>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-page">
|
||||
<main role="main" class="pf-c-page__main" tabindex="-1">
|
||||
|
@ -89,17 +106,10 @@ export class UserSettingsPage extends LitElement {
|
|||
</section>`;
|
||||
});
|
||||
}))}
|
||||
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettings({}).then((sources) => {
|
||||
return sources.map((source) => {
|
||||
// TODO: Check for non-shell sources
|
||||
return html`<section slot="page-${source.objectUid}" data-tab-title="${ifDefined(source.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
<div class="pf-u-display-flex pf-u-justify-content-center">
|
||||
<div class="pf-u-w-75">
|
||||
<ak-site-shell url="${ifDefined(source.component)}">
|
||||
<div slot="body"></div>
|
||||
</ak-site-shell>
|
||||
</div>
|
||||
</div>
|
||||
${until(new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettings({}).then((source) => {
|
||||
return source.map((stage) => {
|
||||
return html`<section slot="page-${stage.objectUid}" data-tab-title="${ifDefined(stage.title)}" class="pf-c-page__main-section pf-m-no-padding-mobile">
|
||||
${this.renderSourceSettings(stage)}
|
||||
</section>`;
|
||||
});
|
||||
}))}
|
||||
|
|
50
web/src/pages/users/settings/SourceSettingsOAuth.ts
Normal file
50
web/src/pages/users/settings/SourceSettingsOAuth.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { customElement, html, TemplateResult } from "lit-element";
|
||||
import { BaseUserSettings } from "./BaseUserSettings";
|
||||
import { OAuthSource, SourcesApi } from "authentik-api";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import { gettext } from "django";
|
||||
import { AppURLManager } from "../../../api/legacy";
|
||||
|
||||
@customElement("ak-user-settings-source-oauth")
|
||||
export class SourceSettingsOAuth extends BaseUserSettings {
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthRead({
|
||||
slug: this.objectId
|
||||
}).then((source) => {
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-pf-c-card__title">
|
||||
${gettext(`Source ${source.name}`)}
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
${this.renderInner(source)}
|
||||
</div>
|
||||
</div>`;
|
||||
}))}`;
|
||||
}
|
||||
|
||||
renderInner(source: OAuthSource): TemplateResult {
|
||||
return html`${until(new SourcesApi(DEFAULT_CONFIG).sourcesOauthUserConnectionsList({
|
||||
source: this.objectId
|
||||
}).then((connection) => {
|
||||
if (connection.results.length > 0) {
|
||||
return html`<p>${gettext("Connected.")}</p>
|
||||
<button class="pf-c-button pf-m-danger"
|
||||
@click=${() => {
|
||||
return new SourcesApi(DEFAULT_CONFIG).sourcesOauthUserConnectionsDelete({
|
||||
id: connection.results[0].pk || 0
|
||||
});
|
||||
}}>
|
||||
${gettext("Disconnect")}
|
||||
</button>`;
|
||||
}
|
||||
return html`<p>${gettext("Not connected.")}</p>
|
||||
<a class="pf-c-button pf-m-primary"
|
||||
href=${AppURLManager.sourceOAuth(source.slug, "login")}>
|
||||
${gettext("Connect")}
|
||||
</a>`;
|
||||
}))}`;
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue