加入了 GVSDSDK 模块,进行了 QModel 兼容层的尝试,生产环境可用
This commit is contained in:
50
gvsdsdk/auth/__init__.py
Normal file
50
gvsdsdk/auth/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""gvsdsdk.auth — ELT 用户认证模块"""
|
||||
|
||||
__all__ = [
|
||||
'ELTAuthentication',
|
||||
'ELT_COOKIE_NAME', 'ELT_COOKIE_DOMAIN', 'ELT_COOKIE_MAX_AGE', 'SetELTCookie', 'DeleteELTCookie',
|
||||
'HashPassword', 'VerifyPassword', 'SymmetricEncrypt', 'SymmetricDecrypt', 'FileSHA1', 'FileSHA256', 'BytesSHA1', 'BytesSHA256',
|
||||
'ELTAuthMiddleware', 'CurrentRoleMiddleware',
|
||||
'IsAdminUser', 'IsCompanyMember', 'CompanyScopedMixin', 'PermissionVerifier',
|
||||
'RoleManager', 'ShopManager', 'PositionManager',
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
if name == 'ELTAuthentication':
|
||||
from gvsdsdk.auth.elt_auth import ELTAuthentication
|
||||
globals()['ELTAuthentication'] = ELTAuthentication
|
||||
return ELTAuthentication
|
||||
if name in ('ELT_COOKIE_NAME', 'ELT_COOKIE_DOMAIN', 'ELT_COOKIE_MAX_AGE', 'SetELTCookie', 'DeleteELTCookie'):
|
||||
from gvsdsdk.auth import cookie as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name in ('HashPassword', 'VerifyPassword', 'SymmetricEncrypt', 'SymmetricDecrypt', 'FileSHA1', 'FileSHA256', 'BytesSHA1', 'BytesSHA256'):
|
||||
from gvsdsdk.auth import crypto as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name in ('ELTAuthMiddleware', 'CurrentRoleMiddleware'):
|
||||
from gvsdsdk.auth import middleware as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name in ('IsAdminUser', 'IsCompanyMember', 'CompanyScopedMixin', 'PermissionVerifier'):
|
||||
from gvsdsdk.auth import permissions as _m
|
||||
val = getattr(_m, name)
|
||||
globals()[name] = val
|
||||
return val
|
||||
if name == 'RoleManager':
|
||||
from gvsdsdk.auth.managers import RoleManager
|
||||
globals()['RoleManager'] = RoleManager
|
||||
return RoleManager
|
||||
if name == 'ShopManager':
|
||||
from gvsdsdk.auth.managers import ShopManager
|
||||
globals()['ShopManager'] = ShopManager
|
||||
return ShopManager
|
||||
if name == 'PositionManager':
|
||||
from gvsdsdk.auth.managers import PositionManager
|
||||
globals()['PositionManager'] = PositionManager
|
||||
return PositionManager
|
||||
raise AttributeError(f"module 'gvsdsdk.auth' has no attribute {name!r}")
|
||||
33
gvsdsdk/auth/cookie.py
Normal file
33
gvsdsdk/auth/cookie.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
|
||||
ELT_COOKIE_NAME = getattr(settings, 'ELT_COOKIE_NAME', 'ELToken')
|
||||
ELT_COOKIE_DOMAIN = getattr(settings, 'ELT_COOKIE_DOMAIN', '.gvsds.com')
|
||||
ELT_COOKIE_MAX_AGE = getattr(settings, 'ELT_COOKIE_MAX_AGE', 30 * 24 * 60 * 60)
|
||||
|
||||
|
||||
def SetELTCookie(response, token_str):
|
||||
response.set_cookie(
|
||||
ELT_COOKIE_NAME, token_str,
|
||||
domain=ELT_COOKIE_DOMAIN,
|
||||
max_age=ELT_COOKIE_MAX_AGE,
|
||||
secure=True, httponly=True, samesite='None',
|
||||
)
|
||||
|
||||
|
||||
def DeleteELTCookie(response):
|
||||
response.delete_cookie(
|
||||
ELT_COOKIE_NAME,
|
||||
domain=ELT_COOKIE_DOMAIN,
|
||||
samesite='None',
|
||||
)
|
||||
|
||||
|
||||
def GetDefaultAvatar():
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
img = Image.new('RGB', (256, 256), (200, 200, 200))
|
||||
buf = BytesIO()
|
||||
img.save(buf, format='PNG')
|
||||
buf.seek(0)
|
||||
return HttpResponse(buf.read(), content_type='image/png')
|
||||
93
gvsdsdk/auth/crypto.py
Normal file
93
gvsdsdk/auth/crypto.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import os
|
||||
import bcrypt
|
||||
import base64
|
||||
import hashlib
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives import hashes as _crypto_hashes
|
||||
|
||||
|
||||
def HashPassword(password, rounds=12):
|
||||
if isinstance(password, str):
|
||||
password = password.encode('utf-8')
|
||||
return bcrypt.hashpw(password, bcrypt.gensalt(rounds=rounds))
|
||||
|
||||
|
||||
def VerifyPassword(password, hashed):
|
||||
if isinstance(password, str):
|
||||
password = password.encode('utf-8')
|
||||
if isinstance(hashed, str):
|
||||
hashed = hashed.encode('utf-8')
|
||||
return bcrypt.checkpw(password, hashed)
|
||||
|
||||
|
||||
def _derive_key(master_secret):
|
||||
"""使用 HKDF-SHA256 从任意长度的主密钥派生 256-bit AES 密钥"""
|
||||
if isinstance(master_secret, str):
|
||||
master_secret = master_secret.encode('utf-8')
|
||||
hkdf = HKDF(
|
||||
algorithm=_crypto_hashes.SHA256(),
|
||||
length=32,
|
||||
salt=b'gvsds-crypto-salt',
|
||||
info=b'aes-gcm-256',
|
||||
)
|
||||
return hkdf.derive(master_secret)
|
||||
|
||||
|
||||
def SymmetricEncrypt(plaintext, master_secret):
|
||||
"""使用 AES-256-GCM 加密
|
||||
|
||||
输出格式: base64(nonce_12 + ciphertext + tag_16)
|
||||
"""
|
||||
if isinstance(plaintext, str):
|
||||
plaintext = plaintext.encode('utf-8')
|
||||
key = _derive_key(master_secret)
|
||||
aesgcm = AESGCM(key)
|
||||
nonce = os.urandom(12)
|
||||
ciphertext_with_tag = aesgcm.encrypt(nonce, plaintext, None)
|
||||
return base64.b64encode(nonce + ciphertext_with_tag).decode('ascii')
|
||||
|
||||
|
||||
def SymmetricDecrypt(ciphertext_b64, master_secret):
|
||||
"""使用 AES-256-GCM 解密
|
||||
|
||||
输入格式: base64(nonce_12 + ciphertext + tag_16)
|
||||
"""
|
||||
raw = base64.b64decode(ciphertext_b64)
|
||||
nonce = raw[:12]
|
||||
ciphertext_with_tag = raw[12:]
|
||||
key = _derive_key(master_secret)
|
||||
aesgcm = AESGCM(key)
|
||||
plaintext = aesgcm.decrypt(nonce, ciphertext_with_tag, None)
|
||||
return plaintext.decode('utf-8')
|
||||
|
||||
|
||||
def FileSHA1(filepath):
|
||||
sha1 = hashlib.sha1()
|
||||
with open(filepath, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
sha1.update(chunk)
|
||||
return sha1.hexdigest()
|
||||
|
||||
|
||||
def FileSHA256(filepath):
|
||||
sha256 = hashlib.sha256()
|
||||
with open(filepath, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
sha256.update(chunk)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def BytesSHA1(data):
|
||||
return hashlib.sha1(data).hexdigest()
|
||||
|
||||
|
||||
def BytesSHA256(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
46
gvsdsdk/auth/elt_auth.py
Normal file
46
gvsdsdk/auth/elt_auth.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import uuid
|
||||
from gvsdsdk.auth.cookie import ELT_COOKIE_NAME
|
||||
|
||||
|
||||
class ELTAuthentication:
|
||||
def authenticate(self, request):
|
||||
elt_token = request.COOKIES.get(ELT_COOKIE_NAME)
|
||||
if not elt_token:
|
||||
return None
|
||||
|
||||
try:
|
||||
token_uuid = uuid.UUID(elt_token)
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
from gvsdsdk.models import User, EphemeralToken
|
||||
import datetime
|
||||
|
||||
try:
|
||||
elt = EphemeralToken.objects.get(
|
||||
ELTUUID=token_uuid,
|
||||
ELTStatus=1,
|
||||
)
|
||||
except EphemeralToken.DoesNotExist:
|
||||
return None
|
||||
|
||||
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
||||
if elt.ELTEndTime.replace(tzinfo=None) < now_utc.replace(tzinfo=None):
|
||||
return None
|
||||
|
||||
# 滑动续期:剩余时间不足7天时,延长30天
|
||||
remaining = elt.ELTEndTime.replace(tzinfo=None) - now_utc.replace(tzinfo=None)
|
||||
if remaining < datetime.timedelta(days=7):
|
||||
elt.ELTEndTime = now_utc + datetime.timedelta(days=30)
|
||||
elt.save(update_fields=['ELTEndTime'])
|
||||
|
||||
try:
|
||||
user = User.objects.get(UserUUID=elt.ELTUserUUID)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
request.elt = elt
|
||||
return (user, elt_token)
|
||||
|
||||
def authenticate_header(self, request):
|
||||
return 'Bearer'
|
||||
265
gvsdsdk/auth/managers.py
Normal file
265
gvsdsdk/auth/managers.py
Normal file
@@ -0,0 +1,265 @@
|
||||
import uuid
|
||||
import datetime
|
||||
from django.contrib.auth.models import BaseUserManager
|
||||
|
||||
from gvsdsdk.models import Role, UserRole, RolePermission, Permission, Shop, RoleShop, Position, PositionTemplate, PositionTemplatePermission, UserDept
|
||||
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
"""用户管理器——提供用户创建与超级用户创建。
|
||||
方法名保留蛇形命名以兼容 Django 内部约定。"""
|
||||
|
||||
def create_user(self, username, password=None, email='', **extra_fields):
|
||||
"""创建普通用户"""
|
||||
if not username:
|
||||
raise ValueError('The Username field must be set')
|
||||
user = self.model(
|
||||
UserUUID=uuid.uuid4().bytes,
|
||||
UserName=username,
|
||||
UserEmail=email or '',
|
||||
**extra_fields
|
||||
)
|
||||
if password:
|
||||
user.SetPassword(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, password=None, **extra_fields):
|
||||
"""创建超级用户"""
|
||||
extra_fields.setdefault('UserAccountLicense', 1)
|
||||
extra_fields.setdefault('UserPositionStatus', 0)
|
||||
extra_fields.setdefault('IsStaff', True)
|
||||
extra_fields.setdefault('IsSuperuser', True)
|
||||
return self.create_user(username, password, **extra_fields)
|
||||
|
||||
|
||||
class RoleManager:
|
||||
"""角色管理器——提供角色创建、分配、权限管理"""
|
||||
|
||||
@staticmethod
|
||||
def CreateRole(Name, TenantUUID, AssignScope=0, AssignScopeUUID=None, RoleType=2):
|
||||
"""创建角色"""
|
||||
role = Role(
|
||||
RoleUUID=uuid.uuid4().bytes,
|
||||
TenantUUID=TenantUUID,
|
||||
RoleName=Name,
|
||||
RoleType=RoleType,
|
||||
RoleStatus=1,
|
||||
AssignScope=AssignScope,
|
||||
AssignScopeUUID=AssignScopeUUID,
|
||||
RoleDesc='',
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
role.save()
|
||||
return role
|
||||
|
||||
@staticmethod
|
||||
def AssignRole(UserUUID, RoleUUID, CompanyUUID):
|
||||
"""为用户分配角色"""
|
||||
user_role = UserRole(
|
||||
UserRoleUUID=uuid.uuid4().bytes,
|
||||
UserUUID=UserUUID,
|
||||
RoleUUID=RoleUUID,
|
||||
CompanyUUID=CompanyUUID,
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
user_role.save()
|
||||
return user_role
|
||||
|
||||
@staticmethod
|
||||
def RemoveRole(UserUUID, RoleUUID, CompanyUUID=None):
|
||||
"""移除用户的指定角色"""
|
||||
qs = UserRole.objects.filter(UserUUID=UserUUID, RoleUUID=RoleUUID)
|
||||
if CompanyUUID is not None:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
deleted, _ = qs.delete()
|
||||
return deleted > 0
|
||||
|
||||
@staticmethod
|
||||
def GetRolesForUser(UserUUID, CompanyUUID=None):
|
||||
"""获取用户的角色"""
|
||||
qs = UserRole.objects.filter(UserUUID=UserUUID)
|
||||
if CompanyUUID is not None:
|
||||
qs = qs.filter(CompanyUUID=CompanyUUID)
|
||||
role_uuids = qs.values_list('RoleUUID', flat=True)
|
||||
return Role.objects.filter(RoleUUID__in=role_uuids)
|
||||
|
||||
@staticmethod
|
||||
def GetUsersForRole(RoleUUID):
|
||||
"""获取拥有指定角色的所有用户"""
|
||||
from gvsdsdk.models import User
|
||||
user_uuids = UserRole.objects.filter(RoleUUID=RoleUUID).values_list('UserUUID', flat=True)
|
||||
return User.objects.filter(UserUUID__in=user_uuids)
|
||||
|
||||
@staticmethod
|
||||
def AssignPermission(RoleUUID, PermUUID):
|
||||
"""为角色分配权限"""
|
||||
rp = RolePermission(
|
||||
RolePermUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=RoleUUID,
|
||||
PermUUID=PermUUID,
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
rp.save()
|
||||
return rp
|
||||
|
||||
@staticmethod
|
||||
def RemovePermission(RoleUUID, PermUUID):
|
||||
"""移除角色的指定权限"""
|
||||
deleted, _ = RolePermission.objects.filter(RoleUUID=RoleUUID, PermUUID=PermUUID).delete()
|
||||
return deleted > 0
|
||||
|
||||
@staticmethod
|
||||
def GetPermissionsForRole(RoleUUID):
|
||||
"""获取角色的所有权限"""
|
||||
perm_uuids = RolePermission.objects.filter(RoleUUID=RoleUUID).values_list('PermUUID', flat=True)
|
||||
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
|
||||
|
||||
|
||||
class ShopManager:
|
||||
"""商铺管理器——提供商铺创建、角色关联、查询"""
|
||||
|
||||
@staticmethod
|
||||
def CreateShop(Name, CompanyUUID, Code='', ShopType=0, **Kwargs):
|
||||
"""创建商铺"""
|
||||
shop = Shop(
|
||||
ShopUUID=uuid.uuid4().bytes,
|
||||
CompanyUUID=CompanyUUID,
|
||||
ShopName=Name,
|
||||
ShopCode=Code,
|
||||
ShopType=ShopType,
|
||||
ShopStatus=1,
|
||||
ShopAddress=Kwargs.get('ShopAddress', ''),
|
||||
ShopContact=Kwargs.get('ShopContact', ''),
|
||||
ShopPhone=Kwargs.get('ShopPhone', ''),
|
||||
ShopDesc=Kwargs.get('ShopDesc', ''),
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
UpdateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
shop.save()
|
||||
return shop
|
||||
|
||||
@staticmethod
|
||||
def AssignRole(RoleUUID, ShopUUID, IsMainShop=False):
|
||||
"""为角色关联商铺"""
|
||||
role_shop = RoleShop(
|
||||
RoleShopUUID=uuid.uuid4().bytes,
|
||||
RoleUUID=RoleUUID,
|
||||
ShopUUID=ShopUUID,
|
||||
IsMainShop=1 if IsMainShop else 0,
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
role_shop.save()
|
||||
return role_shop
|
||||
|
||||
@staticmethod
|
||||
def RemoveRole(RoleUUID, ShopUUID):
|
||||
"""移除角色与商铺的关联"""
|
||||
deleted, _ = RoleShop.objects.filter(RoleUUID=RoleUUID, ShopUUID=ShopUUID).delete()
|
||||
return deleted > 0
|
||||
|
||||
@staticmethod
|
||||
def GetShopsForRole(RoleUUID):
|
||||
"""获取角色关联的所有商铺"""
|
||||
shop_uuids = RoleShop.objects.filter(RoleUUID=RoleUUID).values_list('ShopUUID', flat=True)
|
||||
return Shop.objects.filter(ShopUUID__in=shop_uuids)
|
||||
|
||||
@staticmethod
|
||||
def GetRolesForShop(ShopUUID):
|
||||
"""获取商铺关联的所有角色"""
|
||||
role_uuids = RoleShop.objects.filter(ShopUUID=ShopUUID).values_list('RoleUUID', flat=True)
|
||||
return Role.objects.filter(RoleUUID__in=role_uuids)
|
||||
|
||||
@staticmethod
|
||||
def GetMainShopForRole(RoleUUID):
|
||||
"""获取角色的主商铺"""
|
||||
rs = RoleShop.objects.filter(RoleUUID=RoleUUID, IsMainShop=1).first()
|
||||
if rs:
|
||||
return Shop.objects.filter(ShopUUID=rs.ShopUUID).first()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def GetShopsForCompany(CompanyUUID):
|
||||
"""获取公司下的所有商铺"""
|
||||
return Shop.objects.filter(CompanyUUID=CompanyUUID, ShopStatus=1)
|
||||
|
||||
|
||||
class PositionManager:
|
||||
"""职务管理器——提供人事职务标签创建、岗位模板管理"""
|
||||
|
||||
@staticmethod
|
||||
def CreatePosition(Name, TenantUUID, Code='', Level=0, **Kwargs):
|
||||
"""创建人事职务标签"""
|
||||
position = Position(
|
||||
PositionUUID=uuid.uuid4().bytes,
|
||||
TenantUUID=TenantUUID,
|
||||
PositionName=Name,
|
||||
PositionCode=Code,
|
||||
PositionLevel=Level,
|
||||
PositionStatus=1,
|
||||
PositionDesc=Kwargs.get('PositionDesc', ''),
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
UpdateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
position.save()
|
||||
return position
|
||||
|
||||
@staticmethod
|
||||
def GetPositionsForTenant(TenantUUID):
|
||||
"""获取租户下的所有职务"""
|
||||
return Position.objects.filter(TenantUUID=TenantUUID, PositionStatus=1)
|
||||
|
||||
@staticmethod
|
||||
def CreatePositionTemplate(Name, TenantUUID, Code='', **Kwargs):
|
||||
"""创建岗位模板"""
|
||||
template = PositionTemplate(
|
||||
PositionTemplateUUID=uuid.uuid4().bytes,
|
||||
TenantUUID=TenantUUID,
|
||||
TemplateName=Name,
|
||||
TemplateCode=Code,
|
||||
TemplateStatus=1,
|
||||
TemplateDesc=Kwargs.get('TemplateDesc', ''),
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
UpdateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
template.save()
|
||||
return template
|
||||
|
||||
@staticmethod
|
||||
def AssignPermToTemplate(TemplateUUID, PermUUID):
|
||||
"""为岗位模板关联权限"""
|
||||
ptp = PositionTemplatePermission(
|
||||
PTPermUUID=uuid.uuid4().bytes,
|
||||
PositionTemplateUUID=TemplateUUID,
|
||||
PermUUID=PermUUID,
|
||||
CreateTime=datetime.datetime.utcnow(),
|
||||
)
|
||||
ptp.save()
|
||||
return ptp
|
||||
|
||||
@staticmethod
|
||||
def RemovePermFromTemplate(TemplateUUID, PermUUID):
|
||||
"""移除岗位模板与权限的关联"""
|
||||
deleted, _ = PositionTemplatePermission.objects.filter(
|
||||
PositionTemplateUUID=TemplateUUID, PermUUID=PermUUID
|
||||
).delete()
|
||||
return deleted > 0
|
||||
|
||||
@staticmethod
|
||||
def GetPermsForTemplate(TemplateUUID):
|
||||
"""获取岗位模板关联的所有权限"""
|
||||
perm_uuids = PositionTemplatePermission.objects.filter(
|
||||
PositionTemplateUUID=TemplateUUID
|
||||
).values_list('PermUUID', flat=True)
|
||||
return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1)
|
||||
|
||||
@staticmethod
|
||||
def GetTemplatesForTenant(TenantUUID):
|
||||
"""获取租户下的所有岗位模板"""
|
||||
return PositionTemplate.objects.filter(TenantUUID=TenantUUID, TemplateStatus=1)
|
||||
|
||||
@staticmethod
|
||||
def CopyTemplateToRole(TemplateUUID, RoleName, TenantUUID):
|
||||
"""从岗位模板复制权限到新角色"""
|
||||
template = PositionTemplate.objects.get(PositionTemplateUUID=TemplateUUID)
|
||||
return template.CopyToRole(RoleName, TenantUUID)
|
||||
54
gvsdsdk/auth/middleware.py
Normal file
54
gvsdsdk/auth/middleware.py
Normal file
@@ -0,0 +1,54 @@
|
||||
from gvsdsdk.auth.cookie import ELT_COOKIE_NAME
|
||||
from gvsdsdk.models import UserRole, Role
|
||||
from gvsdsdk.model_utils import UUIDObj
|
||||
|
||||
|
||||
class ELTAuthMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
elt_token = request.COOKIES.get(ELT_COOKIE_NAME)
|
||||
if elt_token:
|
||||
request.elt_token = elt_token
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
|
||||
class CurrentRoleMiddleware:
|
||||
"""解析 CurrentRoleUUID Cookie,设置 request.current_role 和 request.current_company_uuid
|
||||
|
||||
request.current_role : gvsdsdk.models.Role 实例(或 None)
|
||||
request.current_company_uuid : 通过 UserRole.CompanyUUID 获取当前角色所属公司UUID
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
request.current_role = None
|
||||
request.current_company_uuid = None
|
||||
|
||||
# 仅对已认证用户处理角色
|
||||
if hasattr(request, 'user') and request.user and request.user.is_authenticated:
|
||||
role_uuid_str = request.COOKIES.get('CurrentRoleUUID', '')
|
||||
if role_uuid_str:
|
||||
try:
|
||||
role_uuid_bytes = UUIDObj(role_uuid_str).bytes
|
||||
# gvsdsdk.UserRole 的 RoleUUID 是 UUIDField,不能用 select_related
|
||||
user_role = UserRole.objects.filter(
|
||||
UserUUID=request.user.UserUUID,
|
||||
RoleUUID=role_uuid_bytes
|
||||
).first()
|
||||
if user_role:
|
||||
# 单独查询 Role 实例
|
||||
role = Role.objects.filter(RoleUUID=role_uuid_bytes).first()
|
||||
if role:
|
||||
request.current_role = role
|
||||
# 通过 UserRole.CompanyUUID 获取当前公司
|
||||
if user_role.CompanyUUID:
|
||||
request.current_company_uuid = user_role.CompanyUUID
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self.get_response(request)
|
||||
109
gvsdsdk/auth/permissions.py
Normal file
109
gvsdsdk/auth/permissions.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from gvsdsdk.models import User, UserRole, Role, RolePermission, Permission, UserABAC
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
|
||||
class IsAdminUser(BasePermission):
|
||||
"""仅允许管理员(IsStaff 或 IsSuperuser)访问"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
user = request.user
|
||||
if not user or not user.is_authenticated:
|
||||
return False
|
||||
if not isinstance(user, User):
|
||||
return False
|
||||
return user.IsStaff or user.IsSuperuser
|
||||
|
||||
|
||||
class IsCompanyMember(BasePermission):
|
||||
"""验证用户当前角色属于目标数据所在公司"""
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if not request.current_company_uuid:
|
||||
return False
|
||||
obj_company = getattr(obj, 'CompanyUUID_id', None) or getattr(obj, 'CompanyUUID', None)
|
||||
if obj_company is None:
|
||||
return True # 无公司归属的数据不做限制
|
||||
return str(obj_company) == str(request.current_company_uuid)
|
||||
|
||||
|
||||
class CompanyScopedMixin:
|
||||
"""ViewSet Mixin:自动按当前角色所属公司过滤 queryset"""
|
||||
company_field = 'CompanyUUID' # 可在 ViewSet 中覆盖
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
company_uuid = getattr(self.request, 'current_company_uuid', None)
|
||||
if company_uuid and hasattr(qs.model, self.company_field):
|
||||
qs = qs.filter(**{self.company_field: company_uuid})
|
||||
return qs
|
||||
|
||||
def perform_create(self, serializer):
|
||||
company_uuid = getattr(self.request, 'current_company_uuid', None)
|
||||
if company_uuid and hasattr(serializer.Meta.model, 'CompanyUUID'):
|
||||
serializer.save(CompanyUUID=company_uuid)
|
||||
else:
|
||||
serializer.save()
|
||||
|
||||
|
||||
def PermissionVerifier(permission, attributes, user=None, request=None):
|
||||
"""权限验证器——混合 ABAC + RBAC 检查
|
||||
|
||||
RBAC 仅检查当前角色(CurrentRoleUUID Cookie 指定的角色)的权限,
|
||||
通过 RolePermission → Permission 链路查询。
|
||||
超级管理员/员工直接放行。
|
||||
"""
|
||||
if not permission:
|
||||
return True
|
||||
if user is None:
|
||||
return False
|
||||
if not isinstance(user, User):
|
||||
return False
|
||||
|
||||
# 超级管理员和员工直接放行
|
||||
if user.IsSuperuser or user.IsStaff:
|
||||
return True
|
||||
|
||||
current_role = getattr(request, 'current_role', None) if request else None
|
||||
|
||||
for attr in attributes:
|
||||
# ABAC 检查
|
||||
abac_match = UserABAC.objects.filter(
|
||||
UserUUID=user.UserUUID,
|
||||
HierarchicalPermissionIdentifiers=attr
|
||||
).first()
|
||||
if abac_match:
|
||||
continue
|
||||
|
||||
# RBAC 检查:仅检查当前角色
|
||||
if current_role and isinstance(current_role, Role):
|
||||
if current_role.RoleStatus != 1:
|
||||
return False
|
||||
perm_uuids = RolePermission.objects.filter(
|
||||
RoleUUID=current_role.RoleUUID
|
||||
).values_list('PermUUID', flat=True)
|
||||
rbac_result = Permission.objects.filter(
|
||||
PermUUID__in=perm_uuids,
|
||||
PermCode=attr,
|
||||
PermStatus=1,
|
||||
).exists()
|
||||
else:
|
||||
# 无 current_role 时回退到用户所有活跃角色
|
||||
user_role_uuids = UserRole.objects.filter(
|
||||
UserUUID=user.UserUUID
|
||||
).values_list('RoleUUID', flat=True)
|
||||
active_role_uuids = Role.objects.filter(
|
||||
RoleUUID__in=user_role_uuids,
|
||||
RoleStatus=1,
|
||||
).values_list('RoleUUID', flat=True)
|
||||
perm_uuids = RolePermission.objects.filter(
|
||||
RoleUUID__in=active_role_uuids
|
||||
).values_list('PermUUID', flat=True)
|
||||
rbac_result = Permission.objects.filter(
|
||||
PermUUID__in=perm_uuids,
|
||||
PermCode=attr,
|
||||
PermStatus=1,
|
||||
).exists()
|
||||
|
||||
if rbac_result:
|
||||
continue
|
||||
return False
|
||||
return True
|
||||
Reference in New Issue
Block a user