update acl

This commit is contained in:
pycook 2019-11-14 18:35:31 +08:00
parent 47c66be179
commit eaa5cb8bf1
14 changed files with 261 additions and 206 deletions

View File

@ -23,7 +23,7 @@ from api.extensions import (
rd,
)
from api.flask_cas import CAS
from api.models.account import User
from api.models.acl import User
HERE = os.path.abspath(os.path.dirname(__file__))
PROJECT_ROOT = os.path.join(HERE, os.pardir)

View File

@ -8,7 +8,7 @@ from flask import current_app, session, request, url_for, redirect
from flask_login import login_user, logout_user
from six.moves.urllib_request import urlopen
from api.models.account import UserCache
from api.lib.perm.acl.cache import UserCache
from .cas_urls import create_cas_login_url
from .cas_urls import create_cas_logout_url
from .cas_urls import create_cas_validate_url

View File

@ -7,7 +7,7 @@ from flask import g
from api.extensions import db
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import RelationTypeCache
from api.models.account import UserCache
from api.lib.perm.acl.cache import UserCache
from api.models.cmdb import Attribute
from api.models.cmdb import AttributeHistory
from api.models.cmdb import CIRelationHistory

View File

@ -3,6 +3,39 @@
from api.extensions import cache
from api.models.acl import Permission
from api.models.acl import Role
from api.models.acl import User
class UserCache(object):
PREFIX_ID = "User::uid::{0}"
PREFIX_NAME = "User::username::{0}"
PREFIX_NICK = "User::nickname::{0}"
@classmethod
def get(cls, key):
user = cache.get(cls.PREFIX_ID.format(key)) or \
cache.get(cls.PREFIX_NAME.format(key)) or \
cache.get(cls.PREFIX_NICK.format(key))
if not user:
user = User.query.get(key) or \
User.query.get_by_username(key) or \
User.query.get_by_nickname(key)
if user:
cls.set(user)
return user
@classmethod
def set(cls, user):
cache.set(cls.PREFIX_ID.format(user.uid), user)
cache.set(cls.PREFIX_NAME.format(user.username), user)
cache.set(cls.PREFIX_NICK.format(user.nickname), user)
@classmethod
def clean(cls, user):
cache.delete(cls.PREFIX_ID.format(user.uid))
cache.delete(cls.PREFIX_NAME.format(user.username))
cache.delete(cls.PREFIX_NICK.format(user.nickname))
class RoleCache(object):

View File

@ -18,13 +18,21 @@ from api.tasks.acl import role_rebuild
class RoleRelationCRUD(object):
@staticmethod
def get_parents(rids):
rids = [rids] if isinstance(rids, six.integer_types) else rids
def get_parents(rids=None, uids=None):
rid2uid = dict()
if uids is not None:
uids = [uids] if isinstance(uids, six.integer_types) else uids
rids = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.in_(uids))
rid2uid = {i.rid: i.uid for i in rids}
rids = [i.rid for i in rids]
else:
rids = [rids] if isinstance(rids, six.integer_types) else rids
res = db.session.query(RoleRelation).filter(
RoleRelation.child_id.in_(rids)).filter(RoleRelation.deleted.is_(False))
id2parents = {}
for i in res:
id2parents.setdefault(i.child_id, []).append(RoleCache.get(i.parent_id).to_dict())
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
return id2parents

45
api/lib/perm/acl/user.py Normal file
View File

@ -0,0 +1,45 @@
# -*- coding:utf-8 -*-
from flask import abort
from api.extensions import db
from api.lib.perm.acl.cache import UserCache
from api.models.acl import User
class UserCRUD(object):
@staticmethod
def search(q, page=1, page_size=None):
query = db.session.query(User).filter(User.deleted.is_(False))
if q:
query = query.filter(User.username.ilike('%{0}%'.format(q)))
numfound = query.count()
return numfound, query.offset((page - 1) * page_size).limit(page_size)
@staticmethod
def add(**kwargs):
existed = User.get_by(username=kwargs['username'], email=kwargs['email'])
existed and abort(400, "User <{0}> is already existed".format(kwargs['username']))
kwargs['nickname'] = kwargs['username'] if not kwargs.get('nickname') else kwargs['nickname']
kwargs['block'] = 0
return User.create(**kwargs)
@staticmethod
def update(rid, **kwargs):
user = User.get_by_id(rid) or abort(404, "User <{0}> does not exist".format(rid))
UserCache.clean(rid)
return user.update(**kwargs)
@classmethod
def delete(cls, uid):
user = User.get_by_id(uid) or abort(404, "User <{0}> does not exist".format(uid))
UserCache.clean(user)
user.soft_delete()

View File

@ -13,8 +13,8 @@ from flask import request
from flask import session
from flask_login import login_user
from api.models.account import User
from api.models.account import UserCache
from api.models.acl import User
from api.lib.perm.acl.cache import UserCache
def _auth_with_key():

View File

@ -1,6 +1,5 @@
# -*- coding:utf-8 -*-
from .account import User
from .cmdb import *
from .acl import *

View File

@ -1,139 +0,0 @@
# -*- coding:utf-8 -*-
import copy
import hashlib
from datetime import datetime
from flask import current_app
from flask_sqlalchemy import BaseQuery
from api.extensions import cache
from api.extensions import db
from api.lib.database import CRUDModel
class UserQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(UserQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password):
user = self.filter(db.or_(User.username == login,
User.email == login)).first()
if user:
current_app.logger.info(user)
authenticated = user.check_password(password)
else:
authenticated = False
return user, authenticated
def authenticate_with_key(self, key, secret, args, path):
user = self.filter(User.key == key).filter(User.block == 0).first()
if not user:
return None, False
if user and hashlib.sha1('{0}{1}{2}'.format(
path, user.secret, "".join(args)).encode("utf-8")).hexdigest() == secret:
authenticated = True
else:
authenticated = False
return user, authenticated
def search(self, key):
query = self.filter(db.or_(User.email == key,
User.nickname.ilike('%' + key + '%'),
User.username.ilike('%' + key + '%')))
return query
def get_by_username(self, username):
user = self.filter(User.username == username).first()
return user
def get_by_nickname(self, nickname):
user = self.filter(User.nickname == nickname).first()
return user
def get(self, uid):
user = self.filter(User.uid == uid).first()
return copy.deepcopy(user)
class User(CRUDModel):
__tablename__ = 'users'
__bind_key__ = "user"
query_class = UserQuery
uid = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(32), unique=True)
nickname = db.Column(db.String(20), nullable=True)
department = db.Column(db.String(20))
catalog = db.Column(db.String(64))
email = db.Column(db.String(100), unique=True, nullable=False)
mobile = db.Column(db.String(14), unique=True)
_password = db.Column("password", db.String(80))
key = db.Column(db.String(32), nullable=False)
secret = db.Column(db.String(32), nullable=False)
date_joined = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime, default=datetime.utcnow)
block = db.Column(db.Boolean, default=False)
has_logined = db.Column(db.Boolean, default=False)
wx_id = db.Column(db.String(32))
avatar = db.Column(db.String(128))
def __str__(self):
return self.username
def is_active(self):
return not self.block
def get_id(self):
return self.uid
@staticmethod
def is_authenticated():
return True
def _get_password(self):
return self._password
def _set_password(self, password):
self._password = hashlib.md5(password.encode('utf-8')).hexdigest()
password = db.synonym("_password",
descriptor=property(_get_password,
_set_password))
def check_password(self, password):
if self.password is None:
return False
return self.password == password
class UserCache(object):
PREFIX_ID = "User::uid::{0}"
PREFIX_NAME = "User::username::{0}"
PREFIX_NICK = "User::nickname::{0}"
@classmethod
def get(cls, key):
user = cache.get(cls.PREFIX_ID.format(key)) or \
cache.get(cls.PREFIX_NAME.format(key)) or \
cache.get(cls.PREFIX_NICK.format(key))
if not user:
user = User.query.get(key) or \
User.query.get_by_username(key) or \
User.query.get_by_nickname(key)
if user:
cls.set(user)
return user
@classmethod
def set(cls, user):
cache.set(cls.PREFIX_ID.format(user.uid), user)
cache.set(cls.PREFIX_NAME.format(user.username), user)
cache.set(cls.PREFIX_NICK.format(user.nickname), user)
@classmethod
def clean(cls, user):
cache.delete(cls.PREFIX_ID.format(user.uid))
cache.delete(cls.PREFIX_NAME.format(user.username))
cache.delete(cls.PREFIX_NICK.format(user.nickname))

