Merge pull request #1 from pycook/master

怎么玩的?反向pull request
This commit is contained in:
kdyq007 2019-11-18 18:31:14 +08:00 committed by GitHub
commit 6a7bb725cc
11 changed files with 118 additions and 16 deletions

View File

@ -17,6 +17,9 @@
- username: admin - username: admin
- password: admin - password: admin
> **重要提示**: `master` 分支在开发过程中可能处于 *不稳定的状态*
请通过[releases](https://github.com/pycook/cmdb/releases)获取
Overview Overview
---- ----
![基础资源视图](https://raw.githubusercontent.com/pycook/cmdb/master/ui/public/cmdb01.jpeg) ![基础资源视图](https://raw.githubusercontent.com/pycook/cmdb/master/ui/public/cmdb01.jpeg)

View File

@ -10,9 +10,14 @@ from api.lib.exception import CommitException
class FormatMixin(object): class FormatMixin(object):
def to_dict(self): def to_dict(self):
return dict([(k.name, res = dict()
getattr(self, k.name) if not isinstance(getattr(self, k.name), datetime.datetime) else getattr( for k in getattr(self, "__table__").columns:
self, k.name).strftime('%Y-%m-%d %H:%M:%S')) for k in getattr(self, "__table__").columns]) if not isinstance(getattr(self, k.name), datetime.datetime):
res[k.name] = getattr(self, k.name)
else:
res[k.name] = getattr(self, k.name).strftime('%Y-%m-%d %H:%M:%S')
return res
@classmethod @classmethod
def get_columns(cls): def get_columns(cls):

View File

@ -1 +1,23 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from functools import wraps
from flask import request
from flask import abort
from api.lib.perm.acl.cache import AppCache
def validate_app(func):
@wraps(func)
def wrapper(*args, **kwargs):
app_id = request.values.get('app_id')
app = AppCache.get(app_id)
if app is None:
return abort(400, "App <{0}> does not exist".format(app_id))
request.values['app_id'] = app.id
return func(*args, **kwargs)
return wrapper

View File

@ -1,11 +1,37 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from api.extensions import cache from api.extensions import cache
from api.models.acl import App
from api.models.acl import Permission from api.models.acl import Permission
from api.models.acl import Role from api.models.acl import Role
from api.models.acl import User from api.models.acl import User
class AppCache(object):
PREFIX_ID = "App::id::{0}"
PREFIX_NAME = "App::name::{0}"
@classmethod
def get(cls, key):
app = cache.get(cls.PREFIX_ID.format(key)) or cache.get(cls.PREFIX_NAME.format(key))
if app is None:
app = App.get_by_id(key) or App.get_by(name=key, to_dict=False, first=True)
if app is not None:
cls.set(app)
return app
@classmethod
def set(cls, app):
cache.set(cls.PREFIX_ID.format(app.id), app)
cache.set(cls.PREFIX_NAME.format(app.name), app)
@classmethod
def clean(cls, app):
cache.delete(cls.PREFIX_ID.format(app.id))
cache.delete(cls.PREFIX_NAME.format(app.name))
class UserCache(object): class UserCache(object):
PREFIX_ID = "User::uid::{0}" PREFIX_ID = "User::uid::{0}"
PREFIX_NAME = "User::username::{0}" PREFIX_NAME = "User::username::{0}"

View File

@ -156,6 +156,3 @@ class ResourceCRUD(object):
resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id)) resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id))
resource.soft_delete() resource.soft_delete()

View File

