web/admin: filter out service accounts by default
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
a335ca0895
commit
0d370ef0a9
8
Makefile
8
Makefile
|
@ -22,7 +22,7 @@ lint:
|
||||||
bandit -r authentik tests lifecycle -x node_modules
|
bandit -r authentik tests lifecycle -x node_modules
|
||||||
pylint authentik tests lifecycle
|
pylint authentik tests lifecycle
|
||||||
|
|
||||||
gen: coverage
|
gen:
|
||||||
./manage.py generate_swagger -o swagger.yaml -f yaml
|
./manage.py generate_swagger -o swagger.yaml -f yaml
|
||||||
|
|
||||||
local-stack:
|
local-stack:
|
||||||
|
@ -31,7 +31,5 @@ local-stack:
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
docker-compose run --rm server migrate
|
docker-compose run --rm server migrate
|
||||||
|
|
||||||
build-static:
|
run:
|
||||||
docker-compose -f scripts/ci.docker-compose.yml up -d
|
go run -v cmd/server/main.go
|
||||||
docker build -t beryju/authentik-static -f static.Dockerfile --network=scripts_default .
|
|
||||||
docker-compose -f scripts/ci.docker-compose.yml down -v
|
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
"""User API Views"""
|
"""User API Views"""
|
||||||
|
from json import loads
|
||||||
|
|
||||||
from django.http.response import Http404
|
from django.http.response import Http404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.http import urlencode
|
from django.utils.http import urlencode
|
||||||
|
from django_filters.filters import CharFilter
|
||||||
|
from django_filters.filterset import FilterSet
|
||||||
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
|
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
|
||||||
from guardian.utils import get_anonymous_user
|
from guardian.utils import get_anonymous_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, JSONField, SerializerMethodField
|
from rest_framework.fields import CharField, JSONField, SerializerMethodField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import BooleanField, ModelSerializer
|
from rest_framework.serializers import BooleanField, ModelSerializer, ValidationError
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
from authentik.admin.api.metrics import CoordinateSerializer, get_events_per_1h
|
||||||
|
@ -84,13 +88,42 @@ class UserMetricsSerializer(PassiveSerializer):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UsersFilter(FilterSet):
|
||||||
|
"""Filter for users"""
|
||||||
|
|
||||||
|
attributes = CharFilter(
|
||||||
|
field_name="attributes",
|
||||||
|
lookup_expr="",
|
||||||
|
label="Attributes",
|
||||||
|
method="filter_attributes",
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def filter_attributes(self, queryset, name, value):
|
||||||
|
"""Filter attributes by query args"""
|
||||||
|
try:
|
||||||
|
value = loads(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValidationError(detail="filter: failed to parse JSON")
|
||||||
|
if not isinstance(value, dict):
|
||||||
|
raise ValidationError(detail="filter: value must be key:value mapping")
|
||||||
|
qs = {}
|
||||||
|
for key, _value in value.items():
|
||||||
|
qs[f"attributes__{key}"] = _value
|
||||||
|
return queryset.filter(**qs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ["username", "name", "is_active", "attributes"]
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(ModelViewSet):
|
class UserViewSet(ModelViewSet):
|
||||||
"""User Viewset"""
|
"""User Viewset"""
|
||||||
|
|
||||||
queryset = User.objects.none()
|
queryset = User.objects.none()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
search_fields = ["username", "name", "is_active"]
|
search_fields = ["username", "name", "is_active"]
|
||||||
filterset_fields = ["username", "name", "is_active"]
|
filterset_class = UsersFilter
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
return User.objects.all().exclude(pk=get_anonymous_user().pk)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
@ -9,5 +10,11 @@ func (ws *WebServer) configureProxy() {
|
||||||
// Reverse proxy to the application server
|
// Reverse proxy to the application server
|
||||||
u, _ := url.Parse("http://localhost:8000")
|
u, _ := url.Parse("http://localhost:8000")
|
||||||
rp := httputil.NewSingleHostReverseProxy(u)
|
rp := httputil.NewSingleHostReverseProxy(u)
|
||||||
|
rp.ErrorHandler = ws.proxyErrorHandler
|
||||||
ws.m.PathPrefix("/").Handler(rp)
|
ws.m.PathPrefix("/").Handler(rp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebServer) proxyErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
ws.log.WithError(err).Warning("proxy error")
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
}
|
||||||
|
|
|
@ -1994,6 +1994,11 @@ paths:
|
||||||
description: ''
|
description: ''
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
- name: attributes
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
- name: ordering
|
- name: ordering
|
||||||
in: query
|
in: query
|
||||||
description: Which field to use when ordering the results.
|
description: Which field to use when ordering the results.
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class PlexSourceForm extends Form<PlexSource> {
|
||||||
};
|
};
|
||||||
|
|
||||||
async doAuth(): Promise<void> {
|
async doAuth(): Promise<void> {
|
||||||
const authInfo = await PlexAPIClient.getPin(this.source?.clientId);
|
const authInfo = await PlexAPIClient.getPin(this.source?.clientId || "");
|
||||||
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
|
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
|
||||||
PlexAPIClient.pinPoll(this.source?.clientId || "", authInfo.pin.id).then(token => {
|
PlexAPIClient.pinPoll(this.source?.clientId || "", authInfo.pin.id).then(token => {
|
||||||
authWindow?.close();
|
authWindow?.close();
|
||||||
|
|
|
@ -35,12 +35,18 @@ export class UserListPage extends TablePage<User> {
|
||||||
@property()
|
@property()
|
||||||
order = "last_login";
|
order = "last_login";
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
hideServiceAccounts = true;
|
||||||
|
|
||||||
apiEndpoint(page: number): Promise<AKResponse<User>> {
|
apiEndpoint(page: number): Promise<AKResponse<User>> {
|
||||||
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
|
||||||
ordering: this.order,
|
ordering: this.order,
|
||||||
page: page,
|
page: page,
|
||||||
pageSize: PAGE_SIZE,
|
pageSize: PAGE_SIZE,
|
||||||
search: this.search || "",
|
search: this.search || "",
|
||||||
|
attributes: this.hideServiceAccounts ? JSON.stringify({
|
||||||
|
"goauthentik.io/user/service-account__isnull": "true"
|
||||||
|
}) : undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,4 +169,29 @@ export class UserListPage extends TablePage<User> {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderToolbarAfter(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="pf-c-toolbar__group pf-m-filter-group">
|
||||||
|
<div class="pf-c-toolbar__item pf-m-search-filter">
|
||||||
|
<div class="pf-c-input-group">
|
||||||
|
<div class="pf-c-check">
|
||||||
|
<input class="pf-c-check__input"
|
||||||
|
type="checkbox"
|
||||||
|
id="hide-service-accounts"
|
||||||
|
name="hide-service-accounts"
|
||||||
|
?checked=${this.hideServiceAccounts}
|
||||||
|
@change=${() => {
|
||||||
|
this.hideServiceAccounts = !this.hideServiceAccounts;
|
||||||
|
this.page = 1;
|
||||||
|
this.fetch();
|
||||||
|
}} />
|
||||||
|
<label class="pf-c-check__label" for="hide-service-accounts">
|
||||||
|
${t`Hide service-accounts`}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue