コード例 #1
0
def test_login_duplicate_email(test_client, session, error_template, request,
                               app):
    new_users = [
        m.User(name='NEW_USER',
               email='*****@*****.**',
               password='******',
               active=True,
               username='******'),
        m.User(name='NEW_USER',
               email='*****@*****.**',
               password='******',
               active=True,
               username='******')
    ]
    for new_user in new_users:
        session.add(new_user)
    session.commit()

    for user_id in [u.id for u in new_users]:
        user = LocalProxy(lambda: m.User.query.get(user_id))

        with app.app_context():
            res = test_client.req('post',
                                  f'/api/v1/login',
                                  200,
                                  data={
                                      'username': user.username,
                                      'password': '******'
                                  },
                                  result={
                                      'user': {
                                          'email': '*****@*****.**',
                                          'id': int,
                                          'name': 'NEW_USER',
                                          'username': user.username,
                                          'hidden': False,
                                          'group': None,
                                          'is_test_student': False,
                                      },
                                      'access_token': str,
                                  })
            access_token = res['access_token']

        with app.app_context():
            test_client.req(
                'get',
                '/api/v1/login',
                200,
                headers={'Authorization': f'Bearer {access_token}'},
                result={
                    'username': user.username,
                    'id': int,
                    'name': user.name,
                    'group': None,
                    'is_test_student': False,
                })

        with app.app_context():
            test_client.req('get', '/api/v1/login', 401)
コード例 #2
0
def test_password_strength_on_login(test_client, session, app, monkeypatch,
                                    password, is_strong):
    monkeypatch.setitem(app.config, 'MIN_PASSWORD_SCORE', 0)

    new_user = m.User(
        name='NEW_USER',
        email='*****@*****.**',
        password=password,
        active=True,
        username='******',
    )
    session.add(new_user)
    session.commit()

    monkeypatch.setitem(app.config, 'MIN_PASSWORD_SCORE', 3)

    res, rv = test_client.req(
        'post',
        '/api/v1/login',
        200,
        data={
            'username': new_user.username,
            'password': password,
        },
        result=dict,
        include_response=True,
    )

    if is_strong:
        assert 'warning' not in rv.headers
    else:
        assert 'warning' in rv.headers
コード例 #3
0
def test_login(test_client, session, error_template, password, request, active,
               app, username):
    new_user = m.User(
        name='NEW_USER',
        email='*****@*****.**',
        password='******',
        active=active,
        username='******',
        role=session.query(m.Role).first(),
    )
    session.add(new_user)
    session.commit()

    data_err = request.node.get_closest_marker('data_error')
    if data_err:
        error = 400
        if data_err.kwargs.get('wrong'):
            error_template = copy.deepcopy(error_template)
            error_template[
                'message'] == 'The supplied email or password is wrong.'
    elif not active:
        error = 403
    else:
        error = False

    data = {}
    if password is not None:
        data['password'] = password
    if username is not None:
        data['username'] = username

    with app.app_context():
        res = test_client.req('post',
                              f'/api/v1/login?with_permissions',
                              error or 200,
                              data=data,
                              result=error_template if error else {
                                  'user': {
                                      'email': '*****@*****.**',
                                      'id': int,
                                      'name': 'NEW_USER',
                                      'username': '******',
                                      'hidden': False,
                                      'permissions': dict,
                                      'group': None,
                                      'is_test_student': False,
                                  },
                                  'access_token': str
                              })
        access_token = '' if error else res['access_token']

    with app.app_context():
        test_client.req('get',
                        '/api/v1/login',
                        401 if error else 200,
                        headers={'Authorization': f'Bearer {access_token}'})

    with app.app_context():
        test_client.req('get', '/api/v1/login', 401)
コード例 #4
0
ファイル: test_login.py プロジェクト: Boccca2014/CodeGra.de
def test_update_user_info(
    logged_in, test_client, session, new_password, email, name, old_password,
    error_template, request, role
):
    user = m.User(
        name='NEW_USER',
        email='*****@*****.**',
        password='******',
        active=True,
        username='******',
        role=m.Role.query.filter_by(name=role).one(),
    )
    session.add(user)
    session.commit()
    user_id = user.id

    missing_err = request.node.get_closest_marker('missing_error')
    data_err = request.node.get_closest_marker('data_error')
    perm_err = request.node.get_closest_marker('perm_error')
    password_err = request.node.get_closest_marker('password_error')
    needs_pw = request.node.get_closest_marker('needs_password')
    if missing_err:
        error = 400
    elif perm_err:
        error = 403
    elif password_err:
        error = 403
    elif needs_pw and old_password != 'a':
        error = 403
    elif data_err:
        error = 400
    else:
        error = False

    data = {}
    if new_password is not None:
        data['new_password'] = new_password
    if old_password is not None:
        data['old_password'] = old_password
    if email is not None:
        data['email'] = email
    if name is not None:
        data['name'] = name

    with logged_in(user):
        test_client.req(
            'patch',
            '/api/v1/login',
            error or 200,
            data=data,
        )
        new_user = m.User.query.get(user_id)
        if not error:
            assert new_user.name == name
            assert new_user.email == email
        else:
            assert new_user.name != name
コード例 #5
0
ファイル: helpers.py プロジェクト: marksherman/CodeGra.de
def create_user_with_role(session, role, courses, name=None):
    if not isinstance(courses, list):
        courses = [courses]
    n_id = str(uuid.uuid4())
    new_role = m.Role(name=f'NEW_ROLE--{n_id}')
    user = m.User(
        name=f'NEW_USER-{n_id}' if name is None else name,
        email=f'new_user-{n_id}@a.nl',
        password=n_id,
        active=True,
        username=f'a-the-a-er-{n_id}' if name is None else f'{name}{n_id}',
        role=new_role,
    )
    for course in courses:
        user.courses[get_id(course)] = m.CourseRole.query.filter_by(
            name=role, course_id=get_id(course)
        ).one()
    session.add(user)
    session.commit()
    u_id = user.id
    return LocalProxy(lambda: m.User.query.get(u_id))
コード例 #6
0
def test_update_user_info_permissions(logged_in, test_client, session,
                                      error_template, request):
    new_role = m.Role(name='NEW_ROLE')
    info_perm = psef.permissions.GlobalPermission.can_edit_own_info
    pw_perm = psef.permissions.GlobalPermission.can_edit_own_password
    new_role.set_permission(info_perm, False)
    new_role.set_permission(pw_perm, False)

    session.add(new_role)
    user = m.User(
        name='NEW_USER',
        email='*****@*****.**',
        password='******',
        active=True,
        username='******',
        role=new_role,
    )
    session.add(user)

    session.commit()
    user_id = user.id

    data = {}
    data['new_password'] = '******'
    data['old_password'] = '******'
    data['email'] = '*****@*****.**'
    data['name'] = 'new_name'

    with logged_in(user):
        # This user has no permissions so it should not be possible to do this.
        test_client.req(
            'patch',
            '/api/v1/login',
            403,
            data=data,
            result=error_template,
        )

        pw_perm = GlobalPermission.can_edit_own_password
        m.User.query.get(user_id).role.set_permission(pw_perm, True)
        session.commit()

        # This user does not have the permission to change the name, so it
        # should fail
        test_client.req(
            'patch',
            '/api/v1/login',
            403,
            data=data,
            result=error_template,
        )
        # However only password should be good
        test_client.req(
            'patch',
            '/api/v1/login',
            200,
            data={
                'name': 'NEW_USER',
                'email': '*****@*****.**',
                'old_password': '******',
                'new_password': '******'
            },
        )

        pw_perm = psef.permissions.GlobalPermission.can_edit_own_password
        info_perm = psef.permissions.GlobalPermission.can_edit_own_info
        m.User.query.get(user_id).role.set_permission(pw_perm, False)
        m.User.query.get(user_id).role.set_permission(info_perm, True)
        session.commit()

        # This user does not have the permission to change the pw, so it
        # should fail
        test_client.req(
            'patch',
            '/api/v1/login',
            403,
            data=data,
            result=error_template,
        )
        # However only name should be good
        test_client.req(
            'patch',
            '/api/v1/login',
            200,
            data={
                'name': 'new_name1',
                'email': '*****@*****.**',
                'old_password': '',
                'new_password': '',
            },
        )

        m.User.query.get(user_id).role.set_permission(
            GlobalPermission.can_edit_own_password, True)
        session.commit()

        # It now has both so this should work.
        test_client.req(
            'patch',
            '/api/v1/login',
            403,
            data=data,
            result=error_template,
        )
