Merge pull request #95 from learnhouse/swve/eng-79-collections-should-be-org-scoped

Make Collections org-scoped
This commit is contained in:
Badr B 2023-06-19 18:00:37 +02:00 committed by GitHub
commit 7e680c7dbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 215 additions and 116 deletions

View file

@ -1,4 +1,3 @@
from calendar import c
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
import os import os
@ -51,7 +50,7 @@ def get_learnhouse_config() -> LearnHouseConfig:
env_site_description = os.environ.get("LEARNHOUSE_SITE_DESCRIPTION") env_site_description = os.environ.get("LEARNHOUSE_SITE_DESCRIPTION")
env_contact_email = os.environ.get("LEARNHOUSE_CONTACT_EMAIL") env_contact_email = os.environ.get("LEARNHOUSE_CONTACT_EMAIL")
env_domain = os.environ.get("LEARNHOUSE_DOMAIN") env_domain = os.environ.get("LEARNHOUSE_DOMAIN")
env_port = os.environ.get("LEARNHOUSE_PORT") os.environ.get("LEARNHOUSE_PORT")
env_ssl = os.environ.get("LEARNHOUSE_SSL") env_ssl = os.environ.get("LEARNHOUSE_SSL")
env_use_default_org = os.environ.get("LEARNHOUSE_USE_DEFAULT_ORG") env_use_default_org = os.environ.get("LEARNHOUSE_USE_DEFAULT_ORG")
env_allowed_origins = os.environ.get("LEARNHOUSE_ALLOWED_ORIGINS") env_allowed_origins = os.environ.get("LEARNHOUSE_ALLOWED_ORIGINS")

View file

@ -9,10 +9,7 @@ import React from 'react'
const CollectionAdminEditsArea = (props: any) => { const CollectionAdminEditsArea = (props: any) => {
const org_roles_values = ["admin", "owner"]; const org_roles_values = ["admin", "owner"];
const user_roles_values = ["role_admin"]; const user_roles_values = ["role_admin"];
console.log("props: ", props);
const auth: any = React.useContext(AuthContext); const auth: any = React.useContext(AuthContext);
console.log("auth: ", auth);
// this is amazingly terrible code, but gotta release that MVP // this is amazingly terrible code, but gotta release that MVP

View file

@ -22,7 +22,6 @@ export async function generateMetadata(
const access_token_cookie: any = cookieStore.get('access_token_cookie'); const access_token_cookie: any = cookieStore.get('access_token_cookie');
// Get Org context information // Get Org context information
const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] });
return { return {
title: `Collections — ${org.name}`, title: `Collections — ${org.name}`,
description: `Collections of courses from ${org.name}`, description: `Collections of courses from ${org.name}`,
@ -38,10 +37,9 @@ const CollectionsPage = async (params: any) => {
const cookieStore = cookies(); const cookieStore = cookies();
const access_token_cookie: any = cookieStore.get('access_token_cookie'); const access_token_cookie: any = cookieStore.get('access_token_cookie');
const orgslug = params.params.orgslug; const orgslug = params.params.orgslug;
const collections = await getOrgCollectionsWithAuthHeader(access_token_cookie ? access_token_cookie.value : null); const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
const org_id = org.org_id;
const collections = await getOrgCollectionsWithAuthHeader(org_id, access_token_cookie ? access_token_cookie.value : null);
return ( return (
<div className="max-w-7xl mx-auto px-4 py-10" > <div className="max-w-7xl mx-auto px-4 py-10" >

View file

@ -35,7 +35,8 @@ const OrgHomePage = async (params: any) => {
const access_token_cookie: any = cookieStore.get('access_token_cookie'); const access_token_cookie: any = cookieStore.get('access_token_cookie');
const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null); const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null);
const collections = await getOrgCollectionsWithAuthHeader(access_token_cookie ? access_token_cookie.value : null); const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
const collections = await getOrgCollectionsWithAuthHeader(org.org_id, access_token_cookie ? access_token_cookie.value : null);
// function to remove "course_" from the course_id // function to remove "course_" from the course_id

View file

@ -44,7 +44,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.7.18", "@types/node": "18.7.18",
"@types/react": "18.0.20", "@types/react": "18.2.8",
"@types/react-beautiful-dnd": "^13.1.2", "@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",
"@types/react-katex": "^3.0.0", "@types/react-katex": "^3.0.0",
@ -55,7 +55,7 @@
"eslint-config-next": "^13.0.6", "eslint-config-next": "^13.0.6",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "4.8.3" "typescript": "5.1.3"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@ -3599,9 +3599,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.0.20", "version": "18.2.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.20.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.8.tgz",
"integrity": "sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==", "integrity": "sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@ -8181,16 +8181,16 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.8.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
}, },
"engines": { "engines": {
"node": ">=4.2.0" "node": ">=14.17"
} }
}, },
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
@ -11084,9 +11084,9 @@
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
}, },
"@types/react": { "@types/react": {
"version": "18.0.20", "version": "18.2.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.20.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.8.tgz",
"integrity": "sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==", "integrity": "sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@ -14341,9 +14341,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "4.8.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz",
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==",
"dev": true "dev": true
}, },
"unbox-primitive": { "unbox-primitive": {

View file

@ -45,7 +45,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.7.18", "@types/node": "18.7.18",
"@types/react": "18.0.20", "@types/react": "18.2.8",
"@types/react-beautiful-dnd": "^13.1.2", "@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "18.0.6", "@types/react-dom": "18.0.6",
"@types/react-katex": "^3.0.0", "@types/react-katex": "^3.0.0",
@ -56,6 +56,6 @@
"eslint-config-next": "^13.0.6", "eslint-config-next": "^13.0.6",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "4.8.3" "typescript": "5.1.3"
} }
} }

View file

@ -19,7 +19,6 @@ export async function createCollection(collection: any) {
return res; return res;
} }
// Get a colletion by id // Get a colletion by id
export async function getCollectionById(collection_id: any) { export async function getCollectionById(collection_id: any) {
const result: any = await fetch(`${getAPIUrl()}collections/${collection_id}`, { next: { revalidate: 10 } }); const result: any = await fetch(`${getAPIUrl()}collections/${collection_id}`, { next: { revalidate: 10 } });
@ -41,8 +40,8 @@ export async function getOrgCollections() {
return res; return res;
} }
export async function getOrgCollectionsWithAuthHeader(access_token: string) { export async function getOrgCollectionsWithAuthHeader(org_id: string, access_token: string) {
const result: any = await fetch(`${getAPIUrl()}collections/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, { revalidate: 3 }, access_token)); const result: any = await fetch(`${getAPIUrl()}collections/org_id/${org_id}/page/1/limit/10`, RequestBodyWithAuthHeader("GET", null, { revalidate: 3 }, access_token));
const res = await errorHandling(result); const res = await errorHandling(result);
return res; return res;
} }

View file

@ -1,8 +1,7 @@
from urllib.request import Request
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from src.security.auth import * from src.security.auth import AuthJWT, authenticate_user
from src.services.users.users import * from src.services.users.users import PublicUser
router = APIRouter() router = APIRouter()
@ -41,8 +40,8 @@ async def login(
access_token = Authorize.create_access_token(subject=form_data.username) access_token = Authorize.create_access_token(subject=form_data.username)
refresh_token = Authorize.create_refresh_token(subject=form_data.username) refresh_token = Authorize.create_refresh_token(subject=form_data.username)
Authorize.set_refresh_cookies(refresh_token) Authorize.set_refresh_cookies(refresh_token)
# set cookies using fastapi # set cookies using fastapi
response.set_cookie(key="access_token_cookie", value=access_token , httponly=False) response.set_cookie(key="access_token_cookie", value=access_token, httponly=False)
user = PublicUser(**user.dict()) user = PublicUser(**user.dict())
result = { result = {

View file

@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.security.auth import get_current_user from src.security.auth import get_current_user
from fastapi import UploadFile
from src.services.blocks.block_types.imageBlock.images import create_image_block, get_image_block from src.services.blocks.block_types.imageBlock.images import create_image_block, get_image_block
from src.services.blocks.block_types.videoBlock.videoBlock import create_video_block, get_video_block from src.services.blocks.block_types.videoBlock.videoBlock import create_video_block, get_video_block
from src.services.blocks.block_types.pdfBlock.pdfBlock import create_pdf_block, get_pdf_block from src.services.blocks.block_types.pdfBlock.pdfBlock import create_pdf_block, get_pdf_block

View file

@ -1,5 +1,12 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.services.courses.activities.activities import * from src.services.courses.activities.activities import (
Activity,
create_activity,
get_activity,
get_activities,
update_activity,
delete_activity,
)
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.courses.activities.pdf import create_documentpdf_activity from src.services.courses.activities.pdf import create_documentpdf_activity
from src.services.courses.activities.video import ( from src.services.courses.activities.video import (
@ -7,6 +14,7 @@ from src.services.courses.activities.video import (
create_external_video_activity, create_external_video_activity,
create_video_activity, create_video_activity,
) )
from src.services.users.schemas.users import PublicUser
router = APIRouter() router = APIRouter()
@ -104,9 +112,7 @@ async def api_create_external_video_activity(
""" """
Create new activity Create new activity
""" """
return await create_external_video_activity( return await create_external_video_activity(request, current_user, external_video)
request, current_user, external_video
)
@router.post("/documentpdf") @router.post("/documentpdf")

View file

@ -1,14 +1,25 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.collections import Collection, create_collection, get_collection, get_collections, update_collection, delete_collection from src.services.courses.collections import (
Collection,
create_collection,
get_collection,
get_collections,
update_collection,
delete_collection,
)
router = APIRouter() router = APIRouter()
@router.post("/") @router.post("/")
async def api_create_collection(request: Request,collection_object: Collection, current_user: PublicUser = Depends(get_current_user)): async def api_create_collection(
request: Request,
collection_object: Collection,
current_user: PublicUser = Depends(get_current_user),
):
""" """
Create new Collection Create new Collection
""" """
@ -16,31 +27,52 @@ async def api_create_collection(request: Request,collection_object: Collection,
@router.get("/{collection_id}") @router.get("/{collection_id}")
async def api_get_collection(request: Request,collection_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_get_collection(
request: Request,
collection_id: str,
current_user: PublicUser = Depends(get_current_user),
):
""" """
Get single collection by ID Get single collection by ID
""" """
return await get_collection(request, collection_id, current_user) return await get_collection(request, collection_id, current_user)
@router.get("/page/{page}/limit/{limit}") @router.get("/org_id/{org_id}/page/{page}/limit/{limit}")
async def api_get_collections_by(request: Request,page: int, limit: int, current_user: PublicUser = Depends(get_current_user)): async def api_get_collections_by(
request: Request,
page: int,
limit: int,
org_id: str,
current_user: PublicUser = Depends(get_current_user),
):
""" """
Get collections by page and limit Get collections by page and limit
""" """
return await get_collections(request, page, limit) return await get_collections(request, org_id, current_user, page, limit)
@router.put("/{collection_id}") @router.put("/{collection_id}")
async def api_update_collection(request: Request,collection_object: Collection, collection_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_update_collection(
request: Request,
collection_object: Collection,
collection_id: str,
current_user: PublicUser = Depends(get_current_user),
):
""" """
Update collection by ID Update collection by ID
""" """
return await update_collection(request, collection_object, collection_id, current_user) return await update_collection(
request, collection_object, collection_id, current_user
)
@router.delete("/{collection_id}") @router.delete("/{collection_id}")
async def api_delete_collection(request: Request,collection_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_delete_collection(
request: Request,
collection_id: str,
current_user: PublicUser = Depends(get_current_user),
):
""" """
Delete collection by ID Delete collection by ID
""" """

View file

@ -1,5 +1,5 @@
from fastapi import Depends, APIRouter from fastapi import Depends, APIRouter, Request
from src.security.auth import * from src.security.auth import get_current_user
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserWithPassword from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserWithPassword
from src.services.users.users import create_user, delete_user, get_profile_metadata, get_user_by_userid, update_user, update_user_password from src.services.users.users import create_user, delete_user, get_profile_metadata, get_user_by_userid, update_user, update_user_password

View file

@ -1,13 +1,12 @@
from webbrowser import get
from config.config import get_learnhouse_config from config.config import get_learnhouse_config
from pydantic import BaseModel from pydantic import BaseModel
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt from jose import JWTError, jwt
from datetime import datetime, timedelta from datetime import datetime, timedelta
from src.services.users.schemas.users import AnonymousUser from src.services.users.schemas.users import AnonymousUser, PublicUser
from src.services.users.users import * from src.services.users.users import security_get_user, security_verify_password
from src.security.security import * from src.security.security import ALGORITHM, SECRET_KEY
from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth import AuthJWT
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")

View file

@ -2,7 +2,7 @@ from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import * from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
#### Classes #################################################### #### Classes ####################################################
@ -25,17 +25,23 @@ class CollectionInDB(Collection):
# CRUD # CRUD
#################################################### ####################################################
async def get_collection(request: Request,collection_id: str, current_user: PublicUser):
async def get_collection(
request: Request, collection_id: str, current_user: PublicUser
):
collections = request.app.db["collections"] collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": collection_id}) collection = await collections.find_one({"collection_id": collection_id})
# verify collection rights # verify collection rights
await verify_collection_rights(request, collection_id, current_user, "read") await verify_collection_rights(
request, collection_id, current_user, "read", collection["org_id"]
)
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist") status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
collection = Collection(**collection) collection = Collection(**collection)
@ -44,77 +50,97 @@ async def get_collection(request: Request,collection_id: str, current_user: Publ
courseids = [course for course in collection.courses] courseids = [course for course in collection.courses]
collection.courses = [] collection.courses = []
collection.courses = courses.find( collection.courses = courses.find({"course_id": {"$in": courseids}}, {"_id": 0})
{"course_id": {"$in": courseids}}, {'_id': 0})
collection.courses = [course for course in await collection.courses.to_list(length=100)]
collection.courses = [
course for course in await collection.courses.to_list(length=100)
]
return collection return collection
async def create_collection(request: Request,collection_object: Collection, current_user: PublicUser): async def create_collection(
request: Request, collection_object: Collection, current_user: PublicUser
):
collections = request.app.db["collections"] collections = request.app.db["collections"]
# find if collection already exists using name # find if collection already exists using name
isCollectionNameAvailable = await collections.find_one( isCollectionNameAvailable = await collections.find_one(
{"name": collection_object.name}) {"name": collection_object.name}
)
# TODO # TODO
# await verify_collection_rights("*", current_user, "create") # await verify_collection_rights("*", current_user, "create")
if isCollectionNameAvailable: if isCollectionNameAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection name already exists") status_code=status.HTTP_409_CONFLICT,
detail="Collection name already exists",
)
# generate collection_id with uuid4 # generate collection_id with uuid4
collection_id = str(f"collection_{uuid4()}") collection_id = str(f"collection_{uuid4()}")
collection = CollectionInDB( collection = CollectionInDB(collection_id=collection_id, **collection_object.dict())
collection_id=collection_id, **collection_object.dict())
collection_in_db = await collections.insert_one(collection.dict()) collection_in_db = await collections.insert_one(collection.dict())
if not collection_in_db: if not collection_in_db:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return collection.dict() return collection.dict()
async def update_collection(request: Request,collection_object: Collection, collection_id: str, current_user: PublicUser): async def update_collection(
request: Request,
collection_object: Collection,
collection_id: str,
current_user: PublicUser,
):
# verify collection rights # verify collection rights
await verify_collection_rights(request, collection_id, current_user, "update")
collections = request.app.db["collections"] collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": collection_id}) collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "update", collection["org_id"]
)
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist") status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
updated_collection = CollectionInDB( updated_collection = CollectionInDB(
collection_id=collection_id, **collection_object.dict()) collection_id=collection_id, **collection_object.dict()
)
await collections.update_one({"collection_id": collection_id}, { await collections.update_one(
"$set": updated_collection.dict()}) {"collection_id": collection_id}, {"$set": updated_collection.dict()}
)
return Collection(**updated_collection.dict()) return Collection(**updated_collection.dict())
async def delete_collection(request: Request,collection_id: str, current_user: PublicUser): async def delete_collection(
request: Request, collection_id: str, current_user: PublicUser
await verify_collection_rights(request, collection_id, current_user, "delete") ):
collections = request.app.db["collections"] collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": collection_id}) collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "delete", collection["org_id"]
)
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist") status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
isDeleted = await collections.delete_one({"collection_id": collection_id}) isDeleted = await collections.delete_one({"collection_id": collection_id})
@ -122,20 +148,36 @@ async def delete_collection(request: Request,collection_id: str, current_user: P
return {"detail": "collection deleted"} return {"detail": "collection deleted"}
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
#################################################### ####################################################
# Misc # Misc
#################################################### ####################################################
async def get_collections(request: Request,page: int = 1, limit: int = 10): async def get_collections(
## TODO : auth request: Request,
org_id: str,
current_user: PublicUser,
page: int = 1,
limit: int = 10,
):
collections = request.app.db["collections"] collections = request.app.db["collections"]
print(org_id)
# get all collections from database without ObjectId # get all collections from database without ObjectId
all_collections = collections.find({}).sort( all_collections = (
"name", 1).skip(10 * (page - 1)).limit(limit) collections.find({"org_id": org_id})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(limit)
)
await verify_collection_rights(request, "*", current_user, "read", org_id)
# create list of collections and include courses in each collection # create list of collections and include courses in each collection
collections_list = [] collections_list = []
@ -148,30 +190,50 @@ async def get_collections(request: Request,page: int = 1, limit: int = 10):
courses = request.app.db["courses"] courses = request.app.db["courses"]
collection.courses = [] collection.courses = []
collection.courses = courses.find( collection.courses = courses.find(
{"course_id": {"$in": collection_courses}}, {'_id': 0}) {"course_id": {"$in": collection_courses}}, {"_id": 0}
)
collection.courses = [course for course in await collection.courses.to_list(length=100)] collection.courses = [
course for course in await collection.courses.to_list(length=100)
]
return collections_list return collections_list
#### Security #################################################### #### Security ####################################################
async def verify_collection_rights(request: Request,collection_id: str, current_user: PublicUser, action: str): async def verify_collection_rights(
request: Request,
collection_id: str,
current_user: PublicUser,
action: str,
org_id: str,
):
collections = request.app.db["collections"] collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": collection_id}) collection = await collections.find_one({"collection_id": collection_id})
if not collection and action != "create": if not collection and action != "create" and collection_id != "*":
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist") status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, collection_id, collection["org_id"]) # Collections are public by default for now
if current_user.user_id == "anonymous" and action == "read":
return True
hasRoleRights = await verify_user_rights_with_roles(
request, action, current_user.user_id, collection_id, org_id
)
if not hasRoleRights: if not hasRoleRights:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Collection") status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have rights to this Collection",
)
return True return True
#### Security #################################################### #### Security ####################################################

View file

@ -6,8 +6,8 @@ from src.services.courses.activities.activities import ActivityInDB
from src.services.courses.thumbnails import upload_thumbnail from src.services.courses.thumbnails import upload_thumbnail
from src.services.users.schemas.users import AnonymousUser from src.services.users.schemas.users import AnonymousUser
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import * from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, status, UploadFile from fastapi import HTTPException, Request, status, UploadFile
from datetime import datetime from datetime import datetime
#### Classes #################################################### #### Classes ####################################################

View file

@ -1,5 +1,4 @@
import json import json
from typing import Optional
from uuid import uuid4 from uuid import uuid4
from src.services.orgs.logos import upload_org_logo from src.services.orgs.logos import upload_org_logo
from src.services.orgs.schemas.orgs import ( from src.services.orgs.schemas.orgs import (
@ -9,7 +8,7 @@ from src.services.orgs.schemas.orgs import (
) )
from src.services.users.schemas.users import UserOrganization from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.security.security import * from src.security.security import verify_user_rights_with_roles
from fastapi import HTTPException, UploadFile, status, Request from fastapi import HTTPException, UploadFile, status, Request
@ -96,14 +95,13 @@ async def update_org(
orgs = request.app.db["organizations"] orgs = request.app.db["organizations"]
org = await orgs.find_one({"org_id": org_id}) await orgs.find_one({"org_id": org_id})
updated_org = OrganizationInDB(org_id=org_id, **org_object.dict()) updated_org = OrganizationInDB(org_id=org_id, **org_object.dict())
# update org # update org
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()}) await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
return updated_org.dict() return updated_org.dict()
@ -115,19 +113,15 @@ async def update_org_logo(
orgs = request.app.db["organizations"] orgs = request.app.db["organizations"]
org = await orgs.find_one({"org_id": org_id}) await orgs.find_one({"org_id": org_id})
name_in_disk = await upload_org_logo(logo_file) name_in_disk = await upload_org_logo(logo_file)
# update org # update org
org = await orgs.update_one({"org_id": org_id}, {"$set": {"logo": name_in_disk}}) await orgs.update_one({"org_id": org_id}, {"$set": {"logo": name_in_disk}})
return {"detail": "Logo updated"} return {"detail": "Logo updated"}
async def delete_org(request: Request, org_id: str, current_user: PublicUser): async def delete_org(request: Request, org_id: str, current_user: PublicUser):
await verify_org_rights(request, org_id, current_user, "delete") await verify_org_rights(request, org_id, current_user, "delete")

View file

@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from src.security.security import *
#### Classes #################################################### #### Classes ####################################################

View file

@ -2,7 +2,6 @@ from typing import Literal
from uuid import uuid4 from uuid import uuid4
from src.services.roles.schemas.roles import Role, RoleInDB from src.services.roles.schemas.roles import Role, RoleInDB
from src.services.users.schemas.users import PublicUser from src.services.users.schemas.users import PublicUser
from src.security.security import *
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from datetime import datetime from datetime import datetime
@ -12,7 +11,6 @@ async def create_role(request: Request, role_object: Role, current_user: PublicU
await verify_user_permissions_on_roles(request, current_user, "create", None) await verify_user_permissions_on_roles(request, current_user, "create", None)
# create the role object in the database and return the object # create the role object in the database and return the object
role_id = "role_" + str(uuid4()) role_id = "role_" + str(uuid4())
@ -27,6 +25,7 @@ async def create_role(request: Request, role_object: Role, current_user: PublicU
return role return role
async def read_role(request: Request, role_id: str, current_user: PublicUser): async def read_role(request: Request, role_id: str, current_user: PublicUser):
roles = request.app.db["roles"] roles = request.app.db["roles"]
@ -36,7 +35,10 @@ async def read_role(request: Request, role_id: str, current_user: PublicUser):
return role return role
async def update_role(request: Request, role_id: str, role_object: Role, current_user: PublicUser):
async def update_role(
request: Request, role_id: str, role_object: Role, current_user: PublicUser
):
roles = request.app.db["roles"] roles = request.app.db["roles"]
await verify_user_permissions_on_roles(request, current_user, "update", role_id) await verify_user_permissions_on_roles(request, current_user, "update", role_id)
@ -44,10 +46,15 @@ async def update_role(request: Request, role_id: str, role_object: Role, current
role_object.updated_at = datetime.now() role_object.updated_at = datetime.now()
# Update the role object in the database and return the object # Update the role object in the database and return the object
updated_role = RoleInDB(**await roles.find_one_and_update({"role_id": role_id}, {"$set": role_object.dict()}, return_document=True)) updated_role = RoleInDB(
**await roles.find_one_and_update(
{"role_id": role_id}, {"$set": role_object.dict()}, return_document=True
)
)
return updated_role return updated_role
async def delete_role(request: Request, role_id: str, current_user: PublicUser): async def delete_role(request: Request, role_id: str, current_user: PublicUser):
roles = request.app.db["roles"] roles = request.app.db["roles"]
@ -58,9 +65,16 @@ async def delete_role(request: Request, role_id: str, current_user: PublicUser):
return deleted_role return deleted_role
#### Security #################################################### #### Security ####################################################
async def verify_user_permissions_on_roles(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], role_id: str | None):
async def verify_user_permissions_on_roles(
request: Request,
current_user: PublicUser,
action: Literal["create", "read", "update", "delete"],
role_id: str | None,
):
request.app.db["users"] request.app.db["users"]
roles = request.app.db["roles"] roles = request.app.db["roles"]
@ -68,7 +82,8 @@ async def verify_user_permissions_on_roles(request: Request, current_user: Publi
if not current_user: if not current_user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Roles : Not authenticated") status_code=status.HTTP_401_UNAUTHORIZED, detail="Roles : Not authenticated"
)
if action == "create": if action == "create":
if "owner" in [org.org_role for org in current_user.orgs]: if "owner" in [org.org_role for org in current_user.orgs]: