def _login_user(user_id, redirect_path=None, tool_id=None): app.logger.info(f'_login_user: user_id={user_id}, redirect_path={redirect_path}, tool_id={tool_id}') authenticated = login_user(LoginSession(user_id), remember=True) and current_user.is_authenticated if authenticated: if redirect_path: response = redirect(location=f"{app.config['VUE_LOCALHOST_BASE_URL'] or ''}{redirect_path}") else: response = tolerant_jsonify(current_user.to_api_json()) canvas_api_domain = current_user.course.canvas_api_domain canvas = Canvas.find_by_domain(canvas_api_domain) canvas_course_id = current_user.course.canvas_course_id # Yummy cookies! key = f'{canvas_api_domain}|{canvas_course_id}' value = str(current_user.user_id) response.set_cookie( key=key, value=value, samesite='None', secure=True, ) app.logger.info(f'_login_user cookie: key={key} value={str(current_user.user_id)}') response.set_cookie( key=f'{canvas_api_domain}_supports_custom_messaging', value=str(canvas.supports_custom_messaging), samesite='None', secure=True, ) return response elif tool_id: raise UnauthorizedRequestError(f'Unauthorized user during {tool_id} LTI launch (user_id = {user_id})') else: return tolerant_jsonify({'message': f'User {user_id} failed to authenticate.'}, 403)
def app_status(): resp = { 'app': True, 'canvas': _ping_canvas(), 'db': _db_status(), } return tolerant_jsonify(resp)
def previews_callback(): if not verify_preview_service_authorization( request.headers.get('authorization')): raise UnauthorizedRequestError( 'Missing or invalid authorization header.') params = request.form if not (params.get('id', None) and params.get('status', None)): raise BadRequestError('Id and status fields required.') metadata = None try: if params.get('metadata'): metadata = json.loads(params['metadata']) except Exception as e: app.logger.error('Failed to parse JSON preview metadata.') app.logger.exception(e) raise BadRequestError('Could not parse JSON metadata.') asset = Asset.find_by_id(params['id']) if not asset: raise BadRequestError(f"Asset {params['id']} not found.") if asset.update_preview( preview_status=params.get('status'), thumbnail_url=params.get('thumbnail'), image_url=params.get('image'), pdf_url=params.get('pdf'), metadata=metadata, ): return tolerant_jsonify({'status': 'success'}) else: raise InternalServerError( f"Unable to update preview data (asset_id={params['id']}.")
def get_categories(): include_hidden = to_bool_or_none(request.args.get('includeHidden')) categories = Category.get_categories_by_course_id( course_id=current_user.course.id, include_hidden=include_hidden, ) return tolerant_jsonify(Category.to_decorated_json(categories))
def get_comments(asset_id): asset = Asset.find_by_id(asset_id=asset_id) if asset and can_view_asset(asset=asset, user=current_user): return tolerant_jsonify( _decorate_comments(Comment.get_comments(asset.id))) else: raise ResourceNotFoundError( 'Asset is either unavailable or non-existent.')
def get_asset(asset_id): asset = Asset.find_by_id(asset_id=asset_id) if asset and can_view_asset(asset=asset, user=current_user): if current_user.user not in asset.users: asset.increment_views(current_user.user) return tolerant_jsonify( asset.to_api_json(user_id=current_user.get_id())) else: raise ResourceNotFoundError(f'No asset found with id: {asset_id}')
def delete_comment(comment_id): comment = Comment.find_by_id(comment_id=comment_id) if comment and can_delete_comment(comment=comment, user=current_user): Comment.delete(comment_id=comment_id) return tolerant_jsonify({'message': f'Comment {comment_id} deleted'}), 200 else: raise ResourceNotFoundError( 'Comment is either unavailable or non-existent.')
def delete_asset(asset_id): asset = Asset.find_by_id(asset_id) if asset_id else None if not asset: raise ResourceNotFoundError('Asset not found.') if not can_update_asset(asset=asset, user=current_user): raise BadRequestError( 'To delete this asset you must own it or be a teacher in the course.' ) Asset.delete(asset_id=asset_id) return tolerant_jsonify({'message': f'Asset {asset_id} deleted'}), 200
def app_config(): return tolerant_jsonify({ 'squiggyEnv': app.config['SQUIGGY_ENV'], 'ebEnvironment': app.config['EB_ENVIRONMENT'] if 'EB_ENVIRONMENT' in app.config else None, 'timezone': app.config['TIMEZONE'], })
def create_category(): params = request.get_json() or request.form title = params.get('title') if not title: raise BadRequestError('Category creation requires title.') category = Category.create( canvas_assignment_name=title, course_id=current_user.course.id, title=title, ) return tolerant_jsonify(category.to_api_json())
def update_comment(comment_id): params = request.get_json() comment = Comment.find_by_id(comment_id=comment_id) if comment and can_update_comment(comment=comment, user=current_user): body = params.get('body', '').strip() if not body: raise BadRequestError('Comment body is required.') comment = Comment.update(body=body, comment_id=comment.id) return tolerant_jsonify(_decorate_comments([comment.to_api_json()])[0]) else: raise ResourceNotFoundError( 'Asset is either unavailable or non-existent.')
def app_version(): v = { 'version': version, } build_stats = load_json('config/build-summary.json') if build_stats: v.update(build_stats) else: v.update({ 'build': None, }) return tolerant_jsonify(v)
def app_config(): return tolerant_jsonify({ 'assetTypes': assets_type.enums, 'ebEnvironment': app.config['EB_ENVIRONMENT'] if 'EB_ENVIRONMENT' in app.config else None, 'emailAddressSupport': '*****@*****.**', # TODO: get email address 'canvasApiUrl': app.config['CANVAS_API_URL'], 'canvasBaseUrl': app.config['CANVAS_BASE_URL'], 'orderByOptions': assets_sort_by_options, 'squiggyEnv': app.config['SQUIGGY_ENV'], 'staticPath': app.config['STATIC_PATH'], 'timezone': app.config['TIMEZONE'], })
def logout(): response = tolerant_jsonify(current_user.to_api_json()) # Delete our custom cookies canvas_api_domain = current_user.course.canvas_api_domain keys = [ f'{canvas_api_domain}|{current_user.course.canvas_course_id}', f'{canvas_api_domain}_supports_custom_messaging', ] for key in keys: response.set_cookie(key, '', samesite='None', secure=True, expires=0) logout_user() return response
def dev_auth_login(): params = request.get_json() or {} if app.config['DEVELOPER_AUTH_ENABLED']: user_id = to_int(params.get('userId')) password = params.get('password') logger = app.logger if password != app.config['DEVELOPER_AUTH_PASSWORD']: logger.error('Dev auth: Wrong password') return tolerant_jsonify({'message': 'Invalid credentials'}, 401) return _login_user(user_id) else: raise ResourceNotFoundError('Unknown path')
def update_category(): params = request.get_json() category_id = params.get('categoryId') title = params.get('title') visible = to_bool_or_none(params.get('visible')) category = Category.find_by_id(category_id) if category_id else None if not category or not title: raise BadRequestError('Category update requires categoryId and title.') category = Category.update( category_id=category_id, title=title, visible=category.visible if visible is None else visible, ) return tolerant_jsonify(category.to_api_json())
def app_status(): def db_status(): try: db.session.execute('SELECT 1') return True except SQLAlchemyError: app.logger.exception('Database connection error') return False resp = { 'app': True, 'db': db_status(), } return tolerant_jsonify(resp)
def create_comment(): params = request.get_json() asset_id = params.get('assetId') asset = Asset.find_by_id(asset_id=asset_id) if asset and can_view_asset(asset=asset, user=current_user): body = params.get('body', '').strip() if not body: raise BadRequestError('Comment body is required.') parent_id = params.get('parentId') comment = Comment.create( asset=asset, user_id=current_user.user_id, body=body, parent_id=parent_id and int(parent_id), ) return tolerant_jsonify(_decorate_comments([comment.to_api_json()])[0]) else: raise ResourceNotFoundError( 'Asset is either unavailable or non-existent.')
def update_asset(): params = request.get_json() asset_id = params.get('assetId') category_id = params.get('categoryId') description = params.get('description') title = params.get('title') asset = Asset.find_by_id(asset_id) if asset_id else None if not asset or not title: raise BadRequestError('Asset update requires a valid ID and title.') if not can_update_asset(asset=asset, user=current_user): raise BadRequestError( 'To update an asset you must own it or be a teacher in the course.' ) asset = Asset.update( asset_id=asset_id, categories=category_id and [Category.find_by_id(category_id)], description=description, title=title, ) return tolerant_jsonify(asset.to_api_json(user_id=current_user.get_id()))
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())
def get_assets(): params = request.get_json() sort = _get(params, 'sort', None) offset = params.get('offset') limit = params.get('limit') filters = { 'asset_type': _get(params, 'assetType', None), 'category_id': _get(params, 'categoryId', None), 'has_comments': _get(params, 'hasComments', None), 'has_likes': _get(params, 'hasLikes', None), 'has_views': _get(params, 'hasViews', None), 'keywords': _get(params, 'keywords', None), 'order_by': _get(params, 'orderBy', 'recent'), 'owner_id': _get(params, 'userId', None), 'section_id': _get(params, 'sectionId', None), } results = Asset.get_assets(session=current_user, sort=sort, offset=offset, limit=limit, filters=filters) return tolerant_jsonify(results)
def delete(category_id): Category.delete(category_id) return tolerant_jsonify({'message': f'Category {category_id} deleted'}), 200
def remove_like_asset(asset_id): asset = _get_asset_for_like(asset_id) asset.remove_like(user=current_user) return tolerant_jsonify(asset.to_api_json(user_id=current_user.get_id()))
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) ])
def my_profile(): return tolerant_jsonify(current_user.to_api_json())
def logout(): _logout_user() return tolerant_jsonify(current_user.to_api_json())
def to_json(self): if self.message: return tolerant_jsonify({'message': self.message}) else: return ''
def handle_unexpected_error(error): app.logger.exception(error) return tolerant_jsonify( {'message': 'An unexpected server error occurred.'}), 500