コード例 #7
0
ファイル: test_login.py プロジェクト: Boccca2014/CodeGra.de
def test_login_rate_limit(test_client, session, describe, app):
    with describe('setup'):
        password = str(uuid.uuid4())
        new_user1 = m.User(
            name='NEW_USER',
            email='*****@*****.**',
            password=password,
            active=True,
            username='******',
            role=session.query(m.Role).first(),
        )
        new_user2 = m.User(
            name='NEW_USER',
            email='*****@*****.**',
            password=password,
            active=True,
            username='******',
            role=session.query(m.Role).first(),
        )
        session.add(new_user1)
        session.add(new_user2)
        session.commit()

    with describe('Will get rate limit if trying too much'):
        for i in range(100):
            with app.app_context():
                res = test_client.post(
                    '/api/v1/login?with_permissions',
                    json={'username': '******', 'password': '******'}
                )
            if res.status_code == 429:
                assert i >= 5
                break
            else:
                assert res.status_code >= 400
        else:
            assert False, 'Expected 429 at some point'

    with describe('Can still login for another user'):
        with app.app_context():
            res = test_client.req(
                'post',
                '/api/v1/login?with_permissions',
                200,
                data={
                    'username': '******',
                    'password': password,
                },
            )

    with describe('Correct login does not fix rate limit'):
        with app.app_context():
            res = test_client.req(
                'post',
                '/api/v1/login?with_permissions',
                429,
                data={
                    'username': '******',
                    'password': password,
                },
            )
コード例 #8
0
ファイル: manage.py プロジェクト: agreement/CodeGra.de
def test_data(db=None):
    db = db or psef.models.db

    if not app.config['DEBUG']:
        print('You can not add test data in production mode', file=sys.stderr)
        return 1

    seed()
    db.session.commit()
    with open(
        f'{os.path.dirname(os.path.abspath(__file__))}/test_data/courses.json',
        'r'
    ) as c:
        cs = json.load(c)
        for c in cs:
            if m.Course.query.filter_by(name=c['name']).first() is None:
                db.session.add(m.Course(name=c['name']))
    db.session.commit()
    with open(
        f'{os.path.dirname(os.path.abspath(__file__))}/test_data/assignments.json',
        'r'
    ) as c:
        cs = json.load(c)
        for c in cs:
            assig = m.Assignment.query.filter_by(name=c['name']).first()
            if assig is None:
                db.session.add(
                    m.Assignment(
                        name=c['name'],
                        deadline=datetime.datetime.utcnow() +
                        datetime.timedelta(days=c['deadline']),
                        state=c['state'],
                        description=c['description'],
                        course=m.Course.query.filter_by(name=c['course']
                                                        ).first()
                    )
                )
            else:
                assig.description = c['description']
                assig.state = c['state']
                assig.course = m.Course.query.filter_by(name=c['course']
                                                        ).first()
    db.session.commit()
    with open(
        f'{os.path.dirname(os.path.abspath(__file__))}/test_data/users.json',
        'r'
    ) as c:
        cs = json.load(c)
        for c in cs:
            u = m.User.query.filter_by(name=c['name']).first()
            courses = {
                m.Course.query.filter_by(name=name).first(): role
                for name, role in c['courses'].items()
            }
            perms = {
                course.id:
                m.CourseRole.query.filter_by(name=name,
                                             course_id=course.id).first()
                for course, name in courses.items()
            }
            username = c['name'].split(' ')[0].lower()
            if u is not None:
                u.name = c['name']
                u.courses = perms
                u.email = c['name'].replace(' ', '_').lower() + '@example.com'
                u.password = c['name']
                u.username = username
                u.role = m.Role.query.filter_by(name=c['role']).first()
            else:
                u = m.User(
                    name=c['name'],
                    courses=perms,
                    email=c['name'].replace(' ', '_').lower() + '@example.com',
                    password=c['name'],
                    username=username,
                    role=m.Role.query.filter_by(name=c['role']).first()
                )
                db.session.add(u)
                for course, role in courses.items():
                    if role == 'Student':
                        for assig in course.assignments:
                            work = m.Work(assignment=assig, user=u)
                            db.session.add(
                                m.File(
                                    work=work,
                                    name='Top stub dir',
                                    is_directory=True
                                )
                            )
                            db.session.add(work)
    db.session.commit()
    with open(
        f'{os.path.dirname(os.path.abspath(__file__))}/test_data/rubrics.json',
        'r'
    ) as c:
        cs = json.load(c)
        for c in cs:
            for row in c['rows']:
                assignment = m.Assignment.query.filter_by(
                    name=c['assignment']
                ).first()
                if assignment is not None:
                    rubric_row = m.RubricRow.query.filter_by(
                        header=row['header'],
                        description=row['description'],
                        assignment_id=assignment.id
                    ).first()
                    if rubric_row is None:
                        rubric_row = m.RubricRow(
                            header=row['header'],
                            description=row['description'],
                            assignment=assignment
                        )
                        db.session.add(rubric_row)
                    for item in row['items']:
                        if not db.session.query(
                            m.RubricItem.query.filter_by(
                                rubricrow_id=rubric_row.id,
                                **item,
                            ).exists()
                        ).scalar():
                            rubric_item = m.RubricItem(
                                description=item['description'] * 5,
                                header=item['header'],
                                points=item['points'],
                                rubricrow=rubric_row
                            )
                            db.session.add(rubric_item)
    db.session.commit()
