A - 增加支持LDAP用户登录。

1.x
张玉坡 2019-10-27 18:24:13 +08:00
parent fa0786634e
commit 3fb8f51455
15 changed files with 226 additions and 72 deletions

View File

@ -16,6 +16,7 @@ class User(db.Model, ModelMixin):
email = db.Column(db.String(120)) email = db.Column(db.String(120))
mobile = db.Column(db.String(30)) mobile = db.Column(db.String(30))
is_supper = db.Column(db.Boolean, default=False) is_supper = db.Column(db.Boolean, default=False)
type = db.Column(db.String(20), default='系统用户')
is_active = db.Column(db.Boolean, default=True) is_active = db.Column(db.Boolean, default=True)
access_token = db.Column(db.String(32)) access_token = db.Column(db.String(32))
token_expired = db.Column(db.Integer) token_expired = db.Column(db.Integer)
@ -49,6 +50,9 @@ class User(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<User %r>' % self.username return '<User %r>' % self.username
class Meta:
ordering = ('-id',)
class Role(db.Model, ModelMixin): class Role(db.Model, ModelMixin):
__tablename__ = 'account_roles' __tablename__ = 'account_roles'
@ -68,6 +72,9 @@ class Role(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<Role %r>' % self.name return '<Role %r>' % self.name
class Meta:
ordering = ('-id',)
class Permission(db.Model, ModelMixin): class Permission(db.Model, ModelMixin):
__tablename__ = 'account_permissions' __tablename__ = 'account_permissions'
@ -79,6 +86,9 @@ class Permission(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<Permission %r>' % self.name return '<Permission %r>' % self.name
class Meta:
ordering = ('-id',)
class RolePermissionRel(db.Model, ModelMixin): class RolePermissionRel(db.Model, ModelMixin):
__tablename__ = 'account_role_permission_rel' __tablename__ = 'account_role_permission_rel'

View File

@ -6,6 +6,8 @@ from collections import defaultdict
from datetime import datetime from datetime import datetime
import uuid import uuid
import time import time
from public import ldap
blueprint = Blueprint('account_page', __name__) blueprint = Blueprint('account_page', __name__)
login_limit = defaultdict(int) login_limit = defaultdict(int)
@ -118,38 +120,67 @@ def get_self():
@blueprint.route('/login/', methods=['POST']) @blueprint.route('/login/', methods=['POST'])
def login(): def login():
form, error = JsonParser('username', 'password').parse() form, error = JsonParser('username', 'password', 'type').parse()
if error is None: if error is None:
user = User.query.filter_by(username=form.username).first() if form.type == 'ldap':
if user: ldap_login = ldap.bind_user(form.username, form.password)
if user.is_active: if ldap_login:
if user.verify_password(form.password): token = uuid.uuid4().hex
login_limit.pop(form.username, None) # user = User.query.filter_by(username=form.username).filter_by(type='LDAP').first()
token = uuid.uuid4().hex user = User.query.filter_by(username=form.username).first()
# token = user.access_token if not user:
user.access_token = token form.nickname = form.username
user.token_expired = time.time() + 8 * 60 * 60 form.type = 'LDAP'
user.save() form.role_id = 1
return json_response({ form.is_supper = False
'token': token, is_supper = False
'is_supper': user.is_supper, nickname = form.username
'nickname': user.nickname, permissions = []
'permissions': list(user.permissions) User(**form).save()
})
else: else:
login_limit[form.username] += 1 user.access_token = token
if login_limit[form.username] >= 3: user.token_expired = time.time() + 80 * 60 * 6000
user.update(is_active=False) is_supper = user.is_supper,
return json_response(message='用户名或密码错误连续3次错误将会被禁用') nickname = user.nickname,
permissions = list(user.permissions)
user.save()
return json_response({
'token': token,
'is_supper': is_supper,
'nickname': nickname,
'permissions': permissions
})
else: else:
return json_response(message='用户已被禁用,请联系管理员') return json_response(message='用户名或密码错误确认输入的是LDAP的账号密码')
elif login_limit[form.username] >= 3:
return json_response(message='用户已被禁用,请联系管理员')
else: else:
login_limit[form.username] += 1 user = User.query.filter_by(username=form.username).filter_by(type='系统用户').first()
return json_response(message='用户名或密码错误连续3次错误将会被禁用') if user:
else: if user.is_active:
return json_response(message='请输入用户名和密码') if user.verify_password(form.password):
login_limit.pop(form.username, None)
token = uuid.uuid4().hex
user.access_token = token
user.token_expired = time.time() + 80 * 60 * 6000
user.save()
return json_response({
'token': token,
'is_supper': user.is_supper,
'nickname': user.nickname,
'permissions': list(user.permissions)
})
else:
login_limit[form.username] += 1
if login_limit[form.username] >= 3:
user.update(is_active=False)
return json_response(message='用户名或密码错误连续3次错误将会被禁用')
else:
return json_response(message='用户已被禁用,请联系管理员')
elif login_limit[form.username] >= 3:
return json_response(message='用户已被禁用,请联系管理员')
else:
login_limit[form.username] += 1
return json_response(message='用户名不存在,请确认用户名')
@blueprint.route('/logout/') @blueprint.route('/logout/')

View File

@ -18,6 +18,8 @@ class Host(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<Host %r>' % self.name return '<Host %r>' % self.name
class Meta:
ordering = ('-id',)
class HostExtend(db.Model, ModelMixin): class HostExtend(db.Model, ModelMixin):
__tablename__ = 'assets_hosts_extend' __tablename__ = 'assets_hosts_extend'
@ -33,6 +35,9 @@ class HostExtend(db.Model, ModelMixin):
hosts = db.relationship(Host, backref=db.backref('host')) hosts = db.relationship(Host, backref=db.backref('host'))
class Meta:
ordering = ('-id',)
class HostExecTemplate(db.Model, ModelMixin): class HostExecTemplate(db.Model, ModelMixin):
__tablename__ = 'assets_hosts_exec_template' __tablename__ = 'assets_hosts_exec_template'
@ -44,4 +49,7 @@ class HostExecTemplate(db.Model, ModelMixin):
tpl_content = db.Column(db.Text()) tpl_content = db.Column(db.Text())
def __repr__(self): def __repr__(self):
return '<HostExecTemplate %r>' % self.tpl_name return '<HostExecTemplate %r>' % self.tpl_name
class Meta:
ordering = ('-id',)

View File

@ -14,6 +14,9 @@ class Environment(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<Environment %r>' % self.name return '<Environment %r>' % self.name
class Meta:
ordering = ('-id',)
class Service(db.Model, ModelMixin): class Service(db.Model, ModelMixin):
__tablename__ = 'configuration_services' __tablename__ = 'configuration_services'
@ -24,6 +27,9 @@ class Service(db.Model, ModelMixin):
desc = db.Column(db.String(255)) desc = db.Column(db.String(255))
group = db.Column(db.String(50)) group = db.Column(db.String(50))
class Meta:
ordering = ('-id',)
class ConfigValue(db.Model, ModelMixin): class ConfigValue(db.Model, ModelMixin):
__tablename__ = 'configuration_values' __tablename__ = 'configuration_values'

View File

@ -20,6 +20,9 @@ class Image(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<Image %r>' % self.name return '<Image %r>' % self.name
class Meta:
ordering = ('-id',)
class ImageConfig(db.Model, ModelMixin): class ImageConfig(db.Model, ModelMixin):
__tablename__ = 'deploy_image_configs' __tablename__ = 'deploy_image_configs'
@ -63,6 +66,9 @@ class History(db.Model, ModelMixin):
deploy_success = db.Column(db.Boolean) deploy_success = db.Column(db.Boolean)
created = db.Column(db.String(20)) created = db.Column(db.String(20))
class Meta:
ordering = ('-id',)
class App(db.Model, ModelMixin): class App(db.Model, ModelMixin):
__tablename__ = 'deploy_apps' __tablename__ = 'deploy_apps'
@ -84,6 +90,9 @@ class App(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<App %r>' % self.name return '<App %r>' % self.name
class Meta:
ordering = ('-id',)
class AppHostRel(db.Model, ModelMixin): class AppHostRel(db.Model, ModelMixin):
__tablename__ = 'deploy_app_host_rel' __tablename__ = 'deploy_app_host_rel'
@ -123,6 +132,9 @@ class DeployMenu(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<DeployMenu %r>' % self.name return '<DeployMenu %r>' % self.name
class Meta:
ordering = ('-id',)
class AppMenuRel(db.Model, ModelMixin): class AppMenuRel(db.Model, ModelMixin):
__tablename__ = 'deploy_app_menu_rel' __tablename__ = 'deploy_app_menu_rel'

View File

@ -21,6 +21,9 @@ class Job(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<schedule.Job name=%r trigger=%r>' % (self.name, self.trigger) return '<schedule.Job name=%r trigger=%r>' % (self.name, self.trigger)
class Meta:
ordering = ('-id',)
class JobHistory(db.Model, ModelMixin): class JobHistory(db.Model, ModelMixin):
__tablename__ = 'schedule_jobs_history' __tablename__ = 'schedule_jobs_history'
@ -48,3 +51,6 @@ class JobHistory(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<schedule.JonHistory id=%r job_id=%r>' % (self.id, self.job_id) return '<schedule.JonHistory id=%r job_id=%r>' % (self.id, self.job_id)
class Meta:
ordering = ('-id',)

View File

@ -23,4 +23,7 @@ class NoticeWay(db.Model, ModelMixin):
desc = db.Column(db.String(255)) desc = db.Column(db.String(255))
def __repr__(self): def __repr__(self):
return '<NoticeWay %r>' % self.name return '<NoticeWay %r>' % self.name
class Meta:
ordering = ('-id',)

View File

@ -11,4 +11,7 @@ class NotifyWay(db.Model, ModelMixin):
desc = db.Column(db.String(255)) desc = db.Column(db.String(255))
def __repr__(self): def __repr__(self):
return '<NotifyWay %r>' % self.name return '<NotifyWay %r>' % self.name
class Meta:
ordering = ('-id',)

View File

@ -4,10 +4,20 @@ import os
DEBUG = True DEBUG = True
TIME_ZONE = timezone('Asia/Shanghai') TIME_ZONE = timezone('Asia/Shanghai')
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(os.path.abspath(__file__))
#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:redhat@127.0.0.1/doms' SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://spug:spugpwd@123.12.13.18:3306/spug'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'test.db') # SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'test.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False SQLALCHEMY_ECHO = False
DOCKER_REGISTRY_SERVER = 'localhost:5000' DOCKER_REGISTRY_SERVER = 'hub.qbangmang.com'
DOCKER_REGISTRY_AUTH = {'username': 'user', 'password': 'password'} DOCKER_REGISTRY_AUTH = {'username': 'hubuser', 'password': 'hubpassword'}
LDAP_CONFIG = {
'host': '',
'port': 389,
'is_openldap': True,
'base_dn': 'dc=spug,dc=com',
'admin_dn': 'cn=admin,dc=spug,dc=com',
'admin_password': 'spugpwd',
'user_filter': 'cn',
}

View File

@ -1,3 +1,6 @@
-- system role
INSERT INTO account_roles (id, name, `desc`) VALUES (1, '系统默认角色', '系统默认角色');
-- Dashboard -- Dashboard
INSERT INTO account_permissions (id, name, `desc`) VALUES (100, 'home_view', 'Dashboard'); INSERT INTO account_permissions (id, name, `desc`) VALUES (100, 'home_view', 'Dashboard');
@ -118,4 +121,4 @@ INSERT INTO account_permissions (id, name, `desc`) VALUES (1206, 'publish_field_
INSERT INTO account_permissions (id, name, `desc`) VALUES (1301, 'system_notify_view', '系统通知列表'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1301, 'system_notify_view', '系统通知列表');
INSERT INTO account_permissions (id, name, `desc`) VALUES (1302, 'system_notify_add', '添加通知设置'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1302, 'system_notify_add', '添加通知设置');
INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置');
INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置');

View File

@ -17,4 +17,12 @@ INSERT INTO account_permissions (id, name, `desc`) VALUES (1302, 'system_notify_
INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_edit', '编辑通知设置');
INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置'); INSERT INTO account_permissions (id, name, `desc`) VALUES (1304, 'system_notify_del', '删除通知设置');
-- end update 1.0.0 -- -- end update 1.0.0 --
-- start update 1.1.0 --
INSERT INTO account_roles (id, name, `desc`) VALUES (1, '系统默认角色', '系统默认角色');
ALTER TABLE `spug`.`account_users` ADD COLUMN `type` varchar(20) NULL DEFAULT '系统用户' AFTER `is_supper`;
-- end update 1.0.1 --

View File

@ -1,8 +1,20 @@
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
import config import config
from flask_simpleldap import LDAP
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(config) app.config.from_object(config)
app.config['LDAP_BASE_DN'] = app.config['LDAP_CONFIG'].get('base_dn')
app.config['LDAP_USERNAME'] = app.config['LDAP_CONFIG'].get('admin_dn')
app.config['LDAP_PASSWORD'] = app.config['LDAP_CONFIG'].get('admin_password')
app.config['LDAP_OPENLDAP'] = app.config['LDAP_CONFIG'].get('is_openldap')
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=inetOrgPerson)({0}=%s))'\
.format(app.config['LDAP_CONFIG'].get('user_filter'))
app.config['LDAP_HOST'] = app.config['LDAP_CONFIG'].get('host')
app.config['LDAP_PORT'] = app.config['LDAP_CONFIG'].get('port')
db = SQLAlchemy(app) db = SQLAlchemy(app)
ldap = LDAP(app)

View File

@ -41,3 +41,4 @@ tzlocal==1.5.1
urllib3==1.24.3 urllib3==1.24.3
websocket-client==0.56.0 websocket-client==0.56.0
Werkzeug==0.16.0 Werkzeug==0.16.0
python-ldap==3.2.0

View File

@ -1,31 +1,61 @@
<template> <template>
<div class="login"> <div class="login">
<el-row style="z-index: 1;height: 100%;"> <el-row style="z-index: 1;height: 100%;">
<el-card class="login-box" element-loading-background="rgba(0, 0, 0, 0.8)"> <div class="box-container">
<el-form ref="form" :model="form" :rules="rules" label-with="80px" @keyup.enter.native="handleSubmit"> <span class="title">Spug运维平台</span>
<h1 class="title">Spug运维平台</h1> <el-card class="login-box" >
<!--<p class="login-box-msg">运维平台</p>--> <el-tabs v-model="activeName" @tab-click="handleClick">
<el-form-item prop="username"> <el-tab-pane label="标准登录" name="standard">
<el-input v-model="form.username" :autofocus="true" placeholder="请输入用户"> <el-form ref="form" :model="form" :rules="rules" label-with="80px" @keyup.enter.native="handleSubmit">
<template slot="prepend"> <!--<p class="login-box-msg">运维平台</p>-->
<i class="fa fa-user"></i> <el-form-item prop="username">
</template> <el-input v-model="form.username" :autofocus="true" placeholder="请输入用户">
</el-input> <template slot="prepend">
</el-form-item> <i class="fa fa-user"></i>
<el-form-item prop="password"> </template>
<el-input type="password" v-model="form.password" placeholder="请输入密码" > </el-input>
<template slot="prepend"> </el-form-item>
<i class="fa fa-lock"></i> <el-form-item prop="password">
</template> <el-input type="password" v-model="form.password" placeholder="请输入密码" >
</el-input> <template slot="prepend">
</el-form-item> <i class="fa fa-lock"></i>
<el-form-item> </template>
<el-alert v-if="error" :title="error" type="error" style="margin-top: -10px; margin-bottom: 10px" show-icon></el-alert> </el-input>
<el-button type="primary" :loading="loading" @click="handleSubmit" style="width: 100%">登录</el-button> </el-form-item>
</el-form-item> <el-form-item>
</el-form> <el-alert v-if="error" :title="error" type="error" style="margin-top: -10px; margin-bottom: 10px" show-icon></el-alert>
</el-card> <el-button type="primary" :loading="loading" @click="handleSubmit" style="width: 100%">登录</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="LDAP登录" name="ldap">
<el-form ref="form" :model="form" :rules="rules" label-with="80px" @keyup.enter.native="handleSubmit">
<!--<p class="login-box-msg">运维平台</p>-->
<el-form-item prop="username">
<el-input v-model="form.username" :autofocus="true" placeholder="请输入LDAP用户">
<template slot="prepend">
<i class="fa fa-user"></i>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" v-model="form.password" placeholder="请输入LDAP密码" >
<template slot="prepend">
<i class="fa fa-lock"></i>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-alert v-if="error" :title="error" type="error" style="margin-top: -10px; margin-bottom: 10px" show-icon></el-alert>
<el-button type="primary" :loading="loading" @click="handleSubmit" style="width: 100%">登录</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</el-row> </el-row>
</div> </div>
</template> </template>
<style> <style>
@ -36,10 +66,17 @@
height: 100%; height: 100%;
position: fixed; position: fixed;
} }
.login-box { .title {
background: rgba(0, 0, 0, 0.5); width: 100%;
font-size: 50px;
color: #ffffff;
text-align: center;
display: inline-block;
margin-bottom: 20px;
}
.box-container {
border: none; border: none;
width: 25%; width: 30%;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@ -50,16 +87,15 @@
color: #ffffff; color: #ffffff;
text-align: center; text-align: center;
} }
.login-box .title {
color: #ffffff;
text-align: center;
}
</style> </style>
<script> <script>
export default { export default {
data() { data() {
return { return {
activeName: 'standard',
loading: false, loading: false,
selectTab: 'standard',
error: '', error: '',
form: { form: {
username: 'admin', username: 'admin',
@ -83,6 +119,8 @@
return false return false
} }
this.loading = true; this.loading = true;
this.form.type = this.selectTab;
console.log('this.form', this.form);
this.$http.post('/api/account/users/login/', this.form).then(res => { this.$http.post('/api/account/users/login/', this.form).then(res => {
localStorage.setItem('token', res.result['token']); localStorage.setItem('token', res.result['token']);
localStorage.setItem('is_supper', res.result['is_supper']); localStorage.setItem('is_supper', res.result['is_supper']);
@ -93,6 +131,9 @@
this.error = response.result this.error = response.result
}).finally(() => this.loading = false) }).finally(() => this.loading = false)
}) })
},
handleClick(tab, event) {
this.selectTab = tab.name;
} }
}, },
watch: { watch: {
@ -104,4 +145,4 @@
} }
} }
} }
</script> </script>

View File

@ -19,12 +19,12 @@
</el-col> </el-col>
</el-row> </el-row>
<el-table :data="tableData.data" stripe border v-loading="listLoading" style="width: 100%" <el-table :data="tableData.data" stripe border v-loading="listLoading" style="width: 100%">
:default-sort="{prop: 'username', order: 'descending'}">
<el-table-column type="index" width="60"></el-table-column> <el-table-column type="index" width="60"></el-table-column>
<el-table-column prop="username" label="登录名" sortable></el-table-column> <el-table-column prop="username" label="登录名" sortable></el-table-column>
<el-table-column prop="nickname" label="姓名" sortable></el-table-column> <el-table-column prop="nickname" label="姓名" sortable></el-table-column>
<el-table-column prop="role_name" label="角色"></el-table-column> <el-table-column prop="role_name" label="角色"></el-table-column>
<el-table-column prop="type" label="类型"></el-table-column>
<el-table-column prop="last_login" label="最近登录"></el-table-column> <el-table-column prop="last_login" label="最近登录"></el-table-column>
<el-table-column label="状态" sortable width="90"> <el-table-column label="状态" sortable width="90">
<template slot-scope="scope"> <template slot-scope="scope">