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 _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
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 _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}'
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