コード例 #9
0
ファイル: users.py プロジェクト: agreement/CodeGra.de
def register_user() -> JSONResponse[t.Mapping[str, str]]:
    """Create a new :class:`.models.User`.

    .. :quickref: User; Create a new user by registering it.

    :<json str username: The username of the new user.
    :<json str password: The password of the new user.
    :<json str email: The email of the new user.
    :<json str name: The full name of the new user.

    :>json str access_token: The JWT token that can be used to log in the newly
        created user.

    :raises APIException: If the not all given strings are at least 1
        char. (INVALID_PARAM)
    :raises APIException: If there is already a user with the given
        username. (OBJECT_ALREADY_EXISTS)
    :raises APIException: If the given email is not a valid
        email. (INVALID_PARAM)
    """
    content = ensure_json_dict(request.get_json())
    ensure_keys_in_dict(content, [('username', str), ('password', str),
                                  ('email', str), ('name', str)])
    username = t.cast(str, content['username'])
    password = t.cast(str, content['password'])
    email = t.cast(str, content['email'])
    name = t.cast(str, content['name'])

    if not all([username, password, email, name]):
        raise APIException(
            'All fields should contain at least one character',
            ('The lengths of the given password, username and '
             'email were not all larger than 1'),
            APICodes.INVALID_PARAM,
            400,
        )

    if db.session.query(
            models.User.query.filter_by(username=username).exists()).scalar():
        raise APIException(
            'The given username is already in use',
            f'The username "{username}" is taken',
            APICodes.OBJECT_ALREADY_EXISTS,
            400,
        )

    if not validate_email(email):
        raise APIException(
            'The given email is not valid',
            f'The email "{email}"',
            APICodes.INVALID_PARAM,
            400,
        )

    role = models.Role.query.filter_by(
        name=current_app.config['DEFAULT_ROLE']).one()
    user = models.User(username=username,
                       password=password,
                       email=email,
                       name=name,
                       role=role,
                       active=True)
    db.session.add(user)
    db.session.commit()

    token: str = flask_jwt.create_access_token(
        identity=user.id,
        fresh=True,
    )
    return jsonify({'access_token': token})
コード例 #10
0
ファイル: manage.py プロジェクト: te5in/CodeGra.de
def test_data(db=None):
    db = psef.models.db if db is None else db

    if not app.config['DEBUG']:
        print('You can not add test data in production mode', file=sys.stderr)
        return 1
    if not os.path.isdir(app.config['UPLOAD_DIR']):
        os.mkdir(app.config['UPLOAD_DIR'])

    seed()
    db.session.commit()
    with open(
            f'{os.path.dirname(os.path.abspath(__file__))}/test_data/courses.json',
            'r') as c:
        cs = json.load(c)
        for c in cs:
            if m.Course.query.filter_by(name=c['name']).first() is None:
                db.session.add(m.Course.create_and_add(name=c['name']))
    db.session.commit()
    with open(
            f'{os.path.dirname(os.path.abspath(__file__))}/test_data/assignments.json',
            'r') as c:
        cs = json.load(c)
        for c in cs:
            assig = m.Assignment.query.filter_by(name=c['name']).first()
            if assig is None:
                db.session.add(
                    m.Assignment(
                        name=c['name'],
                        deadline=cg_dt_utils.now() +
                        datetime.timedelta(days=c['deadline']),
                        state=c['state'],
                        description=c['description'],
                        course=m.Course.query.filter_by(
                            name=c['course']).first(),
                        is_lti=False,
                    ))
            else:
                assig.description = c['description']
                assig.state = c['state']
                assig.course = m.Course.query.filter_by(
                    name=c['course']).first()
    db.session.commit()
    with open(
            f'{os.path.dirname(os.path.abspath(__file__))}/test_data/users.json',
            'r') as c:
        cs = json.load(c)
        for c in cs:
            u = m.User.query.filter_by(name=c['name']).first()
            courses = {
                m.Course.query.filter_by(name=name).first(): role
                for name, role in c['courses'].items()
            }
            perms = {
                course.id:
                m.CourseRole.query.filter_by(name=name,
                                             course_id=course.id).first()
                for course, name in courses.items()
            }
            username = c['name'].split(' ')[0].lower()
            if u is not None:
                u.name = c['name']
                u.courses = perms
                u.email = c['name'].replace(' ', '_').lower() + '@example.com'
                u.password = c['name']
                u.username = username
                u.role = m.Role.query.filter_by(name=c['role']).first()
            else:
                u = m.User(name=c['name'],
                           courses=perms,
                           email=c['name'].replace(' ', '_').lower() +
                           '@example.com',
                           password=c['name'],
                           username=username,
                           role=m.Role.query.filter_by(name=c['role']).first())
                db.session.add(u)
                for course, role in courses.items():
                    if role == 'Student':
                        for assig in course.assignments:
                            work = m.Work(assignment=assig, user=u)
                            f = m.File(work=work,
                                       name='Top stub dir ({})'.format(u.name),
                                       is_directory=True)
                            filename = str(uuid.uuid4())
                            shutil.copyfile(
                                __file__,
                                os.path.join(app.config['UPLOAD_DIR'],
                                             filename))
                            f.children = [
                                m.File(work=work,
                                       name='manage.py',
                                       is_directory=False,
                                       filename=filename)
                            ]
                            db.session.add(f)
                            db.session.add(work)
    db.session.commit()
    with open(
            f'{os.path.dirname(os.path.abspath(__file__))}/test_data/rubrics.json',
            'r') as c:
        cs = json.load(c)
        for c in cs:
            for row in c['rows']:
                assignment = m.Assignment.query.filter_by(
                    name=c['assignment']).first()
                if assignment is not None:
                    rubric_row = m.RubricRow.query.filter_by(
                        header=row['header'],
                        description=row['description'],
                        assignment_id=assignment.id,
                    ).first()
                    if rubric_row is None:
                        rubric_row = m.RubricRow(
                            header=row['header'],
                            description=row['description'],
                            assignment=assignment,
                            rubric_row_type='normal',
                        )
                        db.session.add(rubric_row)
                    for item in row['items']:
                        if not db.session.query(
                                m.RubricItem.query.filter_by(
                                    rubricrow_id=rubric_row.id,
                                    **item,
                                ).exists()).scalar():
                            rubric_item = m.RubricItem(
                                description=item['description'] * 5,
                                header=item['header'],
                                points=item['points'],
                                rubricrow=rubric_row)
                            db.session.add(rubric_item)
    db.session.commit()
