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,17 +120,48 @@ 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:
if form.type == 'ldap':
ldap_login = ldap.bind_user(form.username, form.password)
if ldap_login:
token = uuid.uuid4().hex
# user = User.query.filter_by(username=form.username).filter_by(type='LDAP').first()
user = User.query.filter_by(username=form.username).first() user = User.query.filter_by(username=form.username).first()
if not user:
form.nickname = form.username
form.type = 'LDAP'
form.role_id = 1
form.is_supper = False
is_supper = False
nickname = form.username
permissions = []
User(**form).save()
else:
user.access_token = token
user.token_expired = time.time() + 80 * 60 * 6000
is_supper = user.is_supper,
nickname = user.nickname,
permissions = list(user.permissions)
user.save()
return json_response({
'token': token,
'is_supper': is_supper,
'nickname': nickname,
'permissions': permissions
})
else:
return json_response(message='用户名或密码错误确认输入的是LDAP的账号密码')
else:
user = User.query.filter_by(username=form.username).filter_by(type='系统用户').first()
if user: if user:
if user.is_active: if user.is_active:
if user.verify_password(form.password): if user.verify_password(form.password):
login_limit.pop(form.username, None) login_limit.pop(form.username, None)
token = uuid.uuid4().hex token = uuid.uuid4().hex
# token = user.access_token
user.access_token = token user.access_token = token
user.token_expired = time.time() + 8 * 60 * 60 user.token_expired = time.time() + 80 * 60 * 6000
user.save() user.save()
return json_response({ return json_response({
'token': token, 'token': token,
@ -147,9 +180,7 @@ def login():
return json_response(message='用户已被禁用,请联系管理员') return json_response(message='用户已被禁用,请联系管理员')
else: else:
login_limit[form.username] += 1 login_limit[form.username] += 1
return json_response(message='用户名或密码错误连续3次错误将会被禁用') return json_response(message='用户名不存在,请确认用户名')
else:
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'
@ -45,3 +50,6 @@ class HostExecTemplate(db.Model, ModelMixin):
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

@ -24,3 +24,6 @@ class NoticeWay(db.Model, ModelMixin):
def __repr__(self): def __repr__(self):
return '<NoticeWay %r>' % self.name return '<NoticeWay %r>' % self.name
class Meta:
ordering = ('-id',)

View File

@ -12,3 +12,6 @@ class NotifyWay(db.Model, ModelMixin):
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');

View File

@ -18,3 +18,11 @@ INSERT INTO account_permissions (id, name, `desc`) VALUES (1303, 'system_notify_
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,9 +1,12 @@
<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">
<span class="title">Spug运维平台</span>
<el-card class="login-box" >
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="标准登录" name="standard">
<el-form ref="form" :model="form" :rules="rules" label-with="80px" @keyup.enter.native="handleSubmit"> <el-form ref="form" :model="form" :rules="rules" label-with="80px" @keyup.enter.native="handleSubmit">
<h1 class="title">Spug运维平台</h1>
<!--<p class="login-box-msg">运维平台</p>--> <!--<p class="login-box-msg">运维平台</p>-->
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="form.username" :autofocus="true" placeholder="请输入用户"> <el-input v-model="form.username" :autofocus="true" placeholder="请输入用户">
@ -24,8 +27,35 @@
<el-button type="primary" :loading="loading" @click="handleSubmit" style="width: 100%">登录</el-button> <el-button type="primary" :loading="loading" @click="handleSubmit" style="width: 100%">登录</el-button>
</el-form-item> </el-form-item>
</el-form> </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> </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: {

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">