def get_all_course_roles( course_id: int ) -> t.Union[JSONResponse[t.List[models.CourseRole]], JSONResponse[t.List[models.CourseRole.AsJSONWithPerms]], ]: """Get a list of all :class:`.models.CourseRole` objects of a given :class:`.models.Course`. .. :quickref: Course; Get all course roles for a single course. :param int course_id: The id of the course to get the roles for. :returns: An array of all course roles for the given course. :>jsonarr perms: All permissions this role has as returned by :py:meth:`.models.CourseRole.get_all_permissions`. :>jsonarrtype perms: :py:class:`t.Mapping[str, bool]` :>jsonarr bool own: True if the current course role is the current users course role. :>jsonarr ``**rest``: The course role as returned by :py:meth:`.models.CourseRole.__to_json__` :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not manage the course with the given id. (INCORRECT_PERMISSION) """ course = helpers.get_or_404(models.Course, course_id) auth.CoursePermissions(course).ensure_may_see_roles() course_roles = models.CourseRole.query.filter( models.CourseRole.course == course, ~models.CourseRole.hidden).order_by(models.CourseRole.name).all() if request.args.get('with_roles') == 'true': res = [r.__to_json_with_perms__() for r in course_roles] return jsonify(res) return jsonify(course_roles)
def get_course_or_global_permissions( ) -> JSONResponse[t.Union[GlobalPermMap, t.Mapping[int, CoursePermMap]]]: """Get all the global :class:`.psef.models.Permission` or the value of a permission in all courses of the currently logged in :class:`.psef.models.User` .. :quickref: Permission; Get global permissions or all the course permissions for the current user. :qparam str type: The type of permissions to get. This can be ``global`` or ``course``. :qparam str permission: The permissions to get when getting course permissions. You can pass this parameter multiple times to get multiple permissions. DEPRECATED: This option is deprecated, as it is preferred that you simply get all permissions for a course. :returns: The returning object depends on the given ``type``. If it was ``global`` a mapping between permissions name and a boolean indicating if the currently logged in user has this permissions is returned. If it was ``course`` such a mapping is returned for every course the user is enrolled in. So it is a mapping between course ids and permission mapping. The permissions given as ``permission`` query parameter are the only ones that are present in the permission map. When no ``permission`` query is given all course permissions are returned. """ ensure_keys_in_dict(request.args, [('type', str)]) permission_type = t.cast(str, request.args['type']).lower() if permission_type == 'global': return jsonify(GPerm.create_map(current_user.get_all_permissions())) elif permission_type == 'course' and 'permission' in request.args: add_deprecate_warning( 'Requesting a subset of course permissions is deprecated') # Make sure at least one permission is present ensure_keys_in_dict(request.args, [('permission', str)]) perm_names = t.cast(t.List[str], request.args.getlist('permission')) return jsonify({ course_id: CPerm.create_map(v) for course_id, v in current_user.get_permissions_in_courses( [CPerm.get_by_name(p) for p in perm_names]).items() }) elif permission_type == 'course': return jsonify({ course_id: CPerm.create_map(v) for course_id, v in current_user.get_all_permissions_in_courses().items() }) else: raise APIException( 'Invalid permission type given', f'The given type "{permission_type}" is not "global" or "course"', APICodes.INVALID_PARAM, 400, )
def get_code(file_id: int) -> t.Union[werkzeug.wrappers.Response, JSONResponse[ t.Union[t.Mapping[str, str], models.File, _FeedbackMapping] ]]: """Get data from the :class:`.models.File` with the given id. .. :quickref: Code; Get code or its metadata. The are several options to change the data that is returned. Based on the argument type in the request different functions are called. - If ``type == 'metadata'`` the JSON serialized :class:`.models.File` is returned. - If ``type == 'file-url'`` or ``type == 'pdf'`` (deprecated) an object with a single key, `name`, with as value the return values of :py:func:`.get_file_url`. - If ``type == 'feedback'`` or ``type == 'linter-feedback'`` see :py:func:`.code.get_feedback` - Otherwise the content of the file is returned as plain text. :param int file_id: The id of the file :returns: A response containing a plain text file unless specified otherwise. :raises APIException: If there is not file with the given id. (OBJECT_ID_NOT_FOUND) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the file does not belong to user and the user can not view files in the attached course. (INCORRECT_PERMISSION) """ file = helpers.filter_single_or_404(models.File, models.File.id == file_id) auth.ensure_can_view_files(file.work, file.fileowner == FileOwner.teacher) get_type = request.args.get('type', None) if get_type == 'metadata': return jsonify(file) elif get_type == 'feedback': return jsonify(get_feedback(file, linter=False)) elif get_type == 'pdf' or get_type == 'file-url': return jsonify({'name': get_file_url(file)}) elif get_type == 'linter-feedback': return jsonify(get_feedback(file, linter=True)) else: contents = psef.files.get_file_contents(file) res: 'werkzeug.wrappers.Response' = make_response(contents) res.headers['Content-Type'] = 'application/octet-stream' return res
def get_assignment_rubric(assignment_id: int ) -> JSONResponse[t.Sequence[models.RubricRow]]: """Return the rubric corresponding to the given `assignment_id`. .. :quickref: Assignment; Get the rubric of an assignment. :param int assignment_id: The id of the assignment :returns: A list of JSON of :class:`.models.RubricRows` items :raises APIException: If no assignment with given id exists. (OBJECT_ID_NOT_FOUND) :raises APIException: If the assignment has no rubric. (OBJECT_ID_NOT_FOUND) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user is not allowed to see this is assignment. (INCORRECT_PERMISSION) """ assig = helpers.get_or_404(models.Assignment, assignment_id) auth.ensure_permission('can_see_assignments', assig.course_id) if not assig.rubric_rows: raise APIException( 'Assignment has no rubric', 'The assignment with id "{}" has no rubric'.format(assignment_id), APICodes.OBJECT_ID_NOT_FOUND, 404 ) return jsonify(assig.rubric_rows)
def get_courses() -> JSONResponse[t.Sequence[t.Mapping[str, t.Any]]]: """Return all :class:`.models.Course` objects the current user is a member of. .. :quickref: Course; Get all courses the current user is enrolled in. :returns: A response containing the JSON serialized courses :param str extended: If set to `true` all the assignments for each course are also included under the key `assignments`. :>jsonarr str role: The name of the role the current user has in this course. :>jsonarr ``**rest``: JSON serialization of :py:class:`psef.models.Course`. :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ def _get_rest(course: models.Course) -> t.Mapping[str, t.Any]: if request.args.get('extended') == 'true': return { 'assignments': course.get_all_visible_assignments(), **course.__to_json__(), } return course.__to_json__() return jsonify([{ 'role': c.name, **_get_rest(c.course), } for c in current_user.courses.values()])
def create_new_assignment(course_id: int) -> JSONResponse[models.Assignment]: """Create a new course for the given assignment. .. :quickref: Course; Create a new assignment in a course. :param int course_id: The course to create an assignment in. :<json str name: The name of the new assignment. :returns: The newly created assignment. :raises PermissionException: If the current user does not have the ``can_create_assignment`` permission (INCORRECT_PERMISSION). """ auth.ensure_permission('can_create_assignment', course_id) content = ensure_json_dict(request.get_json()) ensure_keys_in_dict(content, [('name', str)]) name = t.cast(str, content['name']) course = helpers.get_or_404(models.Course, course_id) if course.lti_course_id is not None: raise APIException('You cannot add assignments to a LTI course', f'The course "{course_id}" is a LTI course', APICodes.INVALID_STATE, 400) assig = models.Assignment(name=name, course=course, deadline=datetime.datetime.utcnow()) db.session.add(assig) db.session.commit() return jsonify(assig)
def user_patch_handle_reset_password() -> JSONResponse[t.Mapping[str, str]]: """Handle the ``reset_password`` type for the PATCH login route. :returns: A response with a jsonified mapping between ``access_token`` and a token which can be used to login. This is only key available. """ data = ensure_json_dict( request.get_json(), replace_log=lambda k, v: '<PASSWORD>' if 'password' in k else v ) ensure_keys_in_dict( data, [('new_password', str), ('token', str), ('user_id', int)] ) password = t.cast(str, data['new_password']) user_id = t.cast(int, data['user_id']) token = t.cast(str, data['token']) user = helpers.get_or_404(models.User, user_id) validate.ensure_valid_password(password, user=user) user.reset_password(token, password) db.session.commit() return jsonify( { 'access_token': flask_jwt.create_access_token( identity=user.id, fresh=True, ) } )
def add_snippet() -> JSONResponse[models.Snippet]: """Add or modify a :class:`.models.Snippet` by key. .. :quickref: Snippet; Add or modify a snippet. :returns: A response containing the JSON serialized snippet and return code 201. :<json str value: The new value of the snippet. :<json str key: The key of the new or existing snippet. :raises APIException: If the parameters "key" and/or "value" were not in the request. (MISSING_REQUIRED_PARAM) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not user snippets (INCORRECT_PERMISSION) """ content = ensure_json_dict(request.get_json()) ensure_keys_in_dict(content, [('value', str), ('key', str)]) value = t.cast(str, content['value']) snippet: t.Optional[models.Snippet] = models.Snippet.query.filter_by( user_id=current_user.id, key=content['key']).first() if snippet is None: snippet = models.Snippet(key=content['key'], value=content['value'], user=current_user) db.session.add(snippet) else: snippet.value = value db.session.commit() return jsonify(snippet, status_code=201)
def register_user_in_course( course_id: int, link_id: uuid.UUID) -> JSONResponse[t.Mapping[str, str]]: """Register as a new user, and directly enroll in a course. .. :quickref: Course; Register as a new user, and enroll in a course. :param course_id: The id of the course to which the registration link is connected. :param link_id: The id of the registration link. :>json access_token: The access token that the created user can use to login. """ link = _get_non_expired_link(course_id, link_id) if not link.allow_register: raise PermissionException( 'You are not allowed to register using this link', 'This link does not support registration', APICodes.INCORRECT_PERMISSION, 403) with get_from_map_transaction(get_json_dict_from_request()) as [get, _]: username = get('username', str) password = get('password', str) email = get('email', str) name = get('name', str) user = models.User.register_new_user(username=username, password=password, email=email, name=name) user.courses[link.course_id] = link.course_role db.session.commit() return jsonify({'access_token': user.make_access_token()})
def get_courses() -> JSONResponse[t.Sequence[t.Mapping[str, t.Any]]]: """Return all :class:`.models.Course` objects the current user is a member of. .. :quickref: Course; Get all courses the current user is enrolled in. :returns: A response containing the JSON serialized courses :param str extended: If set to ``true``, ``1`` or the empty string all the assignments and group sets for each course are also included under the key ``assignments`` and ``group_sets`` respectively. :>jsonarr str role: The name of the role the current user has in this course. :>jsonarr ``**rest``: JSON serialization of :py:class:`psef.models.Course`. :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ def _get_rest(course: models.Course) -> t.Mapping[str, t.Any]: if helpers.extended_requested(): snippets: t.Sequence[models.CourseSnippet] = [] if (current_user.has_permission(GPerm.can_use_snippets) and current_user.has_permission( CPerm.can_view_course_snippets, course_id=course.id)): snippets = course.snippets return { 'assignments': course.get_all_visible_assignments(), 'group_sets': course.group_sets, 'snippets': snippets, **course.__to_json__(), } return course.__to_json__() extra_loads: t.Optional[t.List[t.Any]] = None if helpers.extended_requested(): extra_loads = [ selectinload(models.Course.assignments), selectinload(models.Course.snippets), selectinload(models.Course.group_sets) ] # We don't use `helpers.get_or_404` here as preloading doesn't seem to work # when we do. user = models.User.query.filter_by(id=current_user.id).options([ selectinload(models.User.courses, ).selectinload( models.CourseRole._permissions, # pylint: disable=protected-access ), ]).first() assert user is not None return jsonify([{ 'role': user.courses[c.id].name, **_get_rest(c), } for c in helpers.get_in_or_error( models.Course, t.cast(models.DbColumn[int], models.Course.id), [cr.course_id for cr in user.courses.values()], extra_loads, )])
def search_users() -> JSONResponse[t.Sequence[models.User]]: """Search for a user by name and username. .. :quickref: User; Fuzzy search for a user by name and username. :param str q: The string to search for, all SQL wildcard are escaped and spaces are replaced by wildcards. :returns: A list of :py:class:`.models.User` objects that match the given query string. :raises APIException: If the query string less than 3 characters long. (INVALID_PARAM) :raises PermissionException: If the currently logged in user does not have the permission ``can_search_users``. (INCORRECT_PERMISSION) :raises RateLimitExceeded: If you hit this end point more than once per second. (RATE_LIMIT_EXCEEDED) """ ensure_keys_in_dict(request.args, [('q', str)]) query = t.cast(str, request.args.get('q')) if len(query) < 3: raise APIException( 'The search string should be at least 3 chars', f'The search string "{query}" is not 3 chars or longer.', APICodes.INVALID_PARAM, 400) likes = [ t.cast(t.Any, col).ilike('%{}%'.format( escape_like(query).replace(' ', '%'), )) for col in [models.User.name, models.User.username] ] return jsonify(models.User.query.filter(or_(*likes)).all())
def create_new_assignment(course_id: int) -> JSONResponse[models.Assignment]: """Create a new course for the given assignment. .. :quickref: Course; Create a new assignment in a course. :param int course_id: The course to create an assignment in. :<json str name: The name of the new assignment. :returns: The newly created assignment. """ with get_from_map_transaction(get_json_dict_from_request()) as [get, _]: name = get('name', str) course = helpers.get_or_404( models.Course, course_id, also_error=lambda c: c.virtual, ) assig = models.Assignment( name=name, course=course, is_lti=False, ) auth.AssignmentPermissions(assig).ensure_may_add() db.session.add(assig) db.session.commit() return jsonify(assig)
def get_all_course_assignments( course_id: int) -> JSONResponse[t.Sequence[models.Assignment]]: """Get all :class:`.models.Assignment` objects of the given :class:`.models.Course`. .. :quickref: Course; Get all assignments for single course. The returned assignments are sorted by deadline. :param int course_id: The id of the course :returns: A response containing the JSON serialized assignments sorted by deadline of the assignment. See :py:func:`.models.Assignment.__to_json__` for the way assignments are given. :raises APIException: If there is no course with the given id. (OBJECT_ID_NOT_FOUND) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not see assignments in the given course. (INCORRECT_PERMISSION) """ auth.ensure_permission(CPerm.can_see_assignments, course_id) course = helpers.get_or_404( models.Course, course_id, also_error=lambda c: c.virtual, ) return jsonify(course.get_all_visible_assignments())
def add_course() -> JSONResponse[models.Course]: """Add a new :class:`.models.Course`. .. :quickref: Course; Add a new course. :returns: A response containing the JSON serialization of the new course :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not create courses. (INCORRECT_PERMISSION) :raises APIException: If the parameter "name" is not in the request. (MISSING_REQUIRED_PARAM) """ content = get_json_dict_from_request() ensure_keys_in_dict(content, [('name', str)]) name = t.cast(str, content['name']) new_course = models.Course.create_and_add(name) db.session.commit() role = models.CourseRole.get_initial_course_role(new_course) current_user.courses[new_course.id] = role db.session.commit() return jsonify(new_course)
def get_all_roles() -> JSONResponse[t.Sequence[t.Mapping[str, t.Any]]]: """Get all global roles with their permissions .. :quickref: Role; Get all global roles with their permissions. :returns: A object as described in :py:meth:`.models.Role.__to_json__` with the following keys added: - ``perms``: All permissions of this role, as described in :py:meth:`.models.Role.get_all_permissions`. - ``own``: Is the given role the role of the current user. :raises PermissionException: If the current user does not have the ``can_manage_site_users`` permission. (INCORRECT_PERMISSION) """ roles: t.Sequence[models.Role] roles = models.Role.query.order_by(models.Role.name).all() # type: ignore res = [] for role in roles: json_role = role.__to_json__() json_role['perms'] = role.get_all_permissions() json_role['own'] = current_user.role_id == role.id res.append(json_role) return jsonify(res)
def get_course_data(course_id: int) -> JSONResponse[t.Mapping[str, t.Any]]: """Return course data for a given :class:`.models.Course`. .. :quickref: Course; Get data for a given course. :param int course_id: The id of the course :returns: A response containing the JSON serialized course :>json str role: The name of the role the current user has in this course. :>json ``**rest``: JSON serialization of :py:class:`psef.models.Course`. :raises APIException: If there is no course with the given id. (OBJECT_ID_NOT_FOUND) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ # TODO: Optimize this loop to a single query for course_role in current_user.courses.values(): if course_role.course_id == course_id: return jsonify({ 'role': course_role.name, **course_role.course.__to_json__(), }) raise APIException('Course not found', 'The course with id {} was not found'.format(course_id), APICodes.OBJECT_ID_NOT_FOUND, 404)
def user_patch_handle_reset_password() -> JSONResponse[t.Mapping[str, str]]: """Handle the ``reset_password`` type for the PATCH login route. :returns: A response with a jsonified mapping between ``access_token`` and a token which can be used to login. This is only key available. """ data = ensure_json_dict(request.get_json()) ensure_keys_in_dict(data, [('new_password', str), ('token', str), ('user_id', int)]) password = t.cast(str, data['new_password']) user_id = t.cast(int, data['user_id']) token = t.cast(str, data['token']) if password == '': raise APIException('Password should at least be 1 char', f'The password is {len(password)} chars long', APICodes.INVALID_PARAM, 400) user = helpers.get_or_404(models.User, user_id) user.reset_password(token, password) db.session.commit() return jsonify({ 'access_token': flask_jwt.create_access_token( identity=user.id, fresh=True, ) })
def post_file() -> JSONResponse[str]: """Temporarily store some data on the server. .. :quickref: File; Safe a file temporarily on the server. .. note:: The posted data will be removed after 60 seconds. :returns: A response with the JSON serialized name of the file as content and return code 201. :raises APIException: If the request is bigger than the maximum upload size. (REQUEST_TOO_LARGE) :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ if (request.content_length and request.content_length > app.config['MAX_FILE_SIZE']): raise APIException( 'Uploaded file is too big.', 'Request is bigger than maximum upload size of {}.'.format( app.config['MAX_FILE_SIZE']), APICodes.REQUEST_TOO_LARGE, 400) path, name = psef.files.random_file_path(True) FileStorage(request.stream).save(path) return jsonify(name, status_code=201)
def get_all_course_roles( course_id: int ) -> JSONResponse[t.Union[t.Sequence[models.CourseRole], t.Sequence[t.MutableMapping[str, t.Union[t.Mapping[ str, bool], bool]]]]]: """Get a list of all :class:`.models.CourseRole` objects of a given :class:`.models.Course`. .. :quickref: Course; Get all course roles for a single course. :param int course_id: The id of the course to get the roles for. :returns: An array of all course roles for the given course. :>jsonarr perms: All permissions this role has as returned by :py:meth:`.models.CourseRole.get_all_permissions`. :>jsonarrtype perms: :py:class:`t.Mapping[str, bool]` :>jsonarr bool own: True if the current course role is the current users course role. :>jsonarr ``**rest``: The course role as returned by :py:meth:`.models.CourseRole.__to_json__` :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not manage the course with the given id. (INCORRECT_PERMISSION) """ auth.ensure_permission(CPerm.can_edit_course_roles, course_id) course_roles: t.Sequence[models.CourseRole] course_roles = models.CourseRole.query.filter_by( course_id=course_id, hidden=False).order_by(models.CourseRole.name).all() if request.args.get('with_roles') == 'true': res = [] for course_role in course_roles: json_course = course_role.__to_json__() json_course['perms'] = CPerm.create_map( course_role.get_all_permissions()) json_course['own'] = current_user.courses[ course_role.course_id] == course_role res.append(json_course) return jsonify(res) return jsonify(course_roles)
def get_all_course_users( course_id: int ) -> JSONResponse[t.Union[t.List[_UserCourse], t.List[models.User]]]: """Return a list of all :class:`.models.User` objects and their :class:`.models.CourseRole` in the given :class:`.models.Course`. .. :quickref: Course; Get all users for a single course. :param int course_id: The id of the course :query string q: Search for users matching this query string. This will change the output to a list of users. :returns: A response containing the JSON serialized users and course roles :>jsonarr User: A member of the given course. :>jsonarrtype User: :py:class:`~.models.User` :>jsonarr CourseRole: The role that this user has. :>jsonarrtype CourseRole: :py:class:`~.models.CourseRole` """ auth.ensure_permission(CPerm.can_list_course_users, course_id) course = helpers.get_or_404(models.Course, course_id) if 'q' in request.args: @limiter.limit('1 per second', key_func=lambda: str(current_user.id)) def get_users_in_course() -> t.List[models.User]: query: str = request.args.get('q', '') base = course.get_all_users_in_course( include_test_students=False).from_self(models.User) return helpers.filter_users_by_name(query, base).all() return jsonify(get_users_in_course()) users = course.get_all_users_in_course(include_test_students=False) user_course: t.List[_UserCourse] user_course = [{ 'User': user, 'CourseRole': crole } for user, crole in users] return jsonify(sorted(user_course, key=lambda item: item['User'].name))
def get_course_permissions( ) -> JSONResponse[t.Union[_PermMap, t.Mapping[int, _PermMap]]]: """Get all the global :class:`.psef.models.Permission` or the value of a permission in all courses of the currently logged in :class:`.psef.models.User` .. :quickref: Permission; Get global permissions or all the course permissions for the current user. :qparam str type: The type of permissions to get. This can be ``global`` or ``course``. :qparam str permission: The permissions to get when getting course permissions. You can pass this parameter multiple times to get multiple permissions. :returns: The returning object depends on the given ``type``. If it was ``global`` a mapping between permissions name and a boolean indicating if the currently logged in user has this permissions is returned. If it was ``course`` such a mapping is returned for every course the user is enrolled in. So it is a mapping between course ids and permission mapping. The permissions given as ``permission`` query parameter are the only ones that are present in the permission map. """ ensure_keys_in_dict(request.args, [('type', str)]) permission_type = t.cast(str, request.args['type']).lower() if permission_type == 'global': return jsonify(current_user.get_all_permissions()) elif permission_type == 'course': # Make sure at least one permission is present ensure_keys_in_dict(request.args, [('permission', str)]) perms = t.cast(t.List[str], request.args.getlist('permission')) return jsonify(current_user.get_permissions_in_courses(perms)) else: raise APIException( 'Invalid permission type given', f'The given type "{permission_type}" is not "global" or "course"', APICodes.INVALID_PARAM, 400, )
def create_or_edit_registration_link( course_id: int) -> JSONResponse[models.CourseRegistrationLink]: """Create or edit a registration link. .. :quickref: Course; Create or edit a registration link for a course. :param course_id: The id of the course in which this link should enroll users. :>json id: The id of the link to edit, omit to create a new link. :>json role_id: The id of the role that users should get when registering with this link. :>json expiration_date: The date this link should stop working, this date should be in ISO8061 format without any timezone information, as it will be interpret as a UTC date. :returns: The created or edited link. """ course = helpers.get_or_404(models.Course, course_id, also_error=lambda c: c.virtual) auth.ensure_permission(CPerm.can_edit_course_users, course_id) with get_from_map_transaction( get_json_dict_from_request()) as [get, opt_get]: expiration_date = get('expiration_date', str) role_id = get('role_id', int) link_id = opt_get('id', str, default=None) if link_id is None: link = models.CourseRegistrationLink(course=course) db.session.add(link) else: link = helpers.filter_single_or_404( models.CourseRegistrationLink, models.CourseRegistrationLink.id == uuid.UUID(link_id), also_error=lambda l: l.course_id != course.id) link.course_role = helpers.get_or_404( models.CourseRole, role_id, also_error=lambda r: r.course_id != course.id) link.expiration_date = parsers.parse_datetime(expiration_date) if link.expiration_date < helpers.get_request_start_time(): helpers.add_warning('The link has already expired.', APIWarnings.ALREADY_EXPIRED) if link.course_role.has_permission(CPerm.can_edit_course_roles): helpers.add_warning( ('Users that register with this link will have the permission' ' to give themselves more permissions.'), APIWarnings.DANGEROUS_ROLE) db.session.commit() return jsonify(link)
def self_information( ) -> t.Union[JSONResponse[t.Union[models.User, t.MutableMapping[str, t.Any], t. Mapping[int, str]]], ExtendedJSONResponse[t.Union[models.User, t.MutableMapping[str, t. Any]]], ]: """Get the info of the currently logged in :class:`.models.User`. .. :quickref: User; Get information about the currently logged in user. :query type: If this is ``roles`` a mapping between course_id and role name will be returned, if this is ``extended`` the result of :py:meth:`.models.User.__extended_to_json__()` will be returned. If this is something else or not present the result of :py:meth:`.models.User.__to_json__()` will be returned. :query with_permissions: Setting this to true will add the key ``permissions`` to the user. The value will be a mapping indicating which global permissions this user has. :returns: A response containing the JSON serialized user :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ args = request.args if args.get('type') == 'roles': return jsonify( { role.course_id: role.name for role in current_user.courses.values() } ) elif helpers.extended_requested() or args.get('type') == 'extended': obj = current_user.__extended_to_json__() if request_arg_true('with_permissions'): obj['permissions'] = GPerm.create_map( current_user.get_all_permissions() ) return extended_jsonify(obj) return jsonify(current_user)
def get_group_sets( course_id: int) -> JSONResponse[t.Sequence[models.GroupSet]]: """Get the all the :class:`.models.GroupSet` objects in the given course. .. :quickref: Course; Get all group sets in the course. :param int course_id: The id of the course of which the group sets should be retrieved. :returns: A list of group sets. """ course = helpers.get_or_404(models.Course, course_id) auth.ensure_enrolled(course.id) return jsonify(course.group_sets)
def get_group_sets( course_id: int) -> JSONResponse[t.Sequence[models.GroupSet]]: """Get the all the group sets of a given course. .. :quickref: Course; Get all group sets in the course. :param int course_id: The id of the course of which the group sets should be retrieved. :returns: A list of group sets. """ course = helpers.get_or_404(models.Course, course_id) auth.CoursePermissions(course).ensure_may_see() return jsonify(course.group_sets)
def second_phase_lti_launch() -> helpers.JSONResponse[t.Mapping[str, t.Union[ str, models.Assignment, bool]]]: """Do the second part of an LTI launch. .. :quickref: LTI; Do the callback of a LTI launch. :query string Jwt: The JWT token that is the current LTI state. This token can only be acquired using the ``/lti/launch/1`` route. :>json assignment: The assignment that the LTI launch was for. :>json bool new_role_created: Was a new role created in the LTI launch. :>json access_token: A fresh access token for the current user. This value is not always available, this depends on internal state so you should simply check. :>json updated_email: The new email of the current user. This is value is also not always available, check! :raises APIException: If the given Jwt token is not valid. (INVALID_PARAM) """ try: launch_params = jwt.decode(flask.request.headers.get('Jwt', None), app.config['LTI_SECRET_KEY'], algorithm='HS512')['params'] except jwt.DecodeError: traceback.print_exc() raise errors.APIException( ('Decoding given JWT token failed, LTI is probably ' 'not configured right. Please contact your site admin.'), f'The decoding of "{flask.request.headers.get("Jwt")}" failed.', errors.APICodes.INVALID_PARAM, 400, ) lti = CanvasLTI(launch_params) user, new_token, updated_email = lti.ensure_lti_user() course = lti.get_course() assig = lti.get_assignment(user) lti.set_user_role(user) new_role_created = lti.set_user_course_role(user, course) db.session.commit() result: t.Mapping[str, t.Union[str, models.Assignment, bool]] result = { 'assignment': assig, 'new_role_created': new_role_created, } if new_token is not None: result['access_token'] = new_token if updated_email: result['updated_email'] = updated_email return helpers.jsonify(result)
def get_snippets() -> JSONResponse[t.Sequence[models.Snippet]]: """Get all snippets (:class:`.models.Snippet`) of the current :class:`.models.User`. .. :quickref: Snippet; Get all snippets for the currently logged in user. :returns: An array containing all snippets for the currently logged in user. :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the user can not use snippets. (INCORRECT_PERMISSION) """ return jsonify(models.Snippet.get_all_snippets(current_user))
def get_permissions_for_course( course_id: int, ) -> JSONResponse[t.Mapping[str, bool]]: """Get all the course :class:`.models.Permission` of the currently logged in :class:`.models.User` .. :quickref: Course; Get all the course permissions for the current user. :param int course_id: The id of the course of which the permissions should be retrieved. :returns: A mapping between the permission name and a boolean indicating if the currently logged in user has this permission. """ course = helpers.get_or_404(models.Course, course_id) return jsonify(current_user.get_all_permissions(course))
def self_information() -> t.Union[JSONResponse[t.Union[models.User, t.Mapping[ int, str]]], ExtendedJSONResponse[models.User], ]: """Get the info of the currently logged in :class:`.models.User`. .. :quickref: User; Get information about the currently logged in user. :query type: If this is ``roles`` a mapping between course_id and role name will be returned, if this is ``extended`` the result of :py:meth:`.models.User.__extended_to_json__()` will be returned. If this is something else or not present the result of :py:meth:`.models.User.__to_json__()` will be returned. :returns: A response containing the JSON serialized user :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) """ if request.args.get('type') == 'roles': return jsonify({ role.course_id: role.name for role in current_user.courses.values() }) elif request.args.get('type') == 'extended': return extended_jsonify(current_user) return jsonify(current_user)
def get_all_works_for_assignment( assignment_id: int ) -> t.Union[JSONResponse[WorkList], ExtendedJSONResponse[WorkList]]: """Return all :class:`.models.Work` objects for the given :class:`.models.Assignment`. .. :quickref: Assignment; Get all works for an assignment. :qparam boolean extended: Whether to get extended or normal :class:`.models.Work` objects. The default value is ``false``, you can enable extended by passing ``true``, ``1`` or an empty string. :param int assignment_id: The id of the assignment :returns: A response containing the JSON serialized submissions. :raises PermissionException: If there is no logged in user. (NOT_LOGGED_IN) :raises PermissionException: If the assignment is hidden and the user is not allowed to view it. (INCORRECT_PERMISSION) """ assignment = helpers.get_or_404(models.Assignment, assignment_id) auth.ensure_permission('can_see_assignments', assignment.course_id) if assignment.is_hidden: auth.ensure_permission( 'can_see_hidden_assignments', assignment.course_id ) obj = models.Work.query.filter_by( assignment_id=assignment_id, ).options(joinedload( models.Work.selected_items, )).order_by(t.cast(t.Any, models.Work.created_at).desc()) if not current_user.has_permission( 'can_see_others_work', course_id=assignment.course_id ): obj = obj.filter_by(user_id=current_user.id) extended = request.args.get('extended', 'false').lower() if extended in {'true', '1', ''}: obj = obj.options(undefer(models.Work.comment)) return extended_jsonify( obj.all(), use_extended=lambda obj: isinstance(obj, models.Work), ) else: return jsonify(obj.all())