コード例 #11
0
def post_submissions(assignment_id: int) -> EmptyResponse:
    """Add submissions to the  given:class:`.models.Assignment` from a
    blackboard zip file as :class:`.models.Work` objects.

    .. :quickref: Assignment; Create works from a blackboard zip.

    You should upload a file as multiform post request. The key should start
    with 'file'. Multiple blackboard zips are not supported and result in one
    zip being chosen at (psuedo) random.

    :param int assignment_id: The id of the assignment
    :returns: An empty response with return code 204

    :raises APIException: If no assignment with given id exists.
        (OBJECT_ID_NOT_FOUND)
    :raises APIException: If there was no file in the request.
        (MISSING_REQUIRED_PARAM)
    :raises APIException: If the file parameter name is incorrect or if the
        given file does not contain any valid submissions. (INVALID_PARAM)
    :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN)
    :raises PermissionException: If the user is not allowed to manage the
        course attached to the assignment. (INCORRECT_PERMISSION)
    """
    assignment = helpers.get_or_404(models.Assignment, assignment_id)
    auth.ensure_permission('can_upload_bb_zip', assignment.course_id)
    files = get_submission_files_from_request(check_size=False)

    try:
        submissions = psef.files.process_blackboard_zip(files[0])
    except Exception:  # pylint: disable=broad-except
        # TODO: Narrow this exception down.
        submissions = []

    if not submissions:
        raise APIException(
            "The blackboard zip could not imported or it was empty.",
            'The blackboard zip could not'
            ' be parsed or it did not contain any valid submissions.',
            APICodes.INVALID_PARAM, 400
        )

    missing, recalc_missing = assignment.get_divided_amount_missing()
    sub_lookup = {}
    for sub in assignment.get_all_latest_submissions():
        sub_lookup[sub.user_id] = sub

    student_course_role = models.CourseRole.query.filter_by(
        name='Student', course_id=assignment.course_id
    ).first()
    global_role = models.Role.query.filter_by(name='Student').first()

    subs = []
    hists = []

    found_users = {
        u.username: u
        for u in models.User.query.filter(
            t.cast(
                models.DbColumn[str],
                models.User.username,
            ).in_([si.student_id for si, _ in submissions])
        ).options(joinedload(models.User.courses))
    }

    newly_assigned: t.Set[t.Optional[int]] = set()

    for submission_info, submission_tree in submissions:
        user = found_users.get(submission_info.student_id, None)

        if user is None:
            # TODO: Check if this role still exists
            user = models.User(
                name=submission_info.student_name,
                username=submission_info.student_id,
                courses={assignment.course_id: student_course_role},
                email='',
                password=None,
                role=global_role,
            )
            found_users[user.username] = user
            # We don't need to track the users to insert as we are already
            # tracking the submissions of them and they are coupled.
        else:
            user.courses[assignment.course_id] = student_course_role

        work = models.Work(
            assignment=assignment,
            user=user,
            created_at=submission_info.created_at,
        )
        subs.append(work)

        if user.id is not None and user.id in sub_lookup:
            work.assigned_to = sub_lookup[user.id].assigned_to

        if work.assigned_to is None:
            if missing:
                work.assigned_to = max(
                    missing.keys(), key=lambda k: missing[k]
                )
                missing = recalc_missing(work.assigned_to)
                sub_lookup[user.id] = work

        hists.append(
            work.set_grade(
                submission_info.grade, current_user, add_to_session=False
            )
        )
        work.add_file_tree(db.session, submission_tree)
        if work.assigned_to is not None:
            newly_assigned.add(work.assigned_to)

    assignment.set_graders_to_not_done(
        list(newly_assigned),
        send_mail=True,
        ignore_errors=True,
    )

    db.session.bulk_save_objects(subs)
    db.session.flush()

    # TODO: This loop should be eliminated.
    for hist in hists:
        hist.work_id = hist.work.id
        hist.user_id = hist.user.id

    db.session.bulk_save_objects(hists)
    db.session.commit()

    return make_empty_response()
コード例 #12
0
    def ensure_lti_user(
            self) -> t.Tuple[models.User, t.Optional[str], t.Optional[str]]:
        """Make sure the current LTI user is logged in as a psef user.

        This is done by first checking if we know a user with the current LTI
        user_id, if this is the case this is the user we log in and return.

        Otherwise we check if a user is logged in and this user has no LTI
        user_id, if this is the case we link the current LTI user_id to the
        current logged in user and return this user.

        Otherwise we create a new user and link this user to current LTI
        user_id.

        :returns: A tuple containing the items in order: the user found as
            described above, optionally a new token for the user to login with,
            optionally the updated email of the user as a string, this is
            ``None`` if the email was not updated.
        """
        is_logged_in = _user_active()
        token = None
        user = None

        lti_user = models.User.query.filter_by(
            lti_user_id=self.user_id).first()

        if is_logged_in and current_user.lti_user_id == self.user_id:
            # The currently logged in user is now using LTI
            user = current_user

        elif lti_user is not None:
            # LTI users are used before the current logged user.
            token = flask_jwt.create_access_token(
                identity=lti_user.id,
                fresh=True,
            )
            user = lti_user
        elif is_logged_in and current_user.lti_user_id is None:
            # TODO show some sort of screen if this linking is wanted
            current_user.lti_user_id = self.user_id
            db.session.flush()
            user = current_user
        else:
            # New LTI user id is found and no user is logged in or the current
            # user has a different LTI user id. A new user is created and
            # logged in.
            i = 0

            def _get_username() -> str:
                return self.username + (f' ({i})' if i > 0 else '')

            while db.session.query(
                    models.User.query.filter_by(username=_get_username()).
                    exists()).scalar():  # pragma: no cover
                i += 1

            user = models.User(
                lti_user_id=self.user_id,
                name=self.full_name,
                email=self.user_email,
                active=True,
                password=None,
                username=_get_username(),
            )
            db.session.add(user)
            db.session.flush()

            token = flask_jwt.create_access_token(
                identity=user.id,
                fresh=True,
            )

        updated_email = None
        if user.reset_email_on_lti:
            user.email = self.user_email
            updated_email = self.user_email
            user.reset_email_on_lti = False

        return user, token, updated_email