web/admin: filter out service accounts by default

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-04 18:14:25 +02:00
parent a335ca0895
commit 0d370ef0a9
6 changed files with 82 additions and 8 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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.

View File

@ -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();

View File

@ -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`&nbsp;
<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>`;
}
} }