Пример #1
0
 def test_delete_group(self, client, fake_auth, mock_category):
     """Teacher can delete category."""
     teacher = User.find_by_canvas_user_id(9876543)
     fake_auth.login(teacher.id)
     assert client.delete(
         f'/api/category/{mock_category.id}/delete').status_code == 200
     assert client.get(
         f'/api/category/{mock_category.id}').status_code == 404
Пример #2
0
def mock_asset(app, db_session):
    course = Course.create(
        canvas_api_domain='bcourses.berkeley.edu',
        canvas_course_id=randrange(1000000),
    )
    category_hidden = Category.create(
        canvas_assignment_name='Just look into her false colored eyes',
        course_id=course.id,
        title='What a clown (visible=False)',
        canvas_assignment_id=98765,
        visible=False,
    )
    category_visible = Category.create(
        canvas_assignment_name='Just look into her false colored eyes',
        course_id=course.id,
        title='What a clown (visible=True)',
        canvas_assignment_id=98765,
        visible=True,
    )
    course = Course.query.order_by(Course.name).all()[0]
    canvas_user_id = str(randint(1000000, 9999999))
    user = User.create(
        canvas_course_role='Student',
        canvas_course_sections=[],
        canvas_email=f'{canvas_user_id}@berkeley.edu',
        canvas_enrollment_state='active',
        canvas_full_name=f'Student {canvas_user_id}',
        canvas_user_id=canvas_user_id,
        course_id=course.id,
    )
    unique_token = datetime.now().isoformat()
    asset = Asset.create(
        asset_type='link',
        categories=[category_hidden, category_visible],
        course_id=course.id,
        description=None,
        download_url=
        f"s3://{app.config['AWS_S3_BUCKET_FOR_ASSETS']}/asset/{course.id}_{canvas_user_id}_{unique_token}.pdf",
        title=f'Mock Asset created at {unique_token}',
        url=f'https://en.wikipedia.org/wiki/{unique_token}',
        users=[user],
    )
    for test_comment in _get_mock_comments():
        comment = Comment.create(asset=asset,
                                 body=test_comment['body'],
                                 user_id=user.id)
        for reply in test_comment.get('replies', []):
            reply = Comment.create(
                asset=asset,
                body=reply['body'],
                parent_id=comment.id,
                user_id=user.id,
            )
    std_commit(allow_test_environment=True)
    yield asset
    db_session.delete(asset)
    std_commit(allow_test_environment=True)
Пример #3
0
 def test_unauthorized(self, client, fake_auth, mock_category):
     """Denies unauthorized user."""
     student = User.find_by_canvas_user_id(8765432)
     fake_auth.login(student.id)
     self._api_update_category(
         client,
         category_id=mock_category.id,
         expected_status_code=401,
         title='I don\'t do too much talking',
     )
Пример #4
0
def _decorate_comments(comments):
    user_ids = []
    for comment in comments:
        user_ids.append(comment['userId'])
        for reply in comment.get('replies', []):
            user_ids.append(reply['userId'])
    users_by_id = {user.id: user for user in User.find_by_ids(user_ids)}
    for comment in comments:
        comment['user'] = users_by_id[comment['userId']].to_api_json()
        for reply in comment.get('replies', []):
            reply['user'] = users_by_id[reply['userId']].to_api_json()
    return comments
Пример #5
0
    def _verify_update_category(self, client, fake_auth, mock_category):
        teacher = User.find_by_canvas_user_id(9876543)
        fake_auth.login(teacher.id)

        title = "I've stopped my rambling, I don't do too much gambling these days"
        visible = not mock_category.visible
        api_json = self._api_update_category(client,
                                             category_id=mock_category.id,
                                             title=title,
                                             visible=visible)

        assert api_json['id'] == mock_category.id
        assert api_json['title'] == mock_category.title
        assert api_json['visible'] is visible
Пример #6
0
def _create_users(courses):
    course = courses[0]
    users = []
    for test_user in _test_users:
        user = User.create(
            canvas_course_role=test_user['canvas_course_role'],
            canvas_course_sections=[],
            canvas_email=test_user['canvas_email'],
            canvas_enrollment_state=test_user['canvas_enrollment_state'],
            canvas_full_name=test_user['canvas_full_name'],
            canvas_user_id=test_user['canvas_user_id'],
            course_id=course.id,
        )
        users.append(user)
    std_commit(allow_test_environment=True)
    return users
