How CMS Authorization Is Structured with Groups and Permissions in Mini-Shop-Server
Mini-shop-server implements a group-based RBAC system where users inherit permissions through group membership, with each API endpoint declaring required access metadata that is validated against the Auth table at request time.
The mini-shop-server repository uses a classic group-based permission model for its Content Management System (CMS). This architecture separates users into groups, links those groups to specific permission records, and validates access at runtime by matching endpoint metadata against stored authorizations.
Core Models: Groups and Auth Records
The authorization system relies on two primary SQLAlchemy models that establish the relationship between users and their permitted actions.
The Group Model
Each CMS user belongs to exactly one group, defined in app/models/group.py. The Group class stores the group identity and maintains relationships to routes and UI elements.
# app/models/group.py
class Group(Base):
__tablename__ = 'group'
id = Column(Integer, primary_key=True)
name = Column(String(60), unique=True) # 权限组名称
info = Column(String(255)) # 描述
route = relationship('Route', secondary='menu', back_populates='group')
elements = relationship('Element', secondary='group_2_element', back_populates='groups')
The auth_list property (not fully shown in the excerpt) collects all Auth records associated with the group, effectively representing the permission portfolio available to group members.
The Auth Model
Individual permissions are stored as Auth records in app/models/auth.py. Each row links a specific group to a permission name and its functional module.
# app/models/auth.py
class Auth(Base):
__tablename__ = 'auth'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, nullable=False) # 所属权限组 id
name = Column(String(60)) # 权限字段 (e.g. “新增商品”)
module = Column(String(50)) # 权限所属模块 (e.g. “商品”)
This design allows granular control where a group can be granted "新增商品" (Add Product) permission within the "商品" (Product) module without inheriting unrelated privileges.
Declaring Permissions with Route Metadata
The system uses a metadata registration pattern to declare what permissions each API endpoint requires. This metadata is later used during the authorization check.
The Meta Namedtuple and Registry
In app/core/redprint.py, the system defines a Meta namedtuple to store permission identifiers and a global registry to map view functions to their requirements.
# app/core/redprint.py
from collections import namedtuple
Meta = namedtuple('Meta', ['name', 'module'])
route_meta_infos = {} # key: unique view-function id, value: Meta
The route_meta Decorator
The route_meta method attaches permission requirements to view functions. When applied, it stores the permission name and module in the route_meta_infos dictionary.
# app/core/redprint.py
def route_meta(self, auth: str, module: str = 'common', mount: bool = True):
# registers Meta for the view function
def decorator(f):
# ... registration logic ...
return f
return decorator
In practice, CMS endpoints combine this decorator with the @auth.group_required decorator to enforce checks:
# app/api/cms/product.py
from app.core.auth import auth
from app.core.redprint import api
@api.route('/product/add', methods=['POST'])
@auth.group_required
@api.route_meta('新增商品', module='商品')
def add_product():
"""Add a new product (CMS)"""
return jsonify({'msg': 'product added'})
At application startup, the mount_route_meta_to_endpoint function in app/core/auth.py populates current_app.config['EP_META'], mapping Flask endpoint strings (e.g., cms.product_add) to their corresponding Meta objects.
Runtime Authorization Flow
When a protected request arrives, the system executes a multi-step validation process to determine if the user's group possesses the required permission.
Token Verification and Group Extraction
The verify_group callback in app/core/token_auth.py intercepts all requests to CMS endpoints decorated with @auth.group_required. It decrypts the JWT token to extract the user ID, retrieves the user record, and identifies the associated group_id.
# app/core/token_auth.py
@auth.verify_group
def verify_group(token, password):
(uid, ac_type, scope) = decrypt_token(token)
current_user = User.get_or_404(id=uid)
group_id = current_user.group_id
if not current_user.is_admin:
if group_id is None:
raise AuthFailed(msg='您还不属于任何权限组,请联系系统管理员获得权限')
allowed = is_in_auth_scope(group_id, request.endpoint)
if not allowed:
raise AuthFailed(msg='权限不够,请联系系统管理员获得权限')
g.user = current_user
If the user has no group_id, the request is rejected immediately. Super-admin users bypass this entire check via a separate verify_admin callback.
The is_in_auth_scope Validation
The core authorization logic resides in app/core/auth.py. The is_in_auth_scope function retrieves the endpoint's metadata from the EP_META registry and queries the Auth table for a matching record.
# app/core/auth.py
def is_in_auth_scope(group_id, endpoint):
meta = current_app.config['EP_META'].get(endpoint) # Meta(name, module)
allowed = False
if meta:
allowed = Auth.get(group_id=group_id, name=meta.name, module=meta.module)
return True if allowed else False
The function returns True only if the database contains an Auth row matching the user's group_id, the endpoint's permission name, and the endpoint's module. If no match exists, the request is denied.
Super-Admin Bypass
The system maintains a privileged administrative tier that circumvents group-based checks entirely. In app/core/token_auth.py, the verify_admin decorator handles authentication for super-admin-only endpoints.
# app/core/token_auth.py
@auth.verify_admin
def verify_admin(token, password):
(uid, ac_type, scope) = decrypt_token(token)
current_user = User.get_or_404(id=uid)
if not current_user.is_admin:
raise AuthFailed(msg='该接口为超级管理员权限操作')
g.user = current_user
When current_user.is_admin evaluates to True, the request proceeds without invoking is_in_auth_scope or validating group memberships.
Summary
- Group-based RBAC: Users are assigned to groups via
app/models/group.py, and permissions are granted to groups rather than individual users. - Auth records: The
Authmodel inapp/models/auth.pystores permission tuples of(name, module)linked to specific groups. - Route metadata: The
route_metadecorator inapp/core/redprint.pydeclares required permissions, which are mapped to endpoints in theEP_METAconfiguration. - Runtime validation: The
verify_groupcallback inapp/core/token_auth.pyextracts the user's group and callsis_in_auth_scopeto validate access against theAuthtable. - Admin override: Super-admins identified by
User.is_adminbypass all group permission checks through theverify_adminmechanism.
Frequently Asked Questions
How does the system map API endpoints to specific permission requirements?
The system uses a global registry called EP_META populated at application startup by mount_route_meta_to_endpoint in app/core/auth.py. When developers decorate a view function with @api.route_meta('新增商品', module='商品'), the metadata is stored and later mapped to the Flask endpoint string. During request processing, is_in_auth_scope retrieves this metadata via current_app.config['EP_META'].get(endpoint) to determine what permission name and module are required for access.
What happens if a CMS user does not belong to any group?
If current_user.group_id is None, the verify_group function in app/core/token_auth.py raises an AuthFailed exception with the message "您还不属于任何权限组,请联系系统管理员获得权限". This check occurs before any permission validation, ensuring that ungrouped users cannot access any CMS functionality protected by @auth.group_required.
How are permissions programmatically assigned to a group?
Permissions are created as Auth records linking a group ID to a permission name and module. You can add permissions via database operations:
from app.models.auth import Auth
from app.core.db import db
new_auth = Auth(group_id=2, name='新增商品', module='商品')
db.session.add(new_auth)
db.session.commit()
Once committed, any user with group_id=2 can access endpoints requiring the "新增商品" permission within the "商品" module.
Can individual users have permissions without being assigned to a group?
No. The architecture enforces group-based authorization exclusively. The verify_group logic explicitly checks for group_id presence and rejects requests from users without group membership. Individual permissions are not supported; all authorizations must be granted through the Auth table linked to a Group record. Super-admins bypass this system entirely via the is_admin flag rather than individual permission grants.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →