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.staticfiles import StaticFiles
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
@ -64,8 +64,8 @@ async def root():
return {"Message": "Welcome to LearnHouse ✨"}
@app.get("/initial_data")
async def initial_data(request: Request):
# @app.get("/initial_data")
# async def initial_data(request: Request):
await create_initial_data(request)
return {"Message": "Initial data created 🤖"}
# await create_initial_data(request)
# return {"Message": "Initial data created 🤖"}

View file

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

View file

@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from src.services.users import *
from src.services.users.users import *
from fastapi import Cookie, FastAPI
from src.services.security import *
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.security import OAuth2PasswordRequestForm
from src.dependencies.auth import *
from src.services.users import *
from src.services.users.users import *
from datetime import timedelta
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.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.users import PublicUser
from src.services.users.users import PublicUser
router = APIRouter()

View file

@ -1,7 +1,7 @@
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.users import PublicUser
from src.services.users.users import PublicUser
from src.dependencies.auth import get_current_user
router = APIRouter()

View file

@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, Request
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

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request
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.users import PublicUser
from src.services.users.users import PublicUser
router = APIRouter()

View file

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, Request
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.users import PublicUser, User
from src.services.users.users import PublicUser, User
router = APIRouter()

View file

@ -2,7 +2,7 @@
from fastapi import APIRouter, Depends, Request
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.users import PublicUser, User
from src.services.users.users import PublicUser, User
router = APIRouter()

View file

@ -1,14 +1,15 @@
from fastapi import APIRouter, Depends, Request
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.users import PublicUser, User
from src.services.roles.schemas.roles import Role
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.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
"""
@ -16,31 +17,23 @@ async def api_create_role(request: Request,role_object: Role, current_user: Publ
@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
"""
return await get_role(request, role_id)
@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)
return await read_role(request, role_id, current_user)
@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
"""
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}")
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
"""

View file

@ -2,7 +2,9 @@ from fastapi import Depends, FastAPI, APIRouter
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
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())
@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}")
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("/")
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
"""
return await create_user(request, user_object)
return await create_user(request, None, user_object, org_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
"""
return await delete_user(request, user_id)
return await delete_user(request, current_user, 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
"""
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}")
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
"""
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 src.services.courses.chapters import get_coursechapters_meta
from src.services.users import PublicUser
from src.services.users.users import PublicUser
#### Classes ####################################################

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import os
from fastapi import HTTPException, status, UploadFile,Request
from fastapi.responses import StreamingResponse
from src.services.users import PublicUser
from src.services.users.users import PublicUser
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.lectures.lectures import Lecture, LectureInDB
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

View file

@ -2,7 +2,7 @@ import json
from typing import List
from uuid import uuid4
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 fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime

View file

@ -4,7 +4,7 @@ from uuid import uuid4
from pydantic import BaseModel
from src.services.courses.lectures.lectures import LectureInDB
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 fastapi import HTTPException, status, UploadFile
from datetime import datetime

View file

@ -1,6 +1,6 @@
from pydantic import BaseModel
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 uuid import uuid4
from datetime import datetime

View file

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

View file

@ -1,7 +1,7 @@
from pydantic import BaseModel
from src.services.security import verify_user_rights_with_roles
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 fastapi import HTTPException, status, UploadFile, Request
from uuid import uuid4
@ -48,9 +48,7 @@ async def create_video_lecture(request: Request,name: str, coursechapter_id: st
# upload video
if video_file:
print("uploading video")
# get videofile format
await upload_video(video_file, lecture_id)
# 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()
except Exception as e:
print(e)
return {"message": "There was an error uploading the file"}
finally:
thumbnail_file.file.close()

View file

@ -2,7 +2,7 @@ import json
from typing import List
from uuid import uuid4
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 fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime

View file

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

View file

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

View file

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

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