Skip to content
Snippets Groups Projects
Commit 1c0cd137 authored by Martin Juhás's avatar Martin Juhás
Browse files

Merge branch '202-regenerate-credentials' into 'main'

Resolve "Regenerate credentials"

Closes #202

See merge request inject/backend!209
parents a32902e9 9b97237a
No related branches found
No related tags found
No related merge requests found
...@@ -41,3 +41,4 @@ fix: update performance testing tools to the newest API ...@@ -41,3 +41,4 @@ fix: update performance testing tools to the newest API
fix: fix SendEmailInput authorization checks fix: fix SendEmailInput authorization checks
feat: addition of INJECT_SECRET_KEY env variable #141 feat: addition of INJECT_SECRET_KEY env variable #141
change: set csrf cookie for `/version` endpoint change: set csrf cookie for `/version` endpoint
feat: endpoint for re-generation of user login credentials #202
from typing import List from typing import List
import graphene import graphene
from django.contrib.auth.models import Group from django.utils.crypto import get_random_string
from rest_framework.exceptions import PermissionDenied from django.conf import settings
from aai.models import Perms from aai.models import Perms
from aai.utils import protected, extra_protected, Check from aai.utils import protected, extra_protected, Check
...@@ -19,7 +19,10 @@ from user.schema.validators import ( ...@@ -19,7 +19,10 @@ from user.schema.validators import (
validate_instructor_removing, validate_instructor_removing,
validate_definition_access_assinging, validate_definition_access_assinging,
validate_definition_access_removing, validate_definition_access_removing,
validate_credentials_regeneration,
execute_change_userdata,
) )
from user.email.email_sender import send_credentials
class AssignUsersToTeamMutation(graphene.Mutation): class AssignUsersToTeamMutation(graphene.Mutation):
...@@ -225,38 +228,57 @@ class ChangeUserDataMutation(graphene.Mutation): ...@@ -225,38 +228,57 @@ class ChangeUserDataMutation(graphene.Mutation):
change_user_input = graphene.Argument(ChangeUserInput, required=True) change_user_input = graphene.Argument(ChangeUserInput, required=True)
@classmethod @classmethod
@protected(Perms.update_user) @protected(Perms.update_user.full_name)
def mutate( def mutate(
cls, root, info, change_user_input: ChangeUserInput cls, root, info, change_user_input: ChangeUserInput
) -> graphene.Mutation: ) -> graphene.Mutation:
user = get_model(User, id=change_user_input.user_id) user = get_model(User, id=change_user_input.user_id)
if change_user_input.group is not None: changes = execute_change_userdata(
if ( change_user_input, user, info.context.user
change_user_input.group == "admin" )
and not info.context.user.is_superuser logger.info(
): log_user_msg(info.context, info.context.user)
raise PermissionDenied( + f"updated user: {user} changed data: {changes}"
"Permission denied - Only admin can change user to admin group" )
)
user.group = Group.objects.get(name=change_user_input.group)
if change_user_input.group == "admin":
user.is_superuser = True
user.is_staff = True
elif change_user_input.group == "instructor":
user.is_staff = True
user.is_superuser = False
elif change_user_input.group == "trainee":
user.is_staff = False
user.is_superuser = False
if (
change_user_input.active is not None and not user.is_imported
): # can not change is_active of imported user (should be always False)
user.is_active = change_user_input.active
user.save()
return ChangeUserDataMutation(user=user) return ChangeUserDataMutation(user=user)
class RegenerateCredentialsMutation(graphene.Mutation):
class Arguments:
user_ids = graphene.List(
graphene.ID,
required=True,
description="IDs of the users to have re-generated credentials",
)
operation_done = graphene.Boolean()
@classmethod
@protected(Perms.update_user.full_name)
def mutate(cls, root, info, user_ids: List[str]) -> graphene.Mutation:
users = User.objects.filter(id__in=user_ids, is_active=True)
regenerated_users = []
validate_credentials_regeneration(users, info.context.user)
for user in users:
password = get_random_string(length=15)
user.set_password(password)
regenerated_users.append((user, password))
User.objects.bulk_update(users, ["password"])
if (
not settings.DEBUG
and not settings.TESTING_MODE
and regenerated_users
):
send_credentials(regenerated_users)
logger.info(
log_user_msg(info.context, info.context.user)
+ f"re-generated credentials for users: {users}"
)
return RegenerateCredentialsMutation(operation_done=True)
class Mutation(graphene.ObjectType): class Mutation(graphene.ObjectType):
assign_users_to_team = AssignUsersToTeamMutation.Field( assign_users_to_team = AssignUsersToTeamMutation.Field(
description="Mutation for assigning users to the specific team of the exercise" description="Mutation for assigning users to the specific team of the exercise"
...@@ -279,3 +301,6 @@ class Mutation(graphene.ObjectType): ...@@ -279,3 +301,6 @@ class Mutation(graphene.ObjectType):
change_user_data = ChangeUserDataMutation.Field( change_user_data = ChangeUserDataMutation.Field(
description="Mutation for changing user data" description="Mutation for changing user data"
) )
regenerate_credentials = RegenerateCredentialsMutation.Field(
description="Mutation for re-generating credentials for users"
)
...@@ -64,7 +64,7 @@ class Query(graphene.ObjectType): ...@@ -64,7 +64,7 @@ class Query(graphene.ObjectType):
def resolve_user(self, info, user_id: str) -> User: def resolve_user(self, info, user_id: str) -> User:
return get_model(User, id=user_id) return get_model(User, id=user_id)
@protected(Perms.view_user) @protected(Perms.view_user.full_name)
def resolve_tags(self, info) -> List[Tag]: def resolve_tags(self, info) -> List[Tag]:
return Tag.objects.all() return Tag.objects.all()
......
from typing import List, Type, TypeVar, Union from typing import List, Type, TypeVar, Union
from django.db.models import Model from django.db.models import Model
from django.conf import settings
from rest_framework.exceptions import PermissionDenied
from django.db.models import QuerySet
from django.contrib.auth.models import Group
from user.models import UserInTeam, User, InstructorOfExercise, DefinitionAccess from user.models import UserInTeam, User, InstructorOfExercise, DefinitionAccess
from exercise.models import Team, Exercise, Definition from exercise.models import Team, Exercise, Definition
from aai.models import UserGroup from aai.models import UserGroup
from user.graphql_inputs import ChangeUserInput
ModelType = TypeVar("ModelType", bound=Model) ModelType = TypeVar("ModelType", bound=Model)
...@@ -62,6 +67,18 @@ def __create_users_msg_format(users: List[User]) -> str: ...@@ -62,6 +67,18 @@ def __create_users_msg_format(users: List[User]) -> str:
return ", ".join(invalid_group_users) return ", ".join(invalid_group_users)
def _validate_group_change(requester: User, changed_user: User):
if settings.NOAUTH:
return
if (
changed_user.group == UserGroup.ADMIN
and requester.group != UserGroup.ADMIN
):
raise PermissionDenied(
"Permission denied - Only admin can change user to admin group"
)
def validate_team_assigning(user_ids: List[str], team: Team) -> List[User]: def validate_team_assigning(user_ids: List[str], team: Team) -> List[User]:
users = _check_nonexistent_users(user_ids) users = _check_nonexistent_users(user_ids)
...@@ -147,3 +164,48 @@ def validate_definition_access_removing( ...@@ -147,3 +164,48 @@ def validate_definition_access_removing(
definition_id=definition.id, definition_id=definition.id,
) )
return users return users
def validate_credentials_regeneration(users: QuerySet[User], requester: User):
if settings.NOAUTH:
return
if (
requester.group != UserGroup.ADMIN
and users.filter(group__name=UserGroup.ADMIN).exists()
):
raise PermissionDenied(
"Permission denied - Only admin can re-generate credentials for admin users"
)
def execute_change_userdata(
change_user_input: ChangeUserInput, changing_user: User, requester: User
) -> List[str]:
changes = []
old_group = changing_user.group
old_status = changing_user.is_active
if change_user_input.group is not None:
_validate_group_change(requester, changing_user)
changing_user.group = Group.objects.get(name=change_user_input.group)
if change_user_input.group == "admin":
changing_user.is_superuser = True
changing_user.is_staff = True
changes.append(f"group=(old: {old_group}, new: admin)")
elif change_user_input.group == "instructor":
changing_user.is_staff = True
changing_user.is_superuser = False
changes.append(f"group=(old: {old_group}, new: instructor)")
elif change_user_input.group == "trainee":
changing_user.is_staff = False
changing_user.is_superuser = False
changes.append(f"group=(old: {old_group}, new: trainee)")
if (
change_user_input.active is not None and not changing_user.is_imported
): # can not change is_active of imported user (should be always False)
changing_user.is_active = change_user_input.active
changes.append(
f"active=(old: {old_status}, new: {change_user_input.active})"
)
changing_user.save()
return changes
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment