Commit 835eb89f authored by Richard Glosner's avatar Richard Glosner Committed by Martin Juhás
Browse files

feat: make the reset of credentials atomic

No API changes

Closes #293
parent 6c5e15fa
Loading
Loading
Loading
Loading
+17 −2
Original line number Diff line number Diff line
@@ -10,7 +10,19 @@ from user.models import User
from django.conf import settings


def send_credentials(new_users: List[Tuple[User, str]]):
def send_credentials(new_users: List[Tuple[User, str]]) -> bool:
    if settings.TESTING_MODE:
        logger.info(
            "Credential emails were not sent or printed due to enabled TESTING_MODE"
        )  # this is not a failure of SMTP
        return True

    if not new_users:
        logger.info(
            "No credential emails to send, email sender received empty list"
        )  # this is not a failure of SMTP
        return True

    subject = "Welcome to INJECT!"
    messages = []

@@ -48,7 +60,7 @@ def send_credentials(new_users: List[Tuple[User, str]]):

    try:
        connection = get_connection(fail_silently=False)
        num_sent = connection.send_messages(messages)
        num_sent: int = connection.send_messages(messages)
        logger.info(
            f"{num_sent} emails with credentials were "
            + (
@@ -57,10 +69,13 @@ def send_credentials(new_users: List[Tuple[User, str]]):
                else "sent via SMTP server"
            )
        )
        return num_sent == len(new_users)

    except Exception as e:
        logger.error(
            f"Failed to send emails with credentials via SMTP server: {e}"
        )
        return False


def send_password_change_notification(user: User):
+1 −3
Original line number Diff line number Diff line
from django.conf import settings
from django.utils.crypto import get_random_string
from rest_framework.exceptions import PermissionDenied

@@ -45,7 +44,6 @@ class UserManager:
        )

        logger.info(f"new user `{new_user.username}` created")
        if not settings.DEBUG and not settings.TESTING_MODE:
        send_credentials([(new_user, password)])

        UserTag.objects.bulk_create(
+1 −3
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ from typing import Tuple, Optional, List, Callable, Dict
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.utils.crypto import get_random_string
from rest_framework.response import Response
from django.conf import settings

from user.lib.user_validator import (
    UserValidator,
@@ -129,6 +128,5 @@ class UserUploader:
        logger.info(
            f"Users: {[new_user for new_user, _ in created_users]} were created."
        )
        if not settings.DEBUG and not settings.TESTING_MODE:
        send_credentials(created_users)
        return validator.result_handler.create_response()
+2 −5
Original line number Diff line number Diff line
@@ -3,7 +3,6 @@ import sys
import re

from django.core.management.base import BaseCommand, CommandParser
from django.conf import settings

from user.models import EMAIL_REGEX, User
from user.lib.user_uploader import _create_and_tag_user
@@ -25,7 +24,7 @@ def _validate_admins(admins: str) -> List[ValidatedUserData]:
            sys.stderr.write(f'User with email: "{email}" already exists\n')
        elif email in list_duplicates:
            sys.stderr.write(
                f'User with email: "{email}" is more than once in a initial admin list -> skipping duplicit occurences\n'
                f'User with email: "{email}" is more than once in a initial admin list -> skipping duplicate occurrences\n'
            )
        else:
            valid_users.append(
@@ -41,9 +40,7 @@ def create_initial_admins(admins: str):
        if created_user is not None:
            created.append(created_user)

    if not settings.DEBUG and not settings.TESTING_MODE and created:
    send_credentials(created)

    sys.stderr.write(
        "Created admins: " + str([user.username for user, _ in created]) + "\n"
    )
+17 −17
Original line number Diff line number Diff line
from typing import List

import graphene
from django.conf import settings
from django.utils.crypto import get_random_string

from aai.access import (
@@ -14,6 +13,7 @@ from aai.decorators import protected
from common_lib.logger import logger, log_user_msg
from common_lib.schema.types import UserType, RestrictedUser, TagType
from common_lib.utils import get_model
from common_lib.exceptions import UserOperationException
from exercise.models import (
    Team,
    Exercise,
@@ -277,28 +277,28 @@ class RegenerateCredentialsMutation(graphene.Mutation):
    @classmethod
    @protected(User.AuthGroup.INSTRUCTOR)
    def mutate(cls, root, info, user_ids: List[str]) -> graphene.Mutation:
        users = User.objects.filter(id__in=user_ids)
        regenerated_users = []
        users = User.objects.filter(id__in=user_ids, is_imported=False)
        validate_credentials_regeneration(users, info.context.user)
        to_regenerate = [(user, get_random_string(length=15)) for user in users]

        for user in users:
            password = get_random_string(length=15)
            user.set_password(password)
            regenerated_users.append((user, password))

        if send_credentials(to_regenerate):
            [user.set_password(password) for user, password in to_regenerate]
            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)

        logger.info(
            log_user_msg(info.context, info.context.user)
            + f"re-generated credentials for users: {users} failed due to the SMTP error"
        )
        raise UserOperationException(
            "Due to SMTP failure, credentials did not reset"
        )


class DeleteUsersMutation(graphene.Mutation):
    class Arguments: