feat: init roles & user reengineering

This commit is contained in:
swve 2023-03-19 15:01:27 +01:00
parent 40404cc852
commit 9384cbe85d
38 changed files with 671 additions and 409 deletions

10
app.py
View file

@ -8,7 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi_jwt_auth.exceptions import AuthJWTException
from src.services.mocks.initial import create_initial_data # from src.services.mocks.initial import create_initial_data
######################## ########################
# Pre-Alpha Version 0.1.0 # Pre-Alpha Version 0.1.0
@ -64,8 +64,8 @@ async def root():
return {"Message": "Welcome to LearnHouse ✨"} return {"Message": "Welcome to LearnHouse ✨"}
@app.get("/initial_data") # @app.get("/initial_data")
async def initial_data(request: Request): # async def initial_data(request: Request):
await create_initial_data(request) # await create_initial_data(request)
return {"Message": "Initial data created 🤖"} # return {"Message": "Initial data created 🤖"}

View file

@ -1,11 +1,11 @@
"use client"; "use client";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import React from "react"; import React from "react";
import { Header } from "../../../../../components/UI/Header"; import { Header } from "@components/UI/Header";
import Layout from "../../../../../components/UI/Layout"; import Layout from "@components/UI/Layout";
import { Title } from "../../../../../components/UI/Elements/Styles/Title"; import { Title } from "@components/UI/Elements/Styles/Title";
import { createNewCourse } from "../../../../../services/courses/courses"; import { createNewCourse } from "@services/courses/courses";
import { getOrganizationContextInfo } from "../../../../../services/orgs"; import { getOrganizationContextInfo } from "@services/orgs";
const NewCoursePage = (params: any) => { const NewCoursePage = (params: any) => {
const router = useRouter(); const router = useRouter();

View file

@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext from passlib.context import CryptContext
from jose import JWTError, jwt from jose import JWTError, jwt
from datetime import datetime, timedelta from datetime import datetime, timedelta
from src.services.users import * from src.services.users.users import *
from fastapi import Cookie, FastAPI from fastapi import Cookie, FastAPI
from src.services.security import * from src.services.security import *
from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth import AuthJWT

View file

@ -2,7 +2,7 @@ from urllib.request import Request
from fastapi import Depends, APIRouter, HTTPException, status, Request from fastapi import Depends, APIRouter, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from src.dependencies.auth import * from src.dependencies.auth import *
from src.services.users import * from src.services.users.users import *
from datetime import timedelta from datetime import timedelta
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse

View file

@ -5,7 +5,7 @@ from src.services.blocks.imageBlock.images import create_image_file, get_image_f
from src.services.blocks.videoBlock.videos import create_video_file, get_video_file from src.services.blocks.videoBlock.videos import create_video_file, get_video_file
from src.services.blocks.pdfBlock.documents import create_document_file, get_document_file from src.services.blocks.pdfBlock.documents import create_document_file, get_document_file
from src.services.blocks.quizBlock.quizBlock import create_quiz_block, get_quiz_block_answers, get_quiz_block_options, quizBlock from src.services.blocks.quizBlock.quizBlock import create_quiz_block, get_quiz_block_answers, get_quiz_block_options, quizBlock
from src.services.users import PublicUser from src.services.users.users import PublicUser
router = APIRouter() router = APIRouter()

View file

@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, Request, UploadFile, Form from fastapi import APIRouter, Depends, Request, UploadFile, Form
from src.services.courses.chapters import CourseChapter, CourseChapterMetaData, create_coursechapter, delete_coursechapter, get_coursechapter, get_coursechapters, get_coursechapters_meta, update_coursechapter, update_coursechapters_meta from src.services.courses.chapters import CourseChapter, CourseChapterMetaData, create_coursechapter, delete_coursechapter, get_coursechapter, get_coursechapters, get_coursechapters_meta, update_coursechapter, update_coursechapters_meta
from src.services.users import PublicUser from src.services.users.users import PublicUser
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
router = APIRouter() router = APIRouter()

View file

@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
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

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, get_courses_orgslug, update_course, delete_course, update_course_thumbnail from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, get_courses_orgslug, update_course, delete_course, update_course_thumbnail
from src.services.users import PublicUser from src.services.users.users import PublicUser
router = APIRouter() router = APIRouter()

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, Request
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.houses import House, HouseInDB, create_house, get_house, get_houses, update_house, delete_house from src.services.houses import House, HouseInDB, create_house, get_house, get_houses, update_house, delete_house
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
router = APIRouter() router = APIRouter()

View file

@ -2,7 +2,7 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
router = APIRouter() router = APIRouter()

View file

@ -1,14 +1,15 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.dependencies.auth import get_current_user from src.dependencies.auth import get_current_user
from src.services.roles import Role, create_role, delete_role, get_role, get_roles, update_role from src.services.roles.schemas.roles import Role
from src.services.users import PublicUser, User from src.services.roles.roles import create_role, delete_role, read_role, update_role
from src.services.users.schemas.users import PublicUser, User
router = APIRouter() router = APIRouter()
@router.post("/") @router.post("/")
async def api_create_role(request: Request,role_object: Role, current_user: PublicUser = Depends(get_current_user)): async def api_create_role(request: Request, role_object: Role, current_user: PublicUser = Depends(get_current_user)):
""" """
Create new role Create new role
""" """
@ -16,31 +17,23 @@ async def api_create_role(request: Request,role_object: Role, current_user: Publ
@router.get("/{role_id}") @router.get("/{role_id}")
async def api_get_role(request: Request,role_id: str): async def api_get_role(request: Request, role_id: str, current_user: PublicUser = Depends(get_current_user)):
""" """
Get single role by role_id Get single role by role_id
""" """
return await get_role(request, role_id) return await read_role(request, role_id, current_user)
@router.get("/page/{page}/limit/{limit}")
async def api_get_role_by(request: Request,page: int, limit: int):
"""
Get roles by page and limit
"""
return await get_roles(request, page, limit)
@router.put("/{role_id}") @router.put("/{role_id}")
async def api_update_role(request: Request,role_object: Role, role_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_update_role(request: Request, role_object: Role, role_id: str, current_user: PublicUser = Depends(get_current_user)):
""" """
Update role by role_id Update role by role_id
""" """
return await update_role(request, role_object, role_id, current_user) return await update_role(request, role_id, role_object, current_user)
@router.delete("/{role_id}") @router.delete("/{role_id}")
async def api_delete_role(request: Request,role_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_delete_role(request: Request, role_id: str, current_user: PublicUser = Depends(get_current_user)):
""" """
Delete role by ID Delete role by ID
""" """

View file

@ -2,7 +2,9 @@ from fastapi import Depends, FastAPI, APIRouter
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel from pydantic import BaseModel
from src.dependencies.auth import * from src.dependencies.auth import *
from src.services.users import * 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, read_user, update_user, update_user_password
@ -25,13 +27,6 @@ async def api_get_current_user_metadata(request: Request,current_user: User = De
return await get_profile_metadata(request , current_user.dict()) return await get_profile_metadata(request , current_user.dict())
@router.get("/username/{username}")
async def api_get_user_by_username(request: Request, username: str):
"""
Get single user by username
"""
return await get_user(request, username)
@router.get("/user_id/{user_id}") @router.get("/user_id/{user_id}")
async def api_get_user_by_userid(request: Request,user_id: str): async def api_get_user_by_userid(request: Request,user_id: str):
@ -42,32 +37,32 @@ async def api_get_user_by_userid(request: Request,user_id: str):
@router.post("/") @router.post("/")
async def api_create_user(request: Request,user_object: UserWithPassword): async def api_create_user(request: Request,user_object: UserWithPassword, org_id: str ):
""" """
Create new user Create new user
""" """
return await create_user(request, user_object) return await create_user(request, None, user_object, org_id)
@router.delete("/user_id/{user_id}") @router.delete("/user_id/{user_id}")
async def api_delete_user(request: Request, user_id: str): async def api_delete_user(request: Request, user_id: str, current_user: PublicUser = Depends(get_current_user)):
""" """
Delete user by ID Delete user by ID
""" """
return await delete_user(request, user_id) return await delete_user(request, current_user, user_id)
@router.put("/user_id/{user_id}") @router.put("/user_id/{user_id}")
async def api_update_user(request: Request, user_object: User, user_id: str): async def api_update_user(request: Request, user_object: User, user_id: str, current_user: PublicUser = Depends(get_current_user)):
""" """
Update user by ID Update user by ID
""" """
return await update_user(request, user_id, user_object) return await update_user(request, user_id, user_object, current_user)
@router.put("/password/user_id/{user_id}") @router.put("/password/user_id/{user_id}")
async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm): async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm, current_user: PublicUser = Depends(get_current_user)):
""" """
Update user password by ID Update user password by ID
""" """
return await update_user_password(request, user_id, passwordChangeForm) return await update_user_password(request,current_user, user_id, passwordChangeForm)

View file

@ -7,7 +7,7 @@ from fastapi import HTTPException, Request, status
from pydantic import BaseModel from pydantic import BaseModel
from src.services.courses.chapters import get_coursechapters_meta from src.services.courses.chapters import get_coursechapters_meta
from src.services.users import PublicUser from src.services.users.users import PublicUser
#### Classes #################################################### #### Classes ####################################################

View file

@ -4,7 +4,7 @@ from fastapi import HTTPException, status, UploadFile, Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
import os import os
from src.services.users import PublicUser from src.services.users.users import PublicUser
class PhotoFile(BaseModel): class PhotoFile(BaseModel):

View file

@ -4,7 +4,7 @@ from fastapi import HTTPException, status, UploadFile, Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
import os import os
from src.services.users import PublicUser from src.services.users.users import PublicUser
class DocumentFile(BaseModel): class DocumentFile(BaseModel):

View file

@ -3,7 +3,7 @@ from uuid import uuid4
from fastapi import Request from fastapi import Request
from pydantic import BaseModel from pydantic import BaseModel
from src.services.blocks.blocks import Block from src.services.blocks.blocks import Block
from src.services.users import PublicUser from src.services.users.users import PublicUser
class option(BaseModel): class option(BaseModel):

View file

@ -4,7 +4,7 @@ import os
from fastapi import HTTPException, status, UploadFile,Request from fastapi import HTTPException, status, UploadFile,Request
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from src.services.users import PublicUser from src.services.users.users import PublicUser
class VideoFile(BaseModel): class VideoFile(BaseModel):

View file

@ -6,7 +6,7 @@ from pydantic import BaseModel
from src.services.courses.courses import Course, CourseInDB from src.services.courses.courses import Course, CourseInDB
from src.services.courses.lectures.lectures import Lecture, LectureInDB from src.services.courses.lectures.lectures import Lecture, LectureInDB
from src.services.security import verify_user_rights_with_roles from src.services.security import verify_user_rights_with_roles
from src.services.users import PublicUser from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File from fastapi import HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File

View file

@ -2,7 +2,7 @@ import json
from typing import List from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
from src.services.security import * from src.services.security import *
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime from datetime import datetime

View file

@ -4,7 +4,7 @@ from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.courses.lectures.lectures import LectureInDB from src.services.courses.lectures.lectures import LectureInDB
from src.services.courses.thumbnails import upload_thumbnail from src.services.courses.thumbnails import upload_thumbnail
from src.services.users import PublicUser from src.services.users.users import PublicUser
from src.services.security import * from src.services.security import *
from fastapi import HTTPException, status, UploadFile from fastapi import HTTPException, status, UploadFile
from datetime import datetime from datetime import datetime

View file

@ -1,6 +1,6 @@
from pydantic import BaseModel from pydantic import BaseModel
from src.services.security import verify_user_rights_with_roles from src.services.security import verify_user_rights_with_roles
from src.services.users import PublicUser, User from src.services.users.schemas.users import PublicUser, User
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
from uuid import uuid4 from uuid import uuid4
from datetime import datetime from datetime import datetime

View file

@ -13,7 +13,6 @@ async def upload_video(video_file, lecture_id):
f.close() f.close()
except Exception as e: except Exception as e:
print(e)
return {"message": "There was an error uploading the file"} return {"message": "There was an error uploading the file"}
finally: finally:
video_file.file.close() video_file.file.close()

View file

@ -1,7 +1,7 @@
from pydantic import BaseModel from pydantic import BaseModel
from src.services.security import verify_user_rights_with_roles from src.services.security import verify_user_rights_with_roles
from src.services.courses.lectures.uploads.videos import upload_video from src.services.courses.lectures.uploads.videos import upload_video
from src.services.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.lectures.lectures import LectureInDB from src.services.courses.lectures.lectures import LectureInDB
from fastapi import HTTPException, status, UploadFile, Request from fastapi import HTTPException, status, UploadFile, Request
from uuid import uuid4 from uuid import uuid4
@ -48,9 +48,7 @@ async def create_video_lecture(request: Request,name: str, coursechapter_id: st
# upload video # upload video
if video_file: if video_file:
print("uploading video")
# get videofile format # get videofile format
await upload_video(video_file, lecture_id) await upload_video(video_file, lecture_id)
# todo : choose whether to update the chapter or not # todo : choose whether to update the chapter or not

View file

@ -9,7 +9,6 @@ async def upload_thumbnail(thumbnail_file, name_in_disk):
f.close() f.close()
except Exception as e: except Exception as e:
print(e)
return {"message": "There was an error uploading the file"} return {"message": "There was an error uploading the file"}
finally: finally:
thumbnail_file.file.close() thumbnail_file.file.close()

View file

@ -2,7 +2,7 @@ import json
from typing import List from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
from src.services.security import * from src.services.security import *
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime from datetime import datetime

View file

@ -1,186 +1,186 @@
import requests # import requests
from datetime import datetime # from datetime import datetime
from fileinput import filename # from fileinput import filename
from pprint import pprint # from pprint import pprint
from uuid import uuid4 # from uuid import uuid4
from fastapi import File, UploadFile, Request # from fastapi import File, UploadFile, Request
from src.services.courses.chapters import CourseChapter, create_coursechapter # from src.services.courses.chapters import CourseChapter, create_coursechapter
from src.services.courses.lectures.lectures import Lecture, create_lecture # from src.services.courses.lectures.lectures import Lecture, create_lecture
from src.services.courses.thumbnails import upload_thumbnail # from src.services.courses.thumbnails import upload_thumbnail
from src.services.users import PublicUser, User, UserInDB, UserWithPassword # from src.services.users.users import PublicUser, User, UserInDB, UserWithPassword
from src.services.orgs import OrganizationInDB, Organization, create_org # from src.services.orgs import OrganizationInDB, Organization, create_org
from src.services.roles import Permission, Elements, RolePolicy, create_role # from src.services.roles.schemas.roles import Permission, Elements, RolePolicy, create_role
from src.services.users import create_user # from src.services.users.users import create_user
from src.services.courses.courses import Course, CourseInDB, create_course # from src.services.courses.courses import Course, CourseInDB, create_course
from src.services.roles import Role # from src.services.roles.roles import Role
from faker import Faker # from faker import Faker
async def create_initial_data(request: Request): # async def create_initial_data(request: Request):
fake = Faker(['en_US']) # fake = Faker(['en_US'])
fake_multilang = Faker( # fake_multilang = Faker(
['en_US', 'de_DE', 'ja_JP', 'es_ES', 'it_IT', 'pt_BR', 'ar_PS']) # ['en_US', 'de_DE', 'ja_JP', 'es_ES', 'it_IT', 'pt_BR', 'ar_PS'])
# Create users # # Create users
######################################## # ########################################
database_users = request.app.db["users"] # database_users = request.app.db["users"]
await database_users.delete_many({}) # await database_users.delete_many({})
users = [] # users = []
admin_user = UserWithPassword( # admin_user = UserWithPassword(
username=f"admin", # username=f"admin",
email=f"admin@admin.admin", # email=f"admin@admin.admin",
password="admin", # password="admin",
user_type="isOwner", # user_type="isOwner",
) # )
admin_user = await create_user(request, admin_user) # admin_user = await create_user(request, admin_user)
for i in range(0, 20): # for i in range(0, 20):
user = UserWithPassword( # user = UserWithPassword(
username=fake.simple_profile()['username'], # username=fake.simple_profile()['username'],
email=fake.email(), # email=fake.email(),
password=fake.password(), # password=fake.password(),
user_type="isOwner", # user_type="isOwner",
full_name=fake.name(), # full_name=fake.name(),
) # )
users.append(user) # users.append(user)
for user in users: # for user in users:
await create_user(request, user) # await create_user(request, user)
# find admin user # # find admin user
users = request.app.db["users"] # users = request.app.db["users"]
admin_user = await users.find_one({"username": "admin"}) # admin_user = await users.find_one({"username": "admin"})
if admin_user: # if admin_user:
admin_user = UserInDB(**admin_user) # admin_user = UserInDB(**admin_user)
current_user = PublicUser(**admin_user.dict()) # current_user = PublicUser(**admin_user.dict())
else: # else:
raise Exception("Admin user not found") # raise Exception("Admin user not found")
# Create organizations # # Create organizations
######################################## # ########################################
database_orgs = request.app.db["organizations"] # database_orgs = request.app.db["organizations"]
await database_orgs.delete_many({}) # await database_orgs.delete_many({})
organizations = [] # organizations = []
for i in range(0, 5): # for i in range(0, 5):
company = fake.company() # company = fake.company()
# remove whitespace and special characters and make lowercase # # remove whitespace and special characters and make lowercase
slug = ''.join(e for e in company if e.isalnum()).lower() # slug = ''.join(e for e in company if e.isalnum()).lower()
org = Organization( # org = Organization(
name=company, # name=company,
description=fake.unique.text(), # description=fake.unique.text(),
email=fake.unique.email(), # email=fake.unique.email(),
slug=slug, # slug=slug,
) # )
organizations.append(org) # organizations.append(org)
await create_org(request, org, current_user) # await create_org(request, org, current_user)
# Create roles # # Create roles
######################################## # ########################################
database_roles = request.app.db["roles"] # database_roles = request.app.db["roles"]
await database_roles.delete_many({}) # await database_roles.delete_many({})
roles = [] # roles = []
admin_role = Role( # admin_role = Role(
name="admin", # name="admin",
description="admin", # description="admin",
policies=[RolePolicy(permissions=Permission( # policies=[RolePolicy(permissions=Permission(
action_create=True, # action_create=True,
action_read=True, # action_read=True,
action_update=True, # action_update=True,
action_delete=True, # action_delete=True,
), # ),
elements=Elements( # elements=Elements(
courses=["*"], # courses=["*"],
users=["*"], # users=["*"],
houses=["*"], # houses=["*"],
collections=["*"], # collections=["*"],
organizations=["*"], # organizations=["*"],
coursechapters=["*"], # coursechapters=["*"],
lectures=["*"], # lectures=["*"],
))], # ))],
linked_users=[admin_user.user_id], # linked_users=[admin_user.user_id],
) # )
roles.append(admin_role) # roles.append(admin_role)
await create_role(request, admin_role, current_user) # await create_role(request, admin_role, current_user)
# Generate Courses and CourseChapters # # Generate Courses and CourseChapters
######################################## # ########################################
database_courses = request.app.db["courses"] # database_courses = request.app.db["courses"]
database_chapters = request.app.db["coursechapters"] # database_chapters = request.app.db["coursechapters"]
await database_courses.delete_many({}) # await database_courses.delete_many({})
await database_chapters.delete_many({}) # await database_chapters.delete_many({})
courses = [] # courses = []
orgs = request.app.db["organizations"] # orgs = request.app.db["organizations"]
if await orgs.count_documents({}) > 0: # if await orgs.count_documents({}) > 0:
for org in await orgs.find().to_list(length=100): # for org in await orgs.find().to_list(length=100):
for i in range(0, 5): # for i in range(0, 5):
# get image in BinaryIO format from unsplash and save it to disk # # get image in BinaryIO format from unsplash and save it to disk
image = requests.get( # image = requests.get(
"https://source.unsplash.com/random/800x600") # "https://source.unsplash.com/random/800x600")
with open("thumbnail.jpg", "wb") as f: # with open("thumbnail.jpg", "wb") as f:
f.write(image.content) # f.write(image.content)
course_id = f"course_{uuid4()}" # course_id = f"course_{uuid4()}"
course = CourseInDB( # course = CourseInDB(
name=fake_multilang.unique.sentence(), # name=fake_multilang.unique.sentence(),
description=fake_multilang.unique.text(), # description=fake_multilang.unique.text(),
mini_description=fake_multilang.unique.text(), # mini_description=fake_multilang.unique.text(),
thumbnail="thumbnail", # thumbnail="thumbnail",
org_id=org['org_id'], # org_id=org['org_id'],
learnings=[fake_multilang.unique.sentence() # learnings=[fake_multilang.unique.sentence()
for i in range(0, 5)], # for i in range(0, 5)],
public=True, # public=True,
chapters=[], # chapters=[],
course_id=course_id, # course_id=course_id,
creationDate=str(datetime.now()), # creationDate=str(datetime.now()),
updateDate=str(datetime.now()), # updateDate=str(datetime.now()),
authors=[current_user.user_id], # authors=[current_user.user_id],
) # )
courses = request.app.db["courses"] # courses = request.app.db["courses"]
name_in_disk = f"test_mock{course_id}.jpeg" # name_in_disk = f"test_mock{course_id}.jpeg"
image = requests.get( # image = requests.get(
"https://source.unsplash.com/random/800x600") # "https://source.unsplash.com/random/800x600")
with open(f"content/uploads/img/{name_in_disk}", "wb") as f: # with open(f"content/uploads/img/{name_in_disk}", "wb") as f:
f.write(image.content) # f.write(image.content)
course.thumbnail = name_in_disk # course.thumbnail = name_in_disk
course = CourseInDB(**course.dict()) # course = CourseInDB(**course.dict())
course_in_db = await courses.insert_one(course.dict()) # course_in_db = await courses.insert_one(course.dict())
# create chapters # # create chapters
for i in range(0, 5): # for i in range(0, 5):
coursechapter = CourseChapter( # coursechapter = CourseChapter(
name=fake_multilang.unique.sentence(), # name=fake_multilang.unique.sentence(),
description=fake_multilang.unique.text(), # description=fake_multilang.unique.text(),
lectures=[], # lectures=[],
) # )
coursechapter = await create_coursechapter(request,coursechapter, course_id, current_user) # coursechapter = await create_coursechapter(request,coursechapter, course_id, current_user)
pprint(coursechapter) # pprint(coursechapter)
if coursechapter: # if coursechapter:
# create lectures # # create lectures
for i in range(0, 5): # for i in range(0, 5):
lecture = Lecture( # lecture = Lecture(
name=fake_multilang.unique.sentence(), # name=fake_multilang.unique.sentence(),
type="dynamic", # type="dynamic",
content={}, # content={},
) # )
lecture = await create_lecture(request,lecture, coursechapter['coursechapter_id'], current_user) # lecture = await create_lecture(request,lecture, coursechapter['coursechapter_id'], current_user)

View file

@ -2,7 +2,7 @@ import json
from typing import List from typing import List
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users import PublicUser, User from src.services.users.users import PublicUser, User
from src.services.security import * from src.services.security import *
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime from datetime import datetime

View file

@ -1,167 +0,0 @@
import json
from typing import List
from uuid import uuid4
from pydantic import BaseModel
from src.services.users import PublicUser, User
from src.services.security import *
from src.services.houses import House
from fastapi import HTTPException, status, Request
from datetime import datetime
#### Classes ####################################################
class Permission(BaseModel):
action_create: bool
action_read: bool
action_update: bool
action_delete: bool
class Elements(BaseModel):
courses: List[str]
users: List[str]
houses: List[str]
collections: List[str]
organizations: List[str]
coursechapters: List[str]
lectures : List[str]
class RolePolicy(BaseModel):
permissions: Permission
elements: Elements
class Role(BaseModel):
name: str
description: str
policies: List[RolePolicy]
linked_users: List[str]
class RoleInDB(Role):
role_id: str
creationDate: str
updateDate: str
#### Classes ####################################################
async def get_role(request: Request,role_id: str):
roles = request.app.db["roles"]
role = await roles.find_one({"role_id": role_id})
if not role:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Role does not exist")
role = Role(**role)
return role
async def create_role(request: Request,role_object: Role, current_user: PublicUser):
roles = request.app.db["roles"]
# find if house already exists using name
isRoleAvailable = await roles.find_one({"name": role_object.name})
if isRoleAvailable:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Role name already exists")
await verify_user_permissions_on_roles(request, "create", current_user)
# generate house_id with uuid4
role_id = str(f"role_{uuid4()}")
role = RoleInDB(role_id=role_id, creationDate=str(datetime.now()),
updateDate=str(datetime.now()), **role_object.dict())
role_in_db = await roles.insert_one(role.dict())
if not role_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
return role.dict()
async def update_role(request: Request,role_object: Role, role_id: str, current_user: PublicUser):
# verify house rights
await verify_user_permissions_on_roles(request, "update", current_user)
roles = request.app.db["roles"]
role = await roles.find_one({"role_id": role_id})
if not role:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Role does not exist")
updated_role = RoleInDB(
role_id=role_id, updateDate=str(datetime.now()), creationDate=role["creationDate"], **role_object.dict())
await roles.update_one({"role_id": role_id}, {"$set": updated_role.dict()})
return RoleInDB(**updated_role.dict())
async def delete_role(request: Request,role_id: str, current_user: PublicUser):
# verify house rights
await verify_user_permissions_on_roles(request, "delete", current_user)
roles = request.app.db["roles"]
role = await roles.find_one({"role_id": role_id})
if not role:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Role does not exist")
isDeleted = await roles.delete_one({"role_id": role_id})
if isDeleted:
return {"detail": "Role deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
async def get_roles(request: Request,page: int = 1, limit: int = 10):
roles = request.app.db["roles"]
# get all roles from database
all_roles = roles.find().sort("name", 1).skip(10 * (page - 1)).limit(limit)
return [json.loads(json.dumps(role, default=str)) for role in await all_roles.to_list(length=limit)]
#### Security ####################################################
async def verify_user_permissions_on_roles(request: Request,action: str, current_user: PublicUser):
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
isOwner = "owner" in user["user_type"]
isEditor = "editor" in user["user_type"]
# TODO: verify for all actions.
if action == "delete":
if isEditor:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role")
if not isOwner and not isEditor:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role")
return True
#### Security ####################################################

View file

112
src/services/roles/roles.py Normal file
View file

@ -0,0 +1,112 @@
import json
from typing import List, Literal
from uuid import uuid4
from pydantic import BaseModel
from src.services.roles.schemas.roles import Role, RoleInDB
from src.services.users.schemas.users import PublicUser, User
from src.services.security import *
from src.services.houses import House
from fastapi import HTTPException, status, Request
from datetime import datetime
async def create_role(request: Request, role_object: Role, current_user: PublicUser):
roles = request.app.db["roles"]
await verify_user_permissions_on_roles(request, current_user, "create", None)
# create the role object in the database and return the object
role_id = "role_" + str(uuid4())
role = RoleInDB(
role_id=role_id,
created_at=str(datetime.now()),
updated_at=str(datetime.now()),
**role_object.dict()
)
await roles.insert_one(role.dict())
return role
async def read_role(request: Request, role_id: str, current_user: PublicUser):
roles = request.app.db["roles"]
await verify_user_permissions_on_roles(request, current_user, "read", role_id)
role = RoleInDB(**await roles.find_one({"role_id": role_id}))
return role
async def update_role(request: Request, role_id: str, role_object: Role, current_user: PublicUser):
roles = request.app.db["roles"]
await verify_user_permissions_on_roles(request, current_user, "update", role_id)
role_object.updated_at = datetime.now()
# 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))
return updated_role
async def delete_role(request: Request, role_id: str, current_user: PublicUser):
roles = request.app.db["roles"]
await verify_user_permissions_on_roles(request, current_user, "delete", role_id)
# Delete the role object in the database and return the object
deleted_role = RoleInDB(**await roles.find_one_and_delete({"role_id": role_id}))
return deleted_role
#### Security ####################################################
async def verify_user_permissions_on_roles(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], role_id: str | None):
users = request.app.db["users"]
roles = request.app.db["roles"]
# If current user is not authenticated
if not current_user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Roles : Not authenticated")
if action == "create":
if "owner" in [org.org_role for org in current_user.orgs]:
return True
if role_id is not None:
role = RoleInDB(**await roles.find_one({"role_id": role_id}))
if action == "read":
if "owner" in [org.org_role for org in current_user.orgs]:
return True
for org in current_user.orgs:
if org.org_id == role.org_id:
return True
if action == "update":
for org in current_user.orgs:
# If the user is an owner of the organization
if org.org_id == role.org_id:
if org.org_role == "owner" or org.org_role == "editor":
return True
# Can't update a global role
if role.org_id == "*":
return False
if action == "delete":
for org in current_user.orgs:
# If the user is an owner of the organization
if org.org_id == role.org_id:
if org.org_role == "owner":
return True
# Can't delete a global role
if role.org_id == "*":
return False
#### Security ####################################################

View file

View file

@ -0,0 +1,41 @@
from typing import List, Literal
from pydantic import BaseModel
# Database Models
class Permission(BaseModel):
action_create: bool
action_read: bool
action_update: bool
action_delete: bool
def __getitem__(self, item):
return getattr(self, item)
class Elements(BaseModel):
courses: Permission
users: Permission
houses: Permission
collections: Permission
organizations: Permission
coursechapters: Permission
lectures: Permission
def __getitem__(self, item):
return getattr(self, item)
class Role(BaseModel):
name: str
description: str
elements : Elements
org_id: str | Literal["*"]
class RoleInDB(Role):
role_id: str
created_at: str
updated_at: str

View file

@ -1,6 +1,10 @@
from pprint import pprint
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from passlib.context import CryptContext from passlib.context import CryptContext
from passlib.hash import pbkdf2_sha256 from passlib.hash import pbkdf2_sha256
from src.services.roles.schemas.roles import RoleInDB
from src.services.users.schemas.users import User, UserInDB
### 🔒 JWT ############################################################## ### 🔒 JWT ##############################################################
@ -27,34 +31,41 @@ async def security_verify_password(plain_password: str, hashed_password: str):
### 🔒 Roles checking ############################################################## ### 🔒 Roles checking ##############################################################
async def verify_user_rights_with_roles(request: Request,action: str, user_id: str, element_id: str): async def verify_user_rights_with_roles(request: Request, action: str, user_id: str, element_id: str):
""" """
Check if the user has the right to perform the action on the element Check if the user has the right to perform the action on the element
""" """
roles = request.app.db["roles"] roles = request.app.db["roles"]
users = request.app.db["users"]
# find data where user_id is in linked_users or * is in linked_users # Check if the user is an admin
user_roles_cursor = roles.find({"$or": [{"linked_users": user_id}, {"linked_users": "*"}]}) user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id}))
# Organization roles verification
for org in user.orgs:
# TODO: Check if the org_id (user) is the same as the org_id (element)
# Check if user is owner or reader of the organization
if org.org_role == ("owner" or "editor"):
return True
user_roles = [] # If the user is not an owner or a editor, check if he has a role
# Get user roles
user_roles = user.roles
# Info: permission actions are: read, create, delete, update # TODO: Check if the org_id of the role is the same as the org_id of the element using find
for role in await user_roles_cursor.to_list(length=100): # Check if user has the right role
user_roles.append(role)
for role in user_roles: element_type = await check_element_type(element_id)
for policy in role['policies']: for role_id in user_roles:
element = policy["elements"][await check_element_type(element_id)] role = RoleInDB(**await roles.find_one({"role_id": role_id}))
permission_state = policy["permissions"][f'action_{action}'] if role.elements[element_type][f"action_{action}"]:
return True
## # If no role is found, raise an error
if ("*" in element or element_id in element) and (permission_state is True): raise HTTPException(
return True status_code=status.HTTP_403_FORBIDDEN, detail="You don't have the right to perform this action")
else:
return False
async def check_element_type(element_id): async def check_element_type(element_id):

View file

@ -17,6 +17,8 @@ class User(BaseModel):
verified: bool | None = False verified: bool | None = False
user_type: str | None = None user_type: str | None = None
bio: str | None = None bio: str | None = None
orgs: list
class UserWithPassword(User): class UserWithPassword(User):
password: str password: str
@ -24,9 +26,11 @@ class UserWithPassword(User):
class PublicUser(User): class PublicUser(User):
user_id: str user_id: str
orgs: list
creationDate: str creationDate: str
updateDate: str updateDate: str
class PasswordChangeForm(BaseModel): class PasswordChangeForm(BaseModel):
old_password: str old_password: str
new_password: str new_password: str
@ -86,7 +90,6 @@ async def get_profile_metadata(request: Request, user):
user_roles_list = [] user_roles_list = []
for role in await user_roles.to_list(length=100): for role in await user_roles.to_list(length=100):
print(role)
user_roles_list.append(Role(**role)) user_roles_list.append(Role(**role))
return { return {
@ -142,7 +145,6 @@ async def update_user(request: Request, user_id: str, user_object: User):
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist") status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
# TODO : fix this # TODO : fix this
# okay if username is not changed # okay if username is not changed
@ -151,9 +153,8 @@ async def update_user(request: Request, user_id: str, user_object: User):
else: else:
if isUsernameAvailable: if isUsernameAvailable:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Username already used") status_code=status.HTTP_409_CONFLICT, detail="Username already used")
updated_user = {"$set": user_object.dict()} updated_user = {"$set": user_object.dict()}
users.update_one({"user_id": user_id}, updated_user) users.update_one({"user_id": user_id}, updated_user)

View file

View file

View file

@ -0,0 +1,48 @@
from typing import Literal
from pydantic import BaseModel
class UserOrganization(BaseModel):
org_id: str
org_role: Literal['owner', 'editor', 'member']
class User(BaseModel):
username: str
email: str
full_name: str | None = None
avatar_url: str | None = None
bio: str | None = None
class UserWithPassword(User):
password: str
class UserInDB(User):
user_id: str
password: str
verified: bool | None = False
disabled: bool | None = False
orgs: list[UserOrganization] = []
roles: list[str] = []
creation_date: str
update_date: str
class PublicUser(User):
user_id: str
orgs: list[UserOrganization] = []
roles: list[str] = []
creation_date: str
update_date: str
# Forms ####################################################
class PasswordChangeForm(BaseModel):
old_password: str
new_password: str

232
src/services/users/users.py Normal file
View file

@ -0,0 +1,232 @@
from datetime import datetime
from typing import Literal
from uuid import uuid4
from fastapi import HTTPException, Request, status
from src.services.roles.schemas.roles import Role
from src.services.security import security_hash_password, security_verify_password
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserOrganization, UserWithPassword, UserInDB
async def create_user(request: Request, current_user: PublicUser | None, user_object: UserWithPassword, org_id: str):
users = request.app.db["users"]
isUserAvailable = await users.find_one({"username": user_object.username})
if isUserAvailable:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Username already exists")
# Generate user_id with uuid4
user_id = str(f"user_{uuid4()}")
# Check if the requesting user is authenticated
if current_user is not None:
# Verify rights
await verify_user_rights_on_user(request, current_user, "create", user_id)
# Set the username & hash the password
user_object.username = user_object.username.lower()
user_object.password = await security_hash_password(user_object.password)
# Create initial orgs list with the org_id passed in
orgs = [UserOrganization(org_id=org_id, org_role="member")]
# Give role
roles = ["role_1"]
# Create the user
user = UserInDB(user_id=user_id, creation_date=str(datetime.now()),
update_date=str(datetime.now()), orgs=orgs, roles=roles, **user_object.dict())
# Insert the user into the database
await users.insert_one(user.dict())
return User(**user.dict())
async def read_user(request: Request, current_user: PublicUser, user_id: str):
users = request.app.db["users"]
# Check if the user exists
isUserExists = await users.find_one({"user_id": user_id})
# Verify rights
await verify_user_rights_on_user(request, current_user, "read", user_id)
# If the user does not exist, raise an error
if not isUserExists:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
return User(**isUserExists)
async def update_user(request: Request, user_id: str, user_object: User,current_user: PublicUser):
users = request.app.db["users"]
# Verify rights
await verify_user_rights_on_user(request, current_user, "update", user_id)
isUserExists = await users.find_one({"user_id": user_id})
isUsernameAvailable = await users.find_one({"username": user_object.username})
if not isUserExists:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
# okay if username is not changed
if isUserExists["username"] == user_object.username:
user_object.username = user_object.username.lower()
else:
if isUsernameAvailable:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Username already used")
updated_user = {"$set": user_object.dict()}
users.update_one({"user_id": user_id}, updated_user)
return User(**user_object.dict())
async def update_user_password(request: Request, current_user: PublicUser, user_id: str, password_change_form: PasswordChangeForm):
users = request.app.db["users"]
isUserExists = await users.find_one({"user_id": user_id})
# Verify rights
await verify_user_rights_on_user(request, current_user, "update", user_id)
if not isUserExists:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
if not await security_verify_password(password_change_form.old_password, isUserExists["password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password")
new_password = await security_hash_password(password_change_form.new_password)
updated_user = {"$set": {"password": new_password}}
await users.update_one({"user_id": user_id}, updated_user)
return {"detail": "Password updated"}
async def delete_user(request: Request, current_user: PublicUser, user_id: str):
users = request.app.db["users"]
isUserExists = await users.find_one({"user_id": user_id})
# Verify is user has permission to delete the user
await verify_user_rights_on_user(request, current_user, "delete", user_id)
if not isUserExists:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
await users.delete_one({"user_id": user_id})
return {"detail": "User deleted"}
# Utils & Security functions
async def security_get_user(request: Request, email: str):
users = request.app.db["users"]
user = await users.find_one({"email": email})
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User with Email does not exist")
return UserInDB(**user)
async def get_userid_by_username(request: Request, username: str):
users = request.app.db["users"]
user = await users.find_one({"username": username})
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
return user["user_id"]
async def get_user_by_userid(request: Request, user_id: str):
users = request.app.db["users"]
user = await users.find_one({"user_id": user_id})
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
user = User(**user)
return user
async def get_profile_metadata(request: Request, user):
users = request.app.db["users"]
roles = request.app.db["roles"]
user = await users.find_one({"user_id": user['user_id']})
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
return {
"user_object": PublicUser(**user),
"roles": "random"
}
# Verification of the user's permissions on the roles
async def verify_user_rights_on_user(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], user_id: str):
users = request.app.db["users"]
user = UserInDB(**await users.find_one({"user_id": user_id}))
if action == "create":
return True
if action == "read":
if current_user.user_id == user_id:
return True
for org in current_user.orgs:
if org.org_id in [org.org_id for org in user.orgs]:
return True
return False
if action == "update":
if current_user.user_id == user_id:
return True
for org in current_user.orgs:
if org.org_id in [org.org_id for org in user.orgs]:
if org.org_role == "owner":
return True
# TODO: Verify user roles on the org
return False
if action == "delete":
if current_user.user_id == user_id:
return True
for org in current_user.orgs:
if org.org_id in [org.org_id for org in user.orgs]:
if org.org_role == "owner":
return True
# TODO: Verify user roles on the org