core: add user flag to prevent users from changing their usernames

closes #1590

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-10-20 16:01:41 +02:00
parent 6a95de4e8a
commit 61fab497cf
9 changed files with 63 additions and 22 deletions

View file

@ -45,6 +45,7 @@ from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import LinkSerializer, PassiveSerializer, is_dict
from authentik.core.middleware import SESSION_IMPERSONATE_ORIGINAL_USER, SESSION_IMPERSONATE_USER
from authentik.core.models import (
USER_ATTRIBUTE_CHANGE_USERNAME,
USER_ATTRIBUTE_SA,
USER_ATTRIBUTE_TOKEN_EXPIRING,
Group,
@ -113,14 +114,22 @@ class UserSelfSerializer(ModelSerializer):
)
)
)
def get_groups(self, user: User):
def get_groups(self, _: User):
"""Return only the group names a user is member of"""
for group in user.ak_groups.all():
for group in self.instance.ak_groups.all():
yield {
"name": group.name,
"pk": group.pk,
}
def validate_username(self, username: str):
"""Check if the user is allowed to change their username"""
if self.instance.group_attributes().get(USER_ATTRIBUTE_CHANGE_USERNAME, True):
return username
if username != self.instance.username:
raise ValidationError("Not allowed to change username.")
return username
class Meta:
model = User
@ -337,7 +346,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
# since it caches the full object
if SESSION_IMPERSONATE_USER in request.session:
request.session[SESSION_IMPERSONATE_USER] = new_user
serializer = SessionUserSerializer(data={"user": UserSelfSerializer(request.user).data})
serializer = SessionUserSerializer(data={"user": data.data})
serializer.is_valid()
return Response(serializer.data)

View file

@ -39,6 +39,7 @@ USER_ATTRIBUTE_DEBUG = "goauthentik.io/user/debug"
USER_ATTRIBUTE_SA = "goauthentik.io/user/service-account"
USER_ATTRIBUTE_SOURCES = "goauthentik.io/user/sources"
USER_ATTRIBUTE_TOKEN_EXPIRING = "goauthentik.io/user/token-expires" # nosec
USER_ATTRIBUTE_CHANGE_USERNAME = "goauthentik.io/user/can-change-username" # nosec
USER_ATTRIBUTE_CAN_OVERRIDE_IP = "goauthentik.io/user/override-ips"
GRAVATAR_URL = "https://secure.gravatar.com"

View file

@ -2,7 +2,7 @@
from django.urls.base import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.core.models import USER_ATTRIBUTE_CHANGE_USERNAME, User
from authentik.flows.models import Flow, FlowDesignation
from authentik.stages.email.models import EmailStage
from authentik.tenants.models import Tenant
@ -15,6 +15,24 @@ class TestUsersAPI(APITestCase):
self.admin = User.objects.get(username="akadmin")
self.user = User.objects.create(username="test-user")
def test_update_self(self):
"""Test update_self"""
self.client.force_login(self.admin)
response = self.client.put(
reverse("authentik_api:user-update-self"), data={"username": "foo", "name": "foo"}
)
self.assertEqual(response.status_code, 200)
def test_update_self_username_denied(self):
"""Test update_self"""
self.admin.attributes[USER_ATTRIBUTE_CHANGE_USERNAME] = False
self.admin.save()
self.client.force_login(self.admin)
response = self.client.put(
reverse("authentik_api:user-update-self"), data={"username": "foo", "name": "foo"}
)
self.assertEqual(response.status_code, 400)
def test_metrics(self):
"""Test user's metrics"""
self.client.force_login(self.admin)

View file

@ -38,3 +38,4 @@ class UserOAuthSourceConnectionViewSet(
filterset_fields = ["source__slug"]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
ordering = ["source__slug"]

View file

@ -32,7 +32,7 @@ return ak_is_group_member(request.user, name="test_group")
Fetch a user matching `**filters`.
Returns "None" if no user was found, otherwise [User](/docs/expressions/reference/user-object)
Returns "None" if no user was found, otherwise [User](/docs/user-group/user)
Example:

View file

@ -53,7 +53,7 @@ import Objects from '../expressions/_objects.md'
<Objects />
- `request`: A PolicyRequest object, which has the following properties:
- `request.user`: The current user, against which the policy is applied. See [User](../expressions/reference/user-object.md)
- `request.user`: The current user, against which the policy is applied. See [User](../user-group/user.md#object-attributes)
- `request.http_request`: The Django HTTP Request. See ([Django documentation](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- `request.obj`: A Django Model instance. This is only set if the policy is ran against an object.
- `request.context`: A dictionary with dynamic data. This depends on the origin of the execution.
@ -75,7 +75,7 @@ This includes the following:
- `context['prompt_data']`: Data which has been saved from a prompt stage or an external source.
- `context['application']`: The application the user is in the process of authorizing.
- `context['pending_user']`: The currently pending user, see [User](/docs/expressions/reference/user-object)
- `context['pending_user']`: The currently pending user, see [User](../user-group/user.md#object-attributes)
- `context['auth_method']`: Authentication method set (this value is set by password stages)
Depending on method, `context['auth_method_args']` is also set.

View file

@ -17,6 +17,6 @@ import Objects from '../expressions/_objects.md'
<Objects />
- `user`: The current user. This may be `None` if there is no contextual user. See ([User](../expressions/reference/user-object.md))
- `user`: The current user. This may be `None` if there is no contextual user. See ([User](../user-group/user.md#object-attributes))
- `request`: The current request. This may be `None` if there is no contextual request. See ([Django documentation](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- Other arbitrary arguments given by the provider, this is documented on the Provider/Source.

View file

@ -1,7 +1,23 @@
---
title: User Object
title: User
---
## Attributes
### `goauthentik.io/user/can-change-username`
Optional flag, when set to false prevents the user from changing their own username.
### `goauthentik.io/user/token-expires`:
Optional flag, when set to false, Tokens created by the user will not expire.
### `goauthentik.io/user/debug`:
See [Troubleshooting access problems](../troubleshooting/access.md), when set, the user gets a more detailed explanation of access decisions.
## Object attributes
The User object has the following attributes:
- `username`: User's username.
@ -11,8 +27,8 @@ The User object has the following attributes:
- `is_active` Boolean field if user is active.
- `date_joined` Date user joined/was created.
- `password_change_date` Date password was last changed.
- `attributes` Dynamic attributes.
- `group_attributes` Merged attributes of all groups the user is member of and the user's own attributes.
- `attributes` Dynamic attributes, see above
- `group_attributes()` Merged attributes of all groups the user is member of and the user's own attributes.
- `ak_groups` This is a queryset of all the user's groups.
You can do additional filtering like

View file

@ -8,6 +8,13 @@ module.exports = {
type: "doc",
id: "terminology",
},
{
type: "category",
label: "Users & Groups",
items: [
"user-group/user"
]
},
{
type: "category",
label: "Installation",
@ -145,17 +152,6 @@ module.exports = {
label: "Property Mappings",
items: ["property-mappings/index", "property-mappings/expression"],
},
{
type: "category",
label: "Expressions",
items: [
{
type: "category",
label: "Reference",
items: ["expressions/reference/user-object"],
},
],
},
{
type: "category",
label: "Events",