@ -1,7 +1,12 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
import uuid
import string
import random
from flask import abort from flask import abort
from flask import g
from api.extensions import db from api.extensions import db
from api.lib.perm.acl.cache import UserCache from api.lib.perm.acl.cache import UserCache
@ -20,12 +25,21 @@ class UserCRUD(object):
return numfound, query.offset((page - 1) * page_size).limit(page_size) return numfound, query.offset((page - 1) * page_size).limit(page_size)
@staticmethod @staticmethod
def add(**kwargs): def _gen_key_secret():
key = uuid.uuid4().hex
secret = ''.join(random.sample(string.ascii_letters + string.digits + '~!@#$%^&*?', 32))
return key, secret
@classmethod
def add(cls, **kwargs):
existed = User.get_by(username=kwargs['username'], email=kwargs['email']) existed = User.get_by(username=kwargs['username'], email=kwargs['email'])
existed and abort(400, "User <{0}> is already existed".format(kwargs['username'])) 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['nickname'] = kwargs.get('nickname') or kwargs['username']
kwargs['block'] = 0 kwargs['block'] = 0
kwargs['key'], kwargs['secret'] = cls._gen_key_secret()
return User.create(**kwargs) return User.create(**kwargs)
@staticmethod @staticmethod
@ -36,6 +50,13 @@ class UserCRUD(object):
return user.update(**kwargs) return user.update(**kwargs)
@classmethod
def reset_key_secret(cls):
key, secret = cls._gen_key_secret()
g.user.update(key=key, secret=secret)
return key, secret
@classmethod @classmethod
def delete(cls, uid): def delete(cls, uid):
user = User.get_by_id(uid) or abort(404, "User <{0}> does not exist".format(uid)) user = User.get_by_id(uid) or abort(404, "User <{0}> does not exist".format(uid))

View File

@ -11,6 +11,7 @@ from flask_sqlalchemy import BaseQuery
from api.extensions import db from api.extensions import db
from api.lib.database import CRUDModel from api.lib.database import CRUDModel
from api.lib.database import Model from api.lib.database import Model
from api.lib.database import SoftDeleteMixin
class App(Model): class App(Model):
@ -34,6 +35,7 @@ class UserQuery(BaseQuery):
authenticated = user.check_password(password) authenticated = user.check_password(password)
else: else:
authenticated = False authenticated = False
return user, authenticated return user, authenticated
def authenticate_with_key(self, key, secret, args, path): def authenticate_with_key(self, key, secret, args, path):
@ -45,6 +47,7 @@ class UserQuery(BaseQuery):
authenticated = True authenticated = True
else: else:
authenticated = False authenticated = False
return user, authenticated return user, authenticated
def search(self, key): def search(self, key):
@ -55,18 +58,21 @@ class UserQuery(BaseQuery):
def get_by_username(self, username): def get_by_username(self, username):
user = self.filter(User.username == username).first() user = self.filter(User.username == username).first()
return user return user
def get_by_nickname(self, nickname): def get_by_nickname(self, nickname):
user = self.filter(User.nickname == nickname).first() user = self.filter(User.nickname == nickname).first()
return user return user
def get(self, uid): def get(self, uid):
user = self.filter(User.uid == uid).first() user = self.filter(User.uid == uid).first()
return copy.deepcopy(user) return copy.deepcopy(user)
class User(CRUDModel): class User(CRUDModel, SoftDeleteMixin):
__tablename__ = 'users' __tablename__ = 'users'
# __bind_key__ = "user" # __bind_key__ = "user"
query_class = UserQuery query_class = UserQuery
@ -107,9 +113,7 @@ class User(CRUDModel):
def _set_password(self, password): def _set_password(self, password):
self._password = hashlib.md5(password.encode('utf-8')).hexdigest() self._password = hashlib.md5(password.encode('utf-8')).hexdigest()
password = db.synonym("_password", password = db.synonym("_password", descriptor=property(_get_password, _set_password))
descriptor=property(_get_password,
_set_password))
def check_password(self, password): def check_password(self, password):
if self.password is None: if self.password is None:

View File

@ -1,3 +1 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
__author__ = 'pycook'

View File

@ -3,6 +3,7 @@
from flask import request from flask import request
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl import validate_app
from api.lib.perm.acl.resource import ResourceCRUD from api.lib.perm.acl.resource import ResourceCRUD
from api.lib.perm.acl.resource import ResourceGroupCRUD from api.lib.perm.acl.resource import ResourceGroupCRUD
from api.lib.utils import get_page from api.lib.utils import get_page
@ -15,6 +16,7 @@ class ResourceView(APIView):
url_prefix = ("/resources", "/resources/<int:resource_id>") url_prefix = ("/resources", "/resources/<int:resource_id>")
@args_required('app_id') @args_required('app_id')
@validate_app
def get(self): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@ -31,6 +33,7 @@ class ResourceView(APIView):
@args_required('name') @args_required('name')
@args_required('type_id') @args_required('type_id')
@args_required('app_id') @args_required('app_id')
@validate_app
def post(self): def post(self):
name = request.values.get('name') name = request.values.get('name')
type_id = request.values.get('type_id') type_id = request.values.get('type_id')
@ -57,6 +60,7 @@ class ResourceView(APIView):
class ResourceGroupView(APIView): class ResourceGroupView(APIView):
url_prefix = ("/resource_groups", "/resource_groups/<int:group_id>") url_prefix = ("/resource_groups", "/resource_groups/<int:group_id>")
@validate_app
def get(self): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@ -73,6 +77,7 @@ class ResourceGroupView(APIView):
@args_required('name') @args_required('name')
@args_required('type_id') @args_required('type_id')
@args_required('app_id') @args_required('app_id')
@validate_app
def post(self): def post(self):
name = request.values.get('name') name = request.values.get('name')
type_id = request.values.get('type_id') type_id = request.values.get('type_id')

View File

@ -3,6 +3,7 @@
from flask import request from flask import request
from api.lib.decorator import args_required from api.lib.decorator import args_required
from api.lib.perm.acl import validate_app
from api.lib.perm.acl.role import RoleCRUD from api.lib.perm.acl.role import RoleCRUD
from api.lib.perm.acl.role import RoleRelationCRUD from api.lib.perm.acl.role import RoleRelationCRUD
from api.lib.utils import get_page from api.lib.utils import get_page
@ -14,6 +15,7 @@ class RoleView(APIView):
url_prefix = ("/roles", "/roles/<int:rid>") url_prefix = ("/roles", "/roles/<int:rid>")
@args_required('app_id') @args_required('app_id')
@validate_app
def get(self): def get(self):
page = get_page(request.values.get("page", 1)) page = get_page(request.values.get("page", 1))
page_size = get_page_size(request.values.get("page_size")) page_size = get_page_size(request.values.get("page_size"))
@ -32,6 +34,7 @@ class RoleView(APIView):
@args_required('name') @args_required('name')
@args_required('app_id') @args_required('app_id')
@validate_app
def post(self): def post(self):
name = request.values.get('name') name = request.values.get('name')
app_id = request.values.get('app_id') app_id = request.values.get('app_id')

View File

@ -6,8 +6,8 @@ from flask import session
from flask_login import current_user from flask_login import current_user
from api.lib.decorator import args_required 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.perm.acl.role import RoleRelationCRUD
from api.lib.perm.acl.user import UserCRUD
from api.lib.utils import get_page from api.lib.utils import get_page
from api.lib.utils import get_page_size from api.lib.utils import get_page_size
from api.resource import APIView from api.resource import APIView
@ -36,11 +36,17 @@ class UserView(APIView):
id2parents = RoleRelationCRUD.get_parents(uids=[i.uid for i in users]) id2parents = RoleRelationCRUD.get_parents(uids=[i.uid for i in users])
users = [i.to_dict() for i in users]
for u in users:
u.pop('password', None)
u.pop('key', None)
u.pop('secret', None)
return self.jsonify(numfound=numfound, return self.jsonify(numfound=numfound,
page=page, page=page,
page_size=page_size, page_size=page_size,
id2parents=id2parents, id2parents=id2parents,
users=[i.to_dict() for i in users]) users=users)
@args_required('username') @args_required('username')
@args_required('email') @args_required('email')
@ -58,3 +64,15 @@ class UserView(APIView):
UserCRUD.delete(uid) UserCRUD.delete(uid)
return self.jsonify(uid=uid) return self.jsonify(uid=uid)
class UserResetKeySecretView(APIView):
url_prefix = "/users/reset_key_secret"
def post(self):
key, secret = UserCRUD.reset_key_secret()
return self.jsonify(key=key, secret=secret)
def put(self):
return self.post()