View File

@ -1,6 +1,15 @@
# -*- coding:utf-8 -*-
import copy
import hashlib
from datetime import datetime
from flask import current_app
from flask_sqlalchemy import BaseQuery
from api.extensions import db
from api.lib.database import CRUDModel
from api.lib.database import Model
@ -13,6 +22,101 @@ class App(Model):
secret_key = db.Column(db.Text)
class UserQuery(BaseQuery):
def _join(self, *args, **kwargs):
super(UserQuery, self)._join(*args, **kwargs)
def authenticate(self, login, password):
user = self.filter(db.or_(User.username == login,
User.email == login)).first()
if user:
current_app.logger.info(user)
authenticated = user.check_password(password)
else:
authenticated = False
return user, authenticated
def authenticate_with_key(self, key, secret, args, path):
user = self.filter(User.key == key).filter(User.block == 0).first()
if not user:
return None, False
if user and hashlib.sha1('{0}{1}{2}'.format(
path, user.secret, "".join(args)).encode("utf-8")).hexdigest() == secret:
authenticated = True
else:
authenticated = False
return user, authenticated
def search(self, key):
query = self.filter(db.or_(User.email == key,
User.nickname.ilike('%' + key + '%'),
User.username.ilike('%' + key + '%')))
return query
def get_by_username(self, username):
user = self.filter(User.username == username).first()
return user
def get_by_nickname(self, nickname):
user = self.filter(User.nickname == nickname).first()
return user
def get(self, uid):
user = self.filter(User.uid == uid).first()
return copy.deepcopy(user)
class User(CRUDModel):
__tablename__ = 'users'
__bind_key__ = "user"
query_class = UserQuery
uid = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(32), unique=True)
nickname = db.Column(db.String(20), nullable=True)
department = db.Column(db.String(20))
catalog = db.Column(db.String(64))
email = db.Column(db.String(100), unique=True, nullable=False)
mobile = db.Column(db.String(14), unique=True)
_password = db.Column("password", db.String(80))
key = db.Column(db.String(32), nullable=False)
secret = db.Column(db.String(32), nullable=False)
date_joined = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime, default=datetime.utcnow)
block = db.Column(db.Boolean, default=False)
has_logined = db.Column(db.Boolean, default=False)
wx_id = db.Column(db.String(32))
avatar = db.Column(db.String(128))
def __str__(self):
return self.username
def is_active(self):
return not self.block
def get_id(self):
return self.uid
@staticmethod
def is_authenticated():
return True
def _get_password(self):
return self._password
def _set_password(self, password):
self._password = hashlib.md5(password.encode('utf-8')).hexdigest()
password = db.synonym("_password",
descriptor=property(_get_password,
_set_password))
def check_password(self, password):
if self.password is None:
return False
return self.password == password
class Role(Model):
__tablename__ = "acl_roles"

View File

@ -6,7 +6,6 @@ from flask import Blueprint
from flask_restful import Api
from api.resource import register_resources
from .permission import GetResourcesView, HasPermissionView, GetUserInfoView
from .account import LoginView, LogoutView
HERE = os.path.abspath(os.path.dirname(__file__))
@ -17,13 +16,6 @@ account_rest = Api(blueprint_account)
account_rest.add_resource(LoginView, LoginView.url_prefix)
account_rest.add_resource(LogoutView, LogoutView.url_prefix)
# permission
blueprint_perm_v01 = Blueprint('permission_api', __name__, url_prefix='/api/v1/perms')
perm_rest = Api(blueprint_perm_v01)
perm_rest.add_resource(GetResourcesView, GetResourcesView.url_prefix)
perm_rest.add_resource(HasPermissionView, HasPermissionView.url_prefix)
perm_rest.add_resource(GetUserInfoView, GetUserInfoView.url_prefix)
# cmdb
blueprint_cmdb_v01 = Blueprint('cmdb_api_v01', __name__, url_prefix='/api/v0.1')

View File

@ -10,7 +10,7 @@ from flask_login import login_user, logout_user
from api.lib.decorator import args_required
from api.lib.perm.auth import auth_abandoned
from api.models.account import User
from api.models.acl import User
from api.resource import APIView
@ -24,6 +24,8 @@ class LoginView(APIView):
username = request.values.get("username") or request.values.get("email")
password = request.values.get("password")
user, authenticated = User.query.authenticate(username, password)
if not user:
return abort(403, "User <{0}> does not exist".format(username))
if not authenticated:
return abort(403, "invalid username or password")

60
api/views/acl/user.py Normal file
View File

@ -0,0 +1,60 @@
# -*- coding:utf-8 -*-
from flask import request
from flask import session
from flask_login import current_user
from api.lib.decorator import args_required
from api.lib.perm.acl.user import UserCRUD
from api.lib.perm.acl.role import RoleRelationCRUD
from api.lib.utils import get_page
from api.lib.utils import get_page_size
from api.resource import APIView
class GetUserInfoView(APIView):
url_prefix = "/users/info"
def get(self):
name = session.get("acl", {}).get("nickName") or session.get("CAS_USERNAME") or current_user.nickname
role = dict(permissions=session.get("acl", {}).get("parentRoles", []) or ["admin"])
avatar = session.get("acl", {}).get("avatar") or current_user.avatar
return self.jsonify(result=dict(name=name,
role=role,
avatar=avatar))
class UserView(APIView):
url_prefix = ("/users", "/users/<int:uid>")
def get(self):
page = get_page(request.values.get('page', 1))
page_size = get_page_size(request.values.get('page_size'))
q = request.values.get("q")
numfound, users = UserCRUD.search(q, page, page_size)
id2parents = RoleRelationCRUD.get_parents(uids=[i.uid for i in users])
return self.jsonify(numfound=numfound,
page=page,
page_size=page_size,
id2parents=id2parents,
users=[i.to_dict() for i in users])
@args_required('username')
@args_required('email')
def post(self):
user = UserCRUD.add(**request.values)
return self.jsonify(user.to_dict())
def put(self, uid):
user = UserCRUD.update(uid, **request.values)
return self.jsonify(user.to_dict())
def delete(self, uid):
UserCRUD.delete(uid)
return self.jsonify(uid=uid)

View File

@ -1,49 +0,0 @@
# -*- coding:utf-8 -*-
from flask import request
from flask import session
from flask_login import current_user
from api.lib.decorator import args_required
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import validate_permission
from api.resource import APIView
class HasPermissionView(APIView):
url_prefix = "/validate"
@args_required("resource")
@args_required("resource_type")
@args_required("perm")
def get(self):
resource = request.values.get("resource")
resource_type = request.values.get("resource_type")
perm = request.values.get("perm")
validate_permission(resource, resource_type, perm)
return self.jsonify(is_valid=True)
def post(self):
self.get()
class GetResourcesView(APIView):
url_prefix = "/resources"
@args_required("resource_type")
def get(self):
resource_type = request.values.get("resource_type")
res = ACLManager().get_resources(resource_type)
return self.jsonify(res)
class GetUserInfoView(APIView):
url_prefix = "/user/info"
def get(self):
name = session.get("acl", {}).get("nickName") or session.get("CAS_USERNAME") or current_user.nickname
role = dict(permissions=session.get("acl", {}).get("parentRoles", []) or ["admin"])
avatar = session.get("acl", {}).get("avatar") or current_user.avatar
return self.jsonify(result=dict(name=name,
role=role,
avatar=avatar))