feat: 사용자별 페이지 접근 권한 시스템 구현
- 기존 4단계 권한을 admin/user 2단계로 단순화 - 페이지별 세부 접근 권한 관리 시스템 추가 - 부적합 조회 시 일반 사용자는 본인 등록 건만 조회 가능하도록 제한 - 관리자 전용 전체 부적합 조회 API 추가 (/api/issues/admin/all) Backend Changes: - models.py: UserPagePermission 모델 추가, UserRole 단순화 - page_permissions.py: 페이지 권한 관리 API 라우터 추가 - auth.py: 사용자 목록 조회 및 비밀번호 초기화 API 추가 - issues.py: 권한별 부적합 조회 제한 로직 구현 - 마이그레이션: 010~012 권한 시스템 관련 DB 스키마 변경
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Float, Boolean, Text, ForeignKey, Enum
|
||||
from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Float, Boolean, Text, ForeignKey, Enum, Index
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime, timezone, timedelta
|
||||
@@ -14,8 +14,8 @@ def get_kst_now():
|
||||
Base = declarative_base()
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
admin = "admin"
|
||||
user = "user"
|
||||
admin = "admin" # 관리자
|
||||
user = "user" # 일반 사용자
|
||||
|
||||
class IssueStatus(str, enum.Enum):
|
||||
new = "new"
|
||||
@@ -44,6 +44,28 @@ class User(Base):
|
||||
issues = relationship("Issue", back_populates="reporter")
|
||||
daily_works = relationship("DailyWork", back_populates="created_by")
|
||||
projects = relationship("Project", back_populates="created_by")
|
||||
page_permissions = relationship("UserPagePermission", back_populates="user", foreign_keys="UserPagePermission.user_id")
|
||||
|
||||
class UserPagePermission(Base):
|
||||
__tablename__ = "user_page_permissions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
||||
page_name = Column(String(50), nullable=False)
|
||||
can_access = Column(Boolean, default=False)
|
||||
granted_by_id = Column(Integer, ForeignKey("users.id"))
|
||||
granted_at = Column(DateTime, default=get_kst_now)
|
||||
notes = Column(Text)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", back_populates="page_permissions", foreign_keys=[user_id])
|
||||
granted_by = relationship("User", foreign_keys=[granted_by_id], post_update=True)
|
||||
|
||||
# Unique constraint
|
||||
__table_args__ = (
|
||||
Index('idx_user_page_permissions_user_id', 'user_id'),
|
||||
Index('idx_user_page_permissions_page_name', 'page_name'),
|
||||
)
|
||||
|
||||
class Issue(Base):
|
||||
__tablename__ = "issues"
|
||||
|
||||
@@ -5,7 +5,7 @@ import uvicorn
|
||||
|
||||
from database.database import engine, get_db
|
||||
from database.models import Base
|
||||
from routers import auth, issues, daily_work, reports, projects
|
||||
from routers import auth, issues, daily_work, reports, projects, page_permissions
|
||||
from services.auth_service import create_admin_user
|
||||
|
||||
# 데이터베이스 테이블 생성
|
||||
@@ -36,6 +36,7 @@ app.include_router(issues.router)
|
||||
app.include_router(daily_work.router)
|
||||
app.include_router(reports.router)
|
||||
app.include_router(projects.router)
|
||||
app.include_router(page_permissions.router)
|
||||
|
||||
# 시작 시 관리자 계정 생성
|
||||
@app.on_event("startup")
|
||||
|
||||
15
backend/migrations/010_add_etc_category.sql
Normal file
15
backend/migrations/010_add_etc_category.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- 부적합 카테고리에 'etc' (기타) 값 추가
|
||||
-- 백엔드 코드와 데이터베이스 enum 타입 불일치 해결
|
||||
|
||||
-- issuecategory enum 타입에 'etc' 값 추가
|
||||
ALTER TYPE issuecategory ADD VALUE 'etc';
|
||||
|
||||
-- 확인 쿼리 (주석)
|
||||
-- SELECT enumlabel FROM pg_enum WHERE enumtypid = (SELECT oid FROM pg_type WHERE typname = 'issuecategory') ORDER BY enumsortorder;
|
||||
|
||||
-- 이제 사용 가능한 카테고리:
|
||||
-- 1. material_missing (자재누락)
|
||||
-- 2. design_error (설계미스)
|
||||
-- 3. incoming_defect (입고자재 불량)
|
||||
-- 4. inspection_miss (검사미스)
|
||||
-- 5. etc (기타) ✅ 새로 추가됨
|
||||
137
backend/migrations/011_add_permission_system.sql
Normal file
137
backend/migrations/011_add_permission_system.sql
Normal file
@@ -0,0 +1,137 @@
|
||||
-- 권한 시스템 개선 마이그레이션
|
||||
-- 새로운 사용자 역할 추가 및 개별 권한 테이블 생성
|
||||
|
||||
-- 1. 새로운 사용자 역할 추가
|
||||
ALTER TYPE userrole ADD VALUE 'super_admin';
|
||||
ALTER TYPE userrole ADD VALUE 'manager';
|
||||
|
||||
-- 2. 사용자별 개별 권한 테이블 생성
|
||||
CREATE TABLE IF NOT EXISTS user_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
permission VARCHAR(50) NOT NULL,
|
||||
granted BOOLEAN DEFAULT TRUE,
|
||||
granted_by_id INTEGER REFERENCES users(id),
|
||||
granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
revoked_at TIMESTAMP WITH TIME ZONE,
|
||||
notes TEXT,
|
||||
UNIQUE(user_id, permission)
|
||||
);
|
||||
|
||||
-- 3. 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_user_permissions_user_id ON user_permissions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_permissions_permission ON user_permissions(permission);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_permissions_granted ON user_permissions(granted);
|
||||
|
||||
-- 4. 기본 권한 설정 (기존 관리자에게 super_admin 권한 부여)
|
||||
UPDATE users SET role = 'super_admin' WHERE username = 'hyungi';
|
||||
|
||||
-- 5. 권한 확인 함수 생성
|
||||
CREATE OR REPLACE FUNCTION check_user_permission(p_user_id INTEGER, p_permission VARCHAR)
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
user_role userrole;
|
||||
has_permission BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- 사용자 역할 가져오기
|
||||
SELECT role INTO user_role FROM users WHERE id = p_user_id AND is_active = TRUE;
|
||||
|
||||
IF user_role IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
|
||||
-- super_admin은 모든 권한 보유
|
||||
IF user_role = 'super_admin' THEN
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
|
||||
-- 개별 권한 확인
|
||||
SELECT granted INTO has_permission
|
||||
FROM user_permissions
|
||||
WHERE user_id = p_user_id
|
||||
AND permission = p_permission
|
||||
AND granted = TRUE
|
||||
AND revoked_at IS NULL;
|
||||
|
||||
-- 개별 권한이 없으면 역할 기반 기본 권한 확인
|
||||
IF has_permission IS NULL THEN
|
||||
-- 기본 권한 매트릭스
|
||||
CASE
|
||||
WHEN p_permission IN ('issues.create', 'issues.view') THEN
|
||||
has_permission := TRUE; -- 모든 사용자
|
||||
WHEN p_permission IN ('issues.edit', 'issues.review', 'daily_work.create', 'daily_work.view', 'daily_work.edit') THEN
|
||||
has_permission := user_role IN ('admin', 'manager'); -- 관리자, 매니저
|
||||
WHEN p_permission IN ('projects.create', 'projects.edit', 'issues.delete', 'daily_work.delete') THEN
|
||||
has_permission := user_role = 'admin'; -- 관리자만
|
||||
WHEN p_permission IN ('projects.delete', 'users.create', 'users.edit', 'users.delete', 'users.change_role') THEN
|
||||
has_permission := user_role = 'super_admin'; -- 최고 관리자만
|
||||
ELSE
|
||||
has_permission := FALSE;
|
||||
END CASE;
|
||||
END IF;
|
||||
|
||||
RETURN COALESCE(has_permission, FALSE);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 6. 권한 부여 함수 생성
|
||||
CREATE OR REPLACE FUNCTION grant_user_permission(
|
||||
p_user_id INTEGER,
|
||||
p_permission VARCHAR,
|
||||
p_granted_by_id INTEGER,
|
||||
p_notes TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
INSERT INTO user_permissions (user_id, permission, granted, granted_by_id, notes)
|
||||
VALUES (p_user_id, p_permission, TRUE, p_granted_by_id, p_notes)
|
||||
ON CONFLICT (user_id, permission)
|
||||
DO UPDATE SET
|
||||
granted = TRUE,
|
||||
granted_by_id = p_granted_by_id,
|
||||
granted_at = NOW(),
|
||||
revoked_at = NULL,
|
||||
notes = p_notes;
|
||||
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 7. 권한 취소 함수 생성
|
||||
CREATE OR REPLACE FUNCTION revoke_user_permission(
|
||||
p_user_id INTEGER,
|
||||
p_permission VARCHAR,
|
||||
p_revoked_by_id INTEGER,
|
||||
p_notes TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
UPDATE user_permissions
|
||||
SET granted = FALSE,
|
||||
revoked_at = NOW(),
|
||||
notes = p_notes
|
||||
WHERE user_id = p_user_id
|
||||
AND permission = p_permission;
|
||||
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 8. 사용자 권한 목록 조회 뷰 생성
|
||||
CREATE OR REPLACE VIEW user_permissions_view AS
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
u.username,
|
||||
u.full_name,
|
||||
u.role,
|
||||
up.permission,
|
||||
up.granted,
|
||||
up.granted_at,
|
||||
up.revoked_at,
|
||||
granted_by.username as granted_by_username,
|
||||
up.notes
|
||||
FROM users u
|
||||
LEFT JOIN user_permissions up ON u.id = up.user_id
|
||||
LEFT JOIN users granted_by ON up.granted_by_id = granted_by.id
|
||||
WHERE u.is_active = TRUE
|
||||
ORDER BY u.username, up.permission;
|
||||
111
backend/migrations/012_simplify_permissions.sql
Normal file
111
backend/migrations/012_simplify_permissions.sql
Normal file
@@ -0,0 +1,111 @@
|
||||
-- 권한 시스템 단순화
|
||||
-- admin/user 구조로 변경하고 페이지별 접근 권한으로 변경
|
||||
|
||||
-- 1. 기존 복잡한 권한 테이블 삭제하고 단순한 페이지 권한 테이블로 변경
|
||||
DROP TABLE IF EXISTS user_permissions CASCADE;
|
||||
|
||||
-- 2. 페이지별 접근 권한 테이블 생성
|
||||
CREATE TABLE user_page_permissions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
page_name VARCHAR(50) NOT NULL,
|
||||
can_access BOOLEAN DEFAULT FALSE,
|
||||
granted_by_id INTEGER REFERENCES users(id),
|
||||
granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
notes TEXT,
|
||||
UNIQUE(user_id, page_name)
|
||||
);
|
||||
|
||||
-- 3. 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_user_page_permissions_user_id ON user_page_permissions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_page_permissions_page_name ON user_page_permissions(page_name);
|
||||
|
||||
-- 4. 기존 복잡한 함수들 삭제
|
||||
DROP FUNCTION IF EXISTS check_user_permission(INTEGER, VARCHAR);
|
||||
DROP FUNCTION IF EXISTS grant_user_permission(INTEGER, VARCHAR, INTEGER, TEXT);
|
||||
DROP FUNCTION IF EXISTS revoke_user_permission(INTEGER, VARCHAR, INTEGER, TEXT);
|
||||
|
||||
-- 5. 단순한 페이지 접근 권한 체크 함수
|
||||
CREATE OR REPLACE FUNCTION check_page_access(p_user_id INTEGER, p_page_name VARCHAR)
|
||||
RETURNS BOOLEAN AS $$
|
||||
DECLARE
|
||||
user_role userrole;
|
||||
has_access BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- 사용자 역할 가져오기
|
||||
SELECT role INTO user_role FROM users WHERE id = p_user_id AND is_active = TRUE;
|
||||
|
||||
IF user_role IS NULL THEN
|
||||
RETURN FALSE;
|
||||
END IF;
|
||||
|
||||
-- admin은 모든 페이지 접근 가능
|
||||
IF user_role = 'admin' THEN
|
||||
RETURN TRUE;
|
||||
END IF;
|
||||
|
||||
-- 일반 사용자는 개별 페이지 권한 확인
|
||||
SELECT can_access INTO has_access
|
||||
FROM user_page_permissions
|
||||
WHERE user_id = p_user_id
|
||||
AND page_name = p_page_name;
|
||||
|
||||
-- 권한이 설정되지 않은 경우 기본값 (부적합 등록/조회만 허용)
|
||||
IF has_access IS NULL THEN
|
||||
CASE p_page_name
|
||||
WHEN 'issues_create' THEN has_access := TRUE;
|
||||
WHEN 'issues_view' THEN has_access := TRUE;
|
||||
ELSE has_access := FALSE;
|
||||
END CASE;
|
||||
END IF;
|
||||
|
||||
RETURN COALESCE(has_access, FALSE);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 6. 페이지 권한 부여 함수
|
||||
CREATE OR REPLACE FUNCTION grant_page_access(
|
||||
p_user_id INTEGER,
|
||||
p_page_name VARCHAR,
|
||||
p_can_access BOOLEAN,
|
||||
p_granted_by_id INTEGER,
|
||||
p_notes TEXT DEFAULT NULL
|
||||
)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id, notes)
|
||||
VALUES (p_user_id, p_page_name, p_can_access, p_granted_by_id, p_notes)
|
||||
ON CONFLICT (user_id, page_name)
|
||||
DO UPDATE SET
|
||||
can_access = p_can_access,
|
||||
granted_by_id = p_granted_by_id,
|
||||
granted_at = NOW(),
|
||||
notes = p_notes;
|
||||
|
||||
RETURN TRUE;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 7. 사용자 페이지 권한 조회 뷰
|
||||
CREATE OR REPLACE VIEW user_page_access_view AS
|
||||
SELECT
|
||||
u.id as user_id,
|
||||
u.username,
|
||||
u.full_name,
|
||||
u.role,
|
||||
upp.page_name,
|
||||
upp.can_access,
|
||||
upp.granted_at,
|
||||
granted_by.username as granted_by_username,
|
||||
upp.notes
|
||||
FROM users u
|
||||
LEFT JOIN user_page_permissions upp ON u.id = upp.user_id
|
||||
LEFT JOIN users granted_by ON upp.granted_by_id = granted_by.id
|
||||
WHERE u.is_active = TRUE
|
||||
ORDER BY u.username, upp.page_name;
|
||||
|
||||
-- 8. 기존 super_admin, manager 역할을 admin으로 변경
|
||||
UPDATE users SET role = 'admin' WHERE role IN ('super_admin', 'manager');
|
||||
|
||||
-- 9. 기존 뷰 삭제
|
||||
DROP VIEW IF EXISTS user_permissions_view;
|
||||
@@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from database.database import get_db
|
||||
from database.models import User, UserRole
|
||||
@@ -61,6 +62,15 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session =
|
||||
async def read_users_me(current_user: User = Depends(get_current_user)):
|
||||
return current_user
|
||||
|
||||
@router.get("/users", response_model=List[schemas.User])
|
||||
async def get_all_users(
|
||||
current_admin: User = Depends(get_current_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""모든 사용자 목록 조회 (관리자 전용)"""
|
||||
users = db.query(User).filter(User.is_active == True).all()
|
||||
return users
|
||||
|
||||
@router.post("/users", response_model=schemas.User)
|
||||
async def create_user(
|
||||
user: schemas.UserCreate,
|
||||
@@ -153,3 +163,24 @@ async def change_password(
|
||||
db.commit()
|
||||
|
||||
return {"detail": "Password changed successfully"}
|
||||
|
||||
class PasswordReset(BaseModel):
|
||||
new_password: str
|
||||
|
||||
@router.post("/users/{user_id}/reset-password")
|
||||
async def reset_user_password(
|
||||
user_id: int,
|
||||
password_reset: PasswordReset,
|
||||
current_admin: User = Depends(get_current_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""사용자 비밀번호 초기화 (관리자 전용)"""
|
||||
db_user = db.query(User).filter(User.id == user_id).first()
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# 새 비밀번호로 업데이트
|
||||
db_user.hashed_password = get_password_hash(password_reset.new_password)
|
||||
db.commit()
|
||||
|
||||
return {"detail": f"Password reset successfully for user {db_user.username}"}
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
||||
from database.database import get_db
|
||||
from database.models import Issue, IssueStatus, User, UserRole
|
||||
from database import schemas
|
||||
from routers.auth import get_current_user
|
||||
from routers.auth import get_current_user, get_current_admin
|
||||
from services.file_service import save_base64_image, delete_file
|
||||
|
||||
router = APIRouter(prefix="/api/issues", tags=["issues"])
|
||||
@@ -54,8 +54,30 @@ async def read_issues(
|
||||
):
|
||||
query = db.query(Issue)
|
||||
|
||||
# 모든 사용자가 모든 이슈를 조회 가능
|
||||
# (필터링 제거 - 협업을 위해 모두가 볼 수 있어야 함)
|
||||
# 권한별 조회 제한
|
||||
if current_user.role == UserRole.admin:
|
||||
# 관리자는 모든 이슈 조회 가능
|
||||
pass
|
||||
else:
|
||||
# 일반 사용자는 본인이 등록한 이슈만 조회 가능
|
||||
query = query.filter(Issue.reporter_id == current_user.id)
|
||||
|
||||
if status:
|
||||
query = query.filter(Issue.status == status)
|
||||
|
||||
issues = query.offset(skip).limit(limit).all()
|
||||
return issues
|
||||
|
||||
@router.get("/admin/all", response_model=List[schemas.Issue])
|
||||
async def read_all_issues_admin(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[IssueStatus] = None,
|
||||
current_admin: User = Depends(get_current_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""관리자 전용: 모든 부적합 조회"""
|
||||
query = db.query(Issue)
|
||||
|
||||
if status:
|
||||
query = query.filter(Issue.status == status)
|
||||
@@ -73,7 +95,12 @@ async def read_issue(
|
||||
if not issue:
|
||||
raise HTTPException(status_code=404, detail="Issue not found")
|
||||
|
||||
# 모든 사용자가 모든 이슈를 조회 가능 (협업을 위해)
|
||||
# 권한별 조회 제한
|
||||
if current_user.role != UserRole.admin and issue.reporter_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="본인이 등록한 부적합만 조회할 수 있습니다."
|
||||
)
|
||||
|
||||
return issue
|
||||
|
||||
|
||||
324
backend/routers/page_permissions.py
Normal file
324
backend/routers/page_permissions.py
Normal file
@@ -0,0 +1,324 @@
|
||||
"""
|
||||
페이지 권한 관리 API 라우터
|
||||
사용자별 페이지 접근 권한을 관리하는 엔드포인트들
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
from database.database import get_db
|
||||
from database.models import User, UserPagePermission, UserRole
|
||||
from routers.auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["page-permissions"])
|
||||
|
||||
# Pydantic 모델들
|
||||
class PagePermissionRequest(BaseModel):
|
||||
user_id: int
|
||||
page_name: str
|
||||
can_access: bool
|
||||
notes: Optional[str] = None
|
||||
|
||||
class PagePermissionResponse(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
page_name: str
|
||||
can_access: bool
|
||||
granted_by_id: Optional[int]
|
||||
granted_at: str
|
||||
notes: Optional[str]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class UserPagePermissionSummary(BaseModel):
|
||||
user_id: int
|
||||
username: str
|
||||
full_name: Optional[str]
|
||||
role: str
|
||||
permissions: List[PagePermissionResponse]
|
||||
|
||||
# 기본 페이지 목록
|
||||
DEFAULT_PAGES = {
|
||||
'issues_create': {'title': '부적합 등록', 'default_access': True},
|
||||
'issues_view': {'title': '부적합 조회', 'default_access': True},
|
||||
'issues_manage': {'title': '부적합 관리', 'default_access': False},
|
||||
'projects_manage': {'title': '프로젝트 관리', 'default_access': False},
|
||||
'daily_work': {'title': '일일 공수', 'default_access': False},
|
||||
'reports': {'title': '보고서', 'default_access': False}
|
||||
}
|
||||
|
||||
@router.post("/page-permissions/grant")
|
||||
async def grant_page_permission(
|
||||
request: PagePermissionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""페이지 권한 부여/취소"""
|
||||
|
||||
# 관리자만 권한 설정 가능
|
||||
if current_user.role != UserRole.admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="관리자만 권한을 설정할 수 있습니다."
|
||||
)
|
||||
|
||||
# 대상 사용자 확인
|
||||
target_user = db.query(User).filter(User.id == request.user_id).first()
|
||||
if not target_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="사용자를 찾을 수 없습니다."
|
||||
)
|
||||
|
||||
# 유효한 페이지명 확인
|
||||
if request.page_name not in DEFAULT_PAGES:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="유효하지 않은 페이지명입니다."
|
||||
)
|
||||
|
||||
# 기존 권한 확인
|
||||
existing_permission = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.user_id == request.user_id,
|
||||
UserPagePermission.page_name == request.page_name
|
||||
).first()
|
||||
|
||||
if existing_permission:
|
||||
# 기존 권한 업데이트
|
||||
existing_permission.can_access = request.can_access
|
||||
existing_permission.granted_by_id = current_user.id
|
||||
existing_permission.notes = request.notes
|
||||
db.commit()
|
||||
db.refresh(existing_permission)
|
||||
return {"message": "권한이 업데이트되었습니다.", "permission_id": existing_permission.id}
|
||||
else:
|
||||
# 새 권한 생성
|
||||
new_permission = UserPagePermission(
|
||||
user_id=request.user_id,
|
||||
page_name=request.page_name,
|
||||
can_access=request.can_access,
|
||||
granted_by_id=current_user.id,
|
||||
notes=request.notes
|
||||
)
|
||||
db.add(new_permission)
|
||||
db.commit()
|
||||
db.refresh(new_permission)
|
||||
return {"message": "권한이 설정되었습니다.", "permission_id": new_permission.id}
|
||||
|
||||
@router.get("/users/{user_id}/page-permissions", response_model=List[PagePermissionResponse])
|
||||
async def get_user_page_permissions(
|
||||
user_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""특정 사용자의 페이지 권한 목록 조회"""
|
||||
|
||||
# 관리자이거나 본인의 권한만 조회 가능
|
||||
if current_user.role != UserRole.admin and current_user.id != user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="권한이 없습니다."
|
||||
)
|
||||
|
||||
# 사용자 존재 확인
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="사용자를 찾을 수 없습니다."
|
||||
)
|
||||
|
||||
# 사용자의 페이지 권한 조회
|
||||
permissions = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.user_id == user_id
|
||||
).all()
|
||||
|
||||
return permissions
|
||||
|
||||
@router.get("/page-permissions/check/{user_id}/{page_name}")
|
||||
async def check_page_access(
|
||||
user_id: int,
|
||||
page_name: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""특정 사용자의 특정 페이지 접근 권한 확인"""
|
||||
|
||||
# 사용자 존재 확인
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="사용자를 찾을 수 없습니다."
|
||||
)
|
||||
|
||||
# admin은 모든 페이지 접근 가능
|
||||
if user.role == UserRole.admin:
|
||||
return {"can_access": True, "reason": "admin_role"}
|
||||
|
||||
# 유효한 페이지명 확인
|
||||
if page_name not in DEFAULT_PAGES:
|
||||
return {"can_access": False, "reason": "invalid_page"}
|
||||
|
||||
# 개별 권한 확인
|
||||
permission = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.user_id == user_id,
|
||||
UserPagePermission.page_name == page_name
|
||||
).first()
|
||||
|
||||
if permission:
|
||||
return {
|
||||
"can_access": permission.can_access,
|
||||
"reason": "explicit_permission",
|
||||
"granted_at": permission.granted_at.isoformat() if permission.granted_at else None
|
||||
}
|
||||
|
||||
# 기본 권한 확인
|
||||
default_access = DEFAULT_PAGES[page_name]['default_access']
|
||||
return {
|
||||
"can_access": default_access,
|
||||
"reason": "default_permission"
|
||||
}
|
||||
|
||||
@router.get("/page-permissions/all-users", response_model=List[UserPagePermissionSummary])
|
||||
async def get_all_users_permissions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""모든 사용자의 페이지 권한 요약 조회 (관리자용)"""
|
||||
|
||||
if current_user.role != UserRole.admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="관리자만 접근할 수 있습니다."
|
||||
)
|
||||
|
||||
# 모든 사용자 조회
|
||||
users = db.query(User).filter(User.is_active == True).all()
|
||||
|
||||
result = []
|
||||
for user in users:
|
||||
# 각 사용자의 권한 조회
|
||||
permissions = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.user_id == user.id
|
||||
).all()
|
||||
|
||||
result.append(UserPagePermissionSummary(
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
full_name=user.full_name,
|
||||
role=user.role.value,
|
||||
permissions=permissions
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
@router.get("/page-permissions/available-pages")
|
||||
async def get_available_pages(
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""사용 가능한 페이지 목록 조회"""
|
||||
|
||||
return {
|
||||
"pages": DEFAULT_PAGES,
|
||||
"total_count": len(DEFAULT_PAGES)
|
||||
}
|
||||
|
||||
@router.delete("/page-permissions/{permission_id}")
|
||||
async def delete_page_permission(
|
||||
permission_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""페이지 권한 삭제 (기본값으로 되돌림)"""
|
||||
|
||||
if current_user.role != UserRole.admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="관리자만 권한을 삭제할 수 있습니다."
|
||||
)
|
||||
|
||||
# 권한 조회
|
||||
permission = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.id == permission_id
|
||||
).first()
|
||||
|
||||
if not permission:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="권한을 찾을 수 없습니다."
|
||||
)
|
||||
|
||||
# 권한 삭제
|
||||
db.delete(permission)
|
||||
db.commit()
|
||||
|
||||
return {"message": "권한이 삭제되었습니다. 기본값이 적용됩니다."}
|
||||
|
||||
class BulkPermissionRequest(BaseModel):
|
||||
user_id: int
|
||||
permissions: List[dict] # [{"page_name": "issues_manage", "can_access": true}, ...]
|
||||
|
||||
@router.post("/page-permissions/bulk-grant")
|
||||
async def bulk_grant_permissions(
|
||||
request: BulkPermissionRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""사용자의 여러 페이지 권한을 일괄 설정"""
|
||||
|
||||
if current_user.role != UserRole.admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="관리자만 권한을 설정할 수 있습니다."
|
||||
)
|
||||
|
||||
# 대상 사용자 확인
|
||||
target_user = db.query(User).filter(User.id == request.user_id).first()
|
||||
if not target_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="사용자를 찾을 수 없습니다."
|
||||
)
|
||||
|
||||
updated_permissions = []
|
||||
|
||||
for perm_data in request.permissions:
|
||||
page_name = perm_data.get('page_name')
|
||||
can_access = perm_data.get('can_access', False)
|
||||
|
||||
# 유효한 페이지명 확인
|
||||
if page_name not in DEFAULT_PAGES:
|
||||
continue
|
||||
|
||||
# 기존 권한 확인
|
||||
existing_permission = db.query(UserPagePermission).filter(
|
||||
UserPagePermission.user_id == request.user_id,
|
||||
UserPagePermission.page_name == page_name
|
||||
).first()
|
||||
|
||||
if existing_permission:
|
||||
# 기존 권한 업데이트
|
||||
existing_permission.can_access = can_access
|
||||
existing_permission.granted_by_id = current_user.id
|
||||
updated_permissions.append(existing_permission)
|
||||
else:
|
||||
# 새 권한 생성
|
||||
new_permission = UserPagePermission(
|
||||
user_id=request.user_id,
|
||||
page_name=page_name,
|
||||
can_access=can_access,
|
||||
granted_by_id=current_user.id
|
||||
)
|
||||
db.add(new_permission)
|
||||
updated_permissions.append(new_permission)
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"message": f"{len(updated_permissions)}개의 권한이 설정되었습니다.",
|
||||
"updated_count": len(updated_permissions)
|
||||
}
|
||||
Reference in New Issue
Block a user