Пример #7
0
    def test_unauthorized(self, client):
        """No cookie auth for unauthorized user."""
        canvas_course_id = 1502871
        unauthorized_user = User.find_by_canvas_user_id(canvas_user_id=654321)
        assert unauthorized_user
        assert unauthorized_user.course.canvas_course_id != canvas_course_id

        canvas_api_domain = unauthorized_user.course.canvas_api_domain
        client.set_cookie('localhost',
                          f'{canvas_api_domain}|{canvas_course_id}',
                          str(unauthorized_user.id))
        self._api_assets_with_cookie_auth(
            canvas_api_domain=canvas_api_domain,
            canvas_course_id=canvas_course_id,
            client=client,
            expected_status_code=401,
        )
Пример #8
0
 def test_like_asset_by_non_course_user(self, client, fake_auth,
                                        mock_asset):
     """A user in a different course can't like an asset."""
     second_course = Course.create(
         canvas_api_domain='bcourses.berkeley.edu',
         canvas_course_id=randrange(1000000),
     )
     second_course_user = User.create(
         canvas_course_role='Student',
         canvas_enrollment_state='active',
         canvas_full_name='Doug Yule',
         canvas_user_id=randrange(1000000),
         course_id=second_course.id,
     )
     fake_auth.login(second_course_user.id)
     self._api_like_asset(asset_id=mock_asset.id,
                          client=client,
                          expected_status_code=404)
Пример #9
0
def create_asset():
    params = request.get_json() or request.form
    asset_type = params.get('type')
    category_id = params.get('categoryId')
    description = params.get('description')
    source = params.get('source')
    url = params.get('url')
    title = params.get('title', url)
    visible = params.get('visible', True)
    if not asset_type or not title:
        raise BadRequestError('Asset creation requires title and type.')

    if asset_type == 'link' and not url:
        raise BadRequestError('Link asset creation requires url.')

    if not current_user.course:
        raise BadRequestError('Course data not found')

    s3_attrs = {}
    if asset_type == 'file':
        file_upload = _get_upload_from_http_post()
        s3_attrs = Asset.upload_to_s3(
            filename=file_upload['name'],
            byte_stream=file_upload['byte_stream'],
            course_id=current_user.course.id,
        )

    asset = Asset.create(
        asset_type=asset_type,
        categories=category_id and [Category.find_by_id(category_id)],
        course_id=current_user.course.id,
        description=description,
        download_url=s3_attrs.get('download_url', None),
        mime=s3_attrs.get('content_type', None),
        source=source,
        title=title,
        url=url,
        users=[User.find_by_id(current_user.get_id())],
        visible=visible,
    )
    return tolerant_jsonify(asset.to_api_json())
Пример #10
0
    def test_authorized(self, client):
        """Cookie auth for authorized user."""
        canvas_course_id = 1502870
        authorized_user = User.find_by_canvas_user_id(canvas_user_id=654321)
        assert authorized_user
        assert authorized_user.course.canvas_course_id == canvas_course_id

        canvas_api_domain = authorized_user.course.canvas_api_domain
        client.set_cookie('localhost',
                          f'{canvas_api_domain}|{canvas_course_id}',
                          str(authorized_user.id))
        self._api_assets_with_cookie_auth(
            canvas_api_domain=canvas_api_domain,
            canvas_course_id=canvas_course_id,
            client=client,
        )
        # Finally, log out and verify that cookie has been removed.
        assert client.post('/api/auth/logout').status_code == 200
        self._api_assets_with_cookie_auth(
            canvas_api_domain=canvas_api_domain,
            canvas_course_id=canvas_course_id,
            client=client,
            expected_status_code=401,
        )
Пример #11
0
        def _create_user_at_lti_launch():
            _response = self._api_auth_lti_launch(
                client=client,
                custom_canvas_api_domain=canvas.canvas_api_domain,
                custom_canvas_course_id=canvas_course_id,
                custom_canvas_user_id=canvas_user_id,
                custom_external_tool_url=external_tool_url,
                lis_person_name_full=full_name,
                oauth_consumer_key=canvas.lti_key,
                roles='Student',
                tool_id=TOOL_ID_ASSET_LIBRARY,
            )
            std_commit(allow_test_environment=True)

            user = User.find_by_canvas_user_id(canvas_user_id)
            assert user
            assert user.canvas_full_name == full_name
            assert user.canvas_user_id == canvas_user_id

            course = user.course
            assert course.canvas_course_id == canvas_course_id
            assert course.engagement_index_url is None
            assert course.asset_library_url == external_tool_url
            return _response
Пример #12
0
 def test_unauthorized(self, client, fake_auth):
     """Denies unauthorized user."""
     student = User.find_by_canvas_user_id(8765432)
     fake_auth.login(student.id)
     self._api_create_category(client, expected_status_code=401)
Пример #13
0
def _lti_launch_authentication(tool_id):
    is_asset_library = tool_id == TOOL_ID_ASSET_LIBRARY
    is_engagement_index = tool_id == TOOL_ID_ENGAGEMENT_INDEX
    if not is_asset_library and not is_engagement_index:
        raise BadRequestError(f'Missing or invalid tool_id: {tool_id}')

    def _alpha_num(s):
        value = _str_strip(s)
        return value if value.isalnum() else None

    args = request.form
    lti_params = {}
    validation = {
        'custom_canvas_api_domain': _str_strip,
        'custom_canvas_course_id': _str_strip,
        'custom_canvas_user_id': _str_strip,
        'custom_external_tool_url': _canvas_external_tool_url,
        'lis_person_name_full': _str_strip,
        'oauth_consumer_key': _alpha_num,
        'oauth_nonce': _alpha_num,
        'oauth_signature_method': _str_strip,
        'oauth_timestamp': _str_strip,
        'oauth_version': _str_strip,
        'roles': _str_strip,
    }

    def _fetch(key):
        value = args.get(key)
        validate = validation[key]
        pass_headers = validate is _canvas_external_tool_url
        validated_value = validate(value, request.headers) if pass_headers else validate(value)
        if validated_value:
            lti_params[key] = validated_value
            return lti_params[key]
        else:
            app.logger.warning(f'Invalid \'{key}\' parameter in LTI launch: {value}')

    if all(_fetch(key) for key in validation.keys()):
        app.logger.info(f'LTI launch params passed basic validation: {lti_params}')
        canvas_api_domain = lti_params['custom_canvas_api_domain']
        canvas = Canvas.find_by_domain(canvas_api_domain)

        if not canvas:
            raise ResourceNotFoundError(f'Failed \'canvas\' lookup where canvas_api_domain = {canvas_api_domain}')

        if canvas.lti_key != lti_params['oauth_consumer_key']:
            raise BadRequestError(f'oauth_consumer_key does not match {canvas_api_domain} lti_key in squiggy db.')

        tool_provider = FlaskToolProvider.from_flask_request(
            request=request,
            secret=canvas.lti_secret,
        )
        valid_request = tool_provider.is_valid_request(LtiRequestValidator(canvas))

        if valid_request or app.config['TESTING']:
            # TODO: We do not want app.config['TESTING'] in this conditional. It is here because our tests are failing
            #       on HMAC-signature verification. Let's fix after we get a successful LTI launch.
            app.logger.info(f'FlaskToolProvider validated {canvas_api_domain} LTI launch request.')
        else:
            raise BadRequestError(f'LTI oauth failed in {canvas_api_domain} request')

        external_tool_url = lti_params['custom_external_tool_url']
        canvas_course_id = lti_params['custom_canvas_course_id']
        course = Course.find_by_canvas_course_id(
            canvas_api_domain=canvas_api_domain,
            canvas_course_id=canvas_course_id,
        )
        if course:
            course = Course.update(
                asset_library_url=external_tool_url if is_asset_library else course.asset_library_url,
                course_id=course.id,
                engagement_index_url=external_tool_url if is_engagement_index else course.engagement_index_url,
            )
            app.logger.info(f'Updated course during LTI launch: {course.to_api_json()}')
        else:
            course = Course.create(
                asset_library_url=external_tool_url if is_asset_library else None,
                canvas_api_domain=canvas_api_domain,
                canvas_course_id=canvas_course_id,
                engagement_index_url=external_tool_url if is_engagement_index else None,
                name=args.get('context_title'),
            )
            app.logger.info(f'Created course via LTI launch: {course.to_api_json()}')

        canvas_user_id = lti_params['custom_canvas_user_id']
        user = User.find_by_course_id(canvas_user_id=canvas_user_id, course_id=course.id)
        if user:
            app.logger.info(f'Found user during LTI launch: canvas_user_id={canvas_user_id}, course_id={course.id}')
        else:
            user = User.create(
                course_id=course.id,
                canvas_user_id=canvas_user_id,
                canvas_course_role=str(lti_params['roles']),
                canvas_enrollment_state=args.get('custom_canvas_enrollment_state') or 'active',
                canvas_full_name=lti_params['lis_person_name_full'],
                canvas_image=args.get('user_image'),  # TODO: Verify user_image.
                canvas_email=args.get('lis_person_contact_email_primary'),
                canvas_course_sections=None,  # TODO: Set by poller?
            )
            app.logger.info(f'Created user during LTI launch: canvas_user_id={canvas_user_id}')

        path = '/assets' if is_asset_library else '/engage'
        params = f'canvasApiDomain={canvas_api_domain}&canvasCourseId={canvas_course_id}'
        app.logger.info(f'LTI launch redirect: {path}?{params}')
        return user, f'{path}?{params}'
