def test_delete_asset_by_teacher(self, client, fake_auth, mock_asset, mock_category): """Authorized user can delete asset.""" course = Course.find_by_id(mock_asset.course_id) instructors = list(filter(lambda u: is_teaching(u), course.users)) fake_auth.login(instructors[0].id) self._verify_delete_asset(mock_asset.id, client)
def test_teacher_view_asset(self, client, fake_auth, mock_asset): """Authorized user can view asset.""" course = Course.find_by_id(mock_asset.course_id) instructors = list(filter(lambda u: is_teaching(u), course.users)) fake_auth.login(instructors[0].id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['id'] == mock_asset.id
def test_likes_multiple_users_increment_remove_likes_decrement( self, client, fake_auth, mock_asset): course_users = Course.find_by_id(mock_asset.course_id).users user_iterator = (user for user in course_users if user not in mock_asset.users) different_user_1 = next(user_iterator) different_user_2 = next(user_iterator) fake_auth.login(different_user_1.id) response = self._api_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 1 assert response.json['liked'] is True fake_auth.login(different_user_2.id) response = self._api_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 2 assert response.json['liked'] is True fake_auth.login(different_user_1.id) response = self._api_remove_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 1 assert response.json['liked'] is False fake_auth.login(different_user_2.id) response = self._api_remove_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 0 assert response.json['liked'] is False
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)
def test_teacher_download(self, app, client, fake_auth, mock_asset): """Authorized user can download asset.""" course = Course.find_by_id(mock_asset.course_id) instructors = list(filter(lambda u: is_teaching(u), course.users)) fake_auth.login(instructors[0].id) # TODO: Mock S3 so authorized user actually gets download. For now, 404 oddly indicates success. self._api_download_asset(app, asset_id=mock_asset.id, client=client, expected_status_code=404)
def test_errant_remove_like_does_not_decrement(self, client, fake_auth, mock_asset): course_users = Course.find_by_id(mock_asset.course_id).users different_user = next(user for user in course_users if user not in mock_asset.users) fake_auth.login(different_user.id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['likes'] == 0 assert asset['liked'] is False response = self._api_remove_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 0 assert response.json['liked'] is False
def test_likes_same_user_does_not_increment(self, client, fake_auth, mock_asset): course_users = Course.find_by_id(mock_asset.course_id).users different_user = next(user for user in course_users if user not in mock_asset.users) fake_auth.login(different_user.id) response = self._api_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 1 response = self._api_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 1
def test_like_asset_by_course_user(self, client, fake_auth, mock_asset): """Another user in the same court can like an asset.""" course_users = Course.find_by_id(mock_asset.course_id).users different_user = next(user for user in course_users if user not in mock_asset.users) fake_auth.login(different_user.id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['likes'] == 0 assert asset['liked'] is False response = self._api_like_asset(asset_id=mock_asset.id, client=client, expected_status_code=200) assert response.json['likes'] == 1 assert response.json['liked'] is True asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['likes'] == 1 assert asset['liked'] is True
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)
def test_increment_asset_view_count(self, client, fake_auth, mock_asset): course = Course.find_by_id(mock_asset.course_id) instructors = list(filter(lambda u: is_teaching(u), course.users)) # Instructor 1 increments view count. fake_auth.login(instructors[0].id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['views'] == 1 # Instructor 2 increments view count. fake_auth.login(instructors[1].id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['views'] == 2 # Repeat views do not increment, fake_auth.login(instructors[0].id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['views'] == 2 # Views by asset owners do not increment. fake_auth.login(mock_asset.users[0].id) asset = _api_get_asset(asset_id=mock_asset.id, client=client) assert asset['views'] == 2
def _create_courses(): for c in _test_canvas: canvas = Canvas( canvas_api_domain=c['canvas_api_domain'], api_key=c['api_key'], lti_key=c['lti_key'], lti_secret=c['lti_secret'], name=c['name'], ) db.session.add(canvas) std_commit(allow_test_environment=True) courses = [] for c in _test_courses: course = Course( active=c['active'], canvas_api_domain=c['canvas_api_domain'], canvas_course_id=c['canvas_course_id'], ) db.session.add(course) courses.append(course) std_commit(allow_test_environment=True) return courses
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}'