diff --git a/backend/app/api/api_v1/routers/auth.py b/backend/app/api/api_v1/routers/auth.py index b0e1ffe..3cb2847 100644 --- a/backend/app/api/api_v1/routers/auth.py +++ b/backend/app/api/api_v1/routers/auth.py @@ -25,11 +25,15 @@ async def login( minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES ) if user.is_superuser: - permissions = "admin" + roles = "super" + permissions = "ALL" else: - permissions = "user" + roles = ";".join(role.name for role in user.roles) + perlst = [perm.privilege for role in user.roles for perm in role.permissions] + permissions =";".join(list(set(perlst))) + access_token = security.create_access_token( - data={"sub": user.id, "permissions": permissions}, + data={"sub": user.id, "roles":roles,"permissions": permissions ,}, expires_delta=access_token_expires, ) diff --git a/backend/app/api/api_v1/routers/platform.py b/backend/app/api/api_v1/routers/platform.py index b616def..06348b5 100644 --- a/backend/app/api/api_v1/routers/platform.py +++ b/backend/app/api/api_v1/routers/platform.py @@ -23,7 +23,7 @@ platform_router = r = APIRouter() ) async def apps_list( request: Request, - user = Depends(get_current_user), + user = Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -60,7 +60,7 @@ async def apps_list( async def apps_update( request: Request, app: AppVersion, - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -68,7 +68,21 @@ async def apps_update( except Exception as e: raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e) - +@r.delete( + "/apps/{domainurl}/{appid}", response_model_exclude_none=True +) +async def apps_delete( + request: Request, + domainurl:str, + appid: str, + user=Depends(get_current_active_user), + db=Depends(get_db), +): + try: + return delete_apps(db, domainurl,appid) + except Exception as e: + raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e) + @r.get( "/appsettings/{id}", response_model=App, @@ -183,7 +197,7 @@ async def flow_details( async def flow_list( request: Request, appid: str, - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -198,8 +212,8 @@ async def flow_list( @r.post("/flow", response_model=Flow, response_model_exclude_none=True) async def flow_create( request: Request, - flow: FlowBase, - user=Depends(get_current_user), + flow: FlowIn, + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -215,13 +229,13 @@ async def flow_create( async def flow_edit( request: Request, flowid: str, - flow: FlowBase, - user=Depends(get_current_user), + flow: FlowIn, + user=Depends(get_current_active_user), db=Depends(get_db), ): try: domain = get_activedomain(db, user.id) - return edit_flow(db,domain.url, flow) + return edit_flow(db,domain.url, flow,user.id) except Exception as e: raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e) @@ -259,7 +273,7 @@ async def domain_details( async def domain_create( request: Request, domain: DomainBase, - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -274,10 +288,11 @@ async def domain_create( async def domain_edit( request: Request, domain: DomainBase, + user=Depends(get_current_active_user), db=Depends(get_db), ): try: - return edit_domain(db, domain) + return edit_domain(db, domain,user.id) except Exception as e: raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e) @@ -303,7 +318,7 @@ async def domain_delete( async def userdomain_details( request: Request, userId: Optional[int] = Query(None, alias="userId"), - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -351,7 +366,7 @@ async def userdomain_delete( async def get_useractivedomain( request: Request, userId: Optional[int] = Query(None, alias="userId"), - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: @@ -371,7 +386,7 @@ async def update_activeuserdomain( request: Request, domainid:int, userId: Optional[int] = Query(None, alias="userId"), - user=Depends(get_current_user), + user=Depends(get_current_active_user), db=Depends(get_db), ): try: diff --git a/backend/app/api/api_v1/routers/users.py b/backend/app/api/api_v1/routers/users.py index 06167e2..a42fb44 100644 --- a/backend/app/api/api_v1/routers/users.py +++ b/backend/app/api/api_v1/routers/users.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Request, Depends, Response, encoders +from fastapi import APIRouter, Request, Depends, Response, Security, encoders import typing as t from app.db.session import get_db @@ -8,9 +8,11 @@ from app.db.crud import ( create_user, delete_user, edit_user, + assign_userrole, + get_roles, ) -from app.db.schemas import UserCreate, UserEdit, User, UserOut -from app.core.auth import get_current_active_user, get_current_active_superuser +from app.db.schemas import UserCreate, UserEdit, User, UserOut,Role +from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser users_router = r = APIRouter() @@ -23,14 +25,14 @@ users_router = r = APIRouter() async def users_list( response: Response, db=Depends(get_db), - current_user=Depends(get_current_active_superuser), + current_user=Depends(get_current_active_user), ): """ Get all users """ - users = get_users(db) + users = get_users(db,current_user.is_superuser) # This is necessary for react-admin to work - response.headers["Content-Range"] = f"0-9/{len(users)}" + #response.headers["Content-Range"] = f"0-9/{len(users)}" return users @@ -105,3 +107,30 @@ async def user_delete( Delete existing user """ return delete_user(db, user_id) + + +@r.post("/userrole", + response_model=User, + response_model_exclude_none=True,) +async def assign_role( + request: Request, + userid:int, + roles:t.List[int], + db=Depends(get_db) +): + + return assign_userrole(db,userid,roles) + + +@r.get( + "/roles", + response_model=t.List[Role], + response_model_exclude_none=True, +) +async def roles_list( + response: Response, + db=Depends(get_db), + current_user=Security(get_current_active_user, scopes=["role_list"]), +): + roles = get_roles(db) + return roles diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 97e2985..95e32ea 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -1,5 +1,6 @@ +from fastapi.security import SecurityScopes import jwt -from fastapi import Depends, HTTPException, status +from fastapi import Depends, HTTPException, Request, Security, status from jwt import PyJWTError from app.db import models, schemas, session @@ -7,7 +8,7 @@ from app.db.crud import get_user_by_email, create_user,get_user from app.core import security -async def get_current_user( +async def get_current_user(security_scopes: SecurityScopes, db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme) ): credentials_exception = HTTPException( @@ -16,13 +17,21 @@ async def get_current_user( headers={"WWW-Authenticate": "Bearer"}, ) try: + payload = jwt.decode( token, security.SECRET_KEY, algorithms=[security.ALGORITHM] ) id: int = payload.get("sub") if id is None: raise credentials_exception + permissions: str = payload.get("permissions") + if not permissions =="ALL": + for scope in security_scopes.scopes: + if scope not in permissions.split(";"): + raise HTTPException( + status_code=403, detail="The user doesn't have enough privileges" + ) token_data = schemas.TokenData(id = id, permissions=permissions) except PyJWTError: raise credentials_exception diff --git a/backend/app/db/crud.py b/backend/app/db/crud.py index 45b5bff..9c15d91 100644 --- a/backend/app/db/crud.py +++ b/backend/app/db/crud.py @@ -1,3 +1,4 @@ +from datetime import datetime from fastapi import HTTPException, status from sqlalchemy.orm import Session from sqlalchemy import and_ @@ -19,9 +20,12 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase: def get_users( - db: Session, skip: int = 0, limit: int = 100 + db: Session, super:bool ) -> t.List[schemas.UserOut]: - return db.query(models.User).offset(skip).limit(limit).all() + if super: + return db.query(models.User).all() + else: + return db.query(models.User).filter(models.User.is_superuser == False) def create_user(db: Session, user: schemas.UserCreate): @@ -69,28 +73,48 @@ def edit_user( db.refresh(db_user) return db_user + +def get_roles( + db: Session +) -> t.List[schemas.Role]: + return db.query(models.Role).all() + +def assign_userrole( db: Session, user_id: int, roles: t.List[int]): + db_user = db.query(models.User).get(user_id) + if db_user: + for role in db_user.roles: + db_user.roles.remove(role) + for roleid in roles: + role = db.query(models.Role).get(roleid) + if role: + db_user.roles.append(role) + db.commit() + db.refresh(db_user) + return db_user + def get_apps( db: Session, - domain_url:str + domainurl:str ) -> t.List[schemas.AppList]: - return db.query(models.App).filter(models.App.domainurl == domain_url).all() + return db.query(models.App).filter(models.App.domainurl == domainurl).all() def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int): - app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first() - if app: - app.version = app.version + 1 - db_app = app - appver = app.version - else: - appver = 1 - db_app = models.App( + db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first() + if not db_app: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") + + db_app.version = db_app.version + 1 + appversion = models.AppVersion( domainurl = appedit.domainurl, appid=appedit.appid, - appname=appedit.appname, - version = 1, - updateuser= userid - ) - + appname=db_app.appname, + version = db_app.version, + versionname = appedit.versionname, + comment = appedit.comment, + updateuserid = userid, + createuserid = userid + ) + db.add(appversion) db.add(db_app) flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid)) @@ -103,7 +127,9 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int): name = flow.name, content = flow.content, createuser = userid, - version = appver + version = db_app.version, + updateuserid = userid, + createuserid = userid ) db.add(db_flowhistory) @@ -111,6 +137,17 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int): db.refresh(db_app) return db_app +def delete_apps(db: Session, domainurl: str,appid: str ): + db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first() + if not db_app: + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found") + db.delete(db_app) + db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid)) + for flow in db_flows: + db.delete(flow) + db.commit() + return db_app + def get_appsetting(db: Session, id: int): app = db.query(models.AppSetting).get(id) if not app: @@ -166,16 +203,28 @@ def get_actions(db: Session): return actions -def create_flow(db: Session, domainurl: str, flow: schemas.FlowBase): +def create_flow(db: Session, domainurl: str, flow: schemas.FlowIn,userid:int): db_flow = models.Flow( flowid=flow.flowid, appid=flow.appid, eventid=flow.eventid, domainurl=domainurl, name=flow.name, - content=flow.content + content=flow.content, + createuserid = userid, + updateuserid = userid ) db.add(db_flow) + db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first() + if not db_app: + db_app = models.App( + domainurl = domainurl, + appid=flow.appid, + appname=flow.appname, + version = 0, + createuserid= userid, + updateuserid = userid + ) db.commit() db.refresh(db_flow) return db_flow @@ -190,18 +239,20 @@ def delete_flow(db: Session, flowid: str): def edit_flow( - db: Session, domainurl: str, flow: schemas.FlowBase + db: Session, domainurl: str, flow: schemas.FlowIn,userid:int ) -> schemas.Flow: db_flow = get_flow(db, flow.flowid) if not db_flow: #見つからない時新規作成 - return create_flow(db,domainurl,flow) + return create_flow(db,domainurl,flow,userid) + + db_flow.appid =flow.appid + db_flow.eventid=flow.eventid + db_flow.domainurl=domainurl + db_flow.name=flow.name + db_flow.content=flow.content + db_flow.updateuserid = userid - update_data = flow.dict(exclude_unset=True) - - for key, value in update_data.items(): - setattr(db_flow, key, value) - db.add(db_flow) db.commit() db.refresh(db_flow) @@ -233,7 +284,9 @@ def create_domain(db: Session, domain: schemas.DomainBase,userid:int): name=domain.name, url=domain.url, kintoneuser=domain.kintoneuser, - kintonepwd=domain.kintonepwd + kintonepwd=domain.kintonepwd, + createuserid = userid, + updateuserid = userid ) db.add(db_domain) db.flush() @@ -252,18 +305,19 @@ def delete_domain(db: Session,id: int): def edit_domain( - db: Session, domain: schemas.DomainBase + db: Session, domain: schemas.DomainBase,userid:int ) -> schemas.Domain: domain.encrypt_kintonepwd() db_domain = db.query(models.Domain).get(domain.id) if not db_domain: - raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") - update_data = domain.dict(exclude_unset=True) - - for key, value in update_data.items(): - if key != "id" and not (key == "kintonepwd" and (value is None or value == "")): - setattr(db_domain, key, value) - print(str(db_domain)) + raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") + db_domain.tenantid = domain.tenantid + db_domain.name=domain.name + db_domain.url=domain.url + db_domain.kintoneuser=domain.kintoneuser + db_domain.kintonepwd = domain.kintonepwd + db_domain.updateuserid = userid + db_domain.update_time = datetime.now db.add(db_domain) db.commit() db.refresh(db_domain) diff --git a/backend/app/db/models.py b/backend/app/db/models.py index f710c17..731129b 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -1,8 +1,8 @@ -from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey +from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table from sqlalchemy.ext.declarative import as_declarative from sqlalchemy.orm import relationship from datetime import datetime - +from app.db.session import Base from app.core.security import chacha20Decrypt @as_declarative() @@ -11,6 +11,21 @@ class Base: create_time = Column(DateTime, default=datetime.now) update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now) + +userrole = Table( + "userrole", + Base.metadata, + Column("userid",Integer,ForeignKey("user.id")), + Column("roleid",Integer,ForeignKey("role.id")), +) + +rolepermission = Table( + "rolepermission", + Base.metadata, + Column("roleid",Integer,ForeignKey("role.id")), + Column("permissionid",Integer,ForeignKey("permission.id")), +) + class User(Base): __tablename__ = "user" @@ -20,6 +35,25 @@ class User(Base): hashed_password = Column(String(200), nullable=False) is_active = Column(Boolean, default=True) is_superuser = Column(Boolean, default=False) + roles = relationship("Role",secondary=userrole,back_populates="users") + + +class Role(Base): + __tablename__ = "role" + + name = Column(String(100)) + description = Column(String(255)) + users = relationship("User",secondary=userrole,back_populates="roles") + permissions = relationship("Permission",secondary=rolepermission,back_populates="roles") + +class Permission(Base): + __tablename__ = "permission" + + menu = Column(String(100)) + function = Column(String(255)) + privilege = Column(String(100)) + roles = relationship("Role",secondary=rolepermission,back_populates="permissions") + class App(Base): __tablename__ = "app" @@ -28,8 +62,25 @@ class App(Base): appname = Column(String(200), nullable=False) appid = Column(String(100), index=True, nullable=False) version = Column(Integer) - updateuser = Column(Integer,ForeignKey("user.id")) - user = relationship('User') + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) + +class AppVersion(Base): + __tablename__ = "appversion" + + domainurl = Column(String(200), nullable=False) + appname = Column(String(200), nullable=False) + appid = Column(String(100), index=True, nullable=False) + version = Column(Integer) + versionname = Column(String(200), nullable=False) + comment = Column(String(200), nullable=False) + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) + class AppSetting(Base): __tablename__ = "appsetting" @@ -64,7 +115,11 @@ class Flow(Base): eventid = Column(String(100), index=True, nullable=False) domainurl = Column(String(200)) name = Column(String(200)) - content = Column(String) + content = Column(String) + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) class FlowHistory(Base): __tablename__ = "flowhistory" @@ -75,8 +130,11 @@ class FlowHistory(Base): domainurl = Column(String(200)) name = Column(String(200)) content = Column(String) - createuser = Column(Integer,ForeignKey("user.id")) version = Column(Integer) + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) class Tenant(Base): __tablename__ = "tenant" @@ -98,7 +156,10 @@ class Domain(Base): def decrypt_kintonepwd(self): decrypted_pwd = chacha20Decrypt(self.kintonepwd) return decrypted_pwd - + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) class UserDomain(Base): __tablename__ = "userdomain" diff --git a/backend/app/db/schemas.py b/backend/app/db/schemas.py index 992e406..583c210 100644 --- a/backend/app/db/schemas.py +++ b/backend/app/db/schemas.py @@ -8,13 +8,26 @@ class Base(BaseModel): create_time: datetime update_time: datetime + +class Permission(BaseModel): + id: int + menu:str + function:str + privilege:str + +class Role(BaseModel): + id: int + name:str + description:str + permissions:t.List[Permission] = [] + class UserBase(BaseModel): email: str is_active: bool = True is_superuser: bool = False first_name: str = None last_name: str = None - + roles:t.List[Role] = [] class UserOut(UserBase): pass @@ -54,13 +67,16 @@ class AppList(Base): domainurl: str appname: str appid:str + updateuser: UserOut version:int - user:UserOut - + class AppVersion(BaseModel): domainurl: str appname: str + versionname: str + comment:str appid:str + class TokenData(BaseModel): id:int = 0 @@ -106,9 +122,11 @@ class Action(BaseModel): class ConfigDict: orm_mode = True -class FlowBase(BaseModel): +class FlowIn(BaseModel): flowid: str + # domainurl:str appid: str + appname:str eventid: str name: str = None content: str = None diff --git a/frontend/src/pages/AppManagement.vue b/frontend/src/pages/AppManagement.vue index 99d960d..5698d48 100644 --- a/frontend/src/pages/AppManagement.vue +++ b/frontend/src/pages/AppManagement.vue @@ -144,7 +144,7 @@ const appToAppDisplay = (app: IManagedApp) => { sortId: parseInt(app.appid, 10), name: app.appname, url: `${app.domainurl}/k/${app.appid}`, - user: `${app.user.first_name} ${app.user.last_name}` , + user: `${app.updateuser.first_name} ${app.updateuser.last_name}` , updatetime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'), version: app.version } @@ -158,4 +158,4 @@ const toEditFlowPage = (app:IAppDisplay) => { store.selectFlow(undefined); router.push('/FlowChart/' + app.id); }; - \ No newline at end of file + diff --git a/frontend/src/stores/flowEditor.ts b/frontend/src/stores/flowEditor.ts index db31fb8..ec077b7 100644 --- a/frontend/src/stores/flowEditor.ts +++ b/frontend/src/stores/flowEditor.ts @@ -128,6 +128,7 @@ export const useFlowEditorStore = defineStore('flowEditor', { const jsonData = { flowid: isNew ? flow.createNewId() : flow.id, appid: this.appInfo?.appId, + appname: this.appInfo?.name, eventid: root?.name, name: root?.subTitle, content: JSON.stringify(flow), diff --git a/frontend/src/types/AppTypes.ts b/frontend/src/types/AppTypes.ts index 97c084f..4606f0d 100644 --- a/frontend/src/types/AppTypes.ts +++ b/frontend/src/types/AppTypes.ts @@ -1,14 +1,14 @@ -interface IUser { - first_name: string; - last_name: string; - email: string; -} - -export interface IManagedApp { - appid: string; - appname: string; - domainurl: string; - version: string; - user: IUser; - update_time: string; -} +interface IUser { + first_name: string; + last_name: string; + email: string; +} + +export interface IManagedApp { + appid: string; + appname: string; + domainurl: string; + version: string; + updateuser: IUser; + update_time: string; +}