Пример #14
0
 def test_admin(self, client, fake_auth, authorized_user_id):
     """Cookies for authenticated user."""
     user = User.find_by_id(authorized_user_id)
     fake_auth.login(user.id)
     self._assert_cookie(client=client, user=user)
Пример #15
0
 def test_load_admin_user(self, authorized_user_id):
     """Returns authorization record to Flask-Login for recognized UID."""
     loaded_user = User.find_by_id(authorized_user_id)
     assert loaded_user.id == authorized_user_id
Пример #16
0
 def test_load_unknown_user(self):
     """Returns None to Flask-Login for unrecognized UID."""
     assert User.find_by_id(unauthorized_user_id) is None
Пример #17
0
 def __init__(self, user_id):
     self.user = User.find_by_id(user_id) if user_id else None
Пример #18
0
def get_users():
    return tolerant_jsonify([
        u.to_api_json()
        for u in User.get_users_by_course_id(course_id=current_user.course.id)
    ])
Пример #19
0
 def test_unauthorized(self, client, fake_auth, mock_category):
     """Denies unauthorized user."""
     student = User.find_by_canvas_user_id(8765432)
     fake_auth.login(student.id)
     assert client.delete(
         f'/api/category/{mock_category.id}/delete').status_code == 401
Пример #20
0
    def poll_users(self, db_course, api_course):  # noqa C901
        db_users_by_canvas_id = {u.canvas_user_id: u for u in db_course.users}

        api_sections = list(api_course.get_sections(include=['students']))
        app.logger.info(f'Retrieved {len(api_sections)} sections from Canvas: {_format_course(db_course)}')
        api_sections_by_user_id = {}
        for s in api_sections:
            for u in (s.students or []):
                user_id = u['id']
                if api_sections_by_user_id.get(user_id):
                    api_sections_by_user_id[user_id].append(s.name)
                else:
                    api_sections_by_user_id[user_id] = [s.name]

        api_users = list(api_course.get_users(include=['enrollments', 'avatar_url', 'email']))
        app.logger.info(f'Retrieved {len(api_users)} users from Canvas: {_format_course(db_course)}')
        api_user_ids = set()
        for u in api_users:
            api_user_ids.add(u.id)
            enrollment_state = 'active'
            course_role = 'Student'
            enrollment = next((e for e in u.enrollments if e['course_id'] == db_course.canvas_course_id), None)
            if not enrollment:
                enrollment_state = 'completed'
            else:
                if enrollment['enrollment_state'] in ['active', 'completed', 'inactive', 'invited', 'rejected']:
                    enrollment_state = enrollment['enrollment_state']
                if enrollment['role'] in ['Adv Designer', 'DesignerEnrollment', 'TaEnrollment', 'TeacherEnrollment']:
                    course_role = 'urn:lti:role:ims/lis/Instructor'

            user_attributes = {
                'canvas_course_role': course_role,
                'canvas_enrollment_state': enrollment_state,
                'canvas_full_name': u.name,
                'canvas_user_id': u.id,
                'course_id': db_course.id,
                'canvas_course_sections': api_sections_by_user_id.get(u.id, []),
                'canvas_email': getattr(u, 'email', None),
                'canvas_image': getattr(u, 'avatar_url', None),
            }
            db_user = db_users_by_canvas_id.get(u.id)
            if not db_user:
                app.logger.debug(f'Adding new user {u.id}: {_format_course(db_course)}')
                db_users_by_canvas_id[u.id] = User.create(**user_attributes)
            else:
                updated = False
                for key, value in user_attributes.items():
                    if getattr(db_user, key, None) != value:
                        setattr(db_user, key, value)
                        updated = True
                if updated:
                    app.logger.debug(f'Updating info for user {db_user.canvas_user_id}: {_format_course(db_course)}')
                    db.session.add(db_user)
                    std_commit()

        for db_user in db_users_by_canvas_id.values():
            if db_user.canvas_user_id not in api_user_ids and db_user.canvas_enrollment_state != 'inactive':
                app.logger.debug(f'Marking user {db_user.canvas_user_id} as inactive: {_format_course(db_course)}')
                db_user.canvas_enrollment_state = 'inactive'
                db.session.add(db_user)
        std_commit()
        return db_users_by_canvas_id