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

View File

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

View File

@ -18,6 +18,8 @@ class Host(db.Model, ModelMixin):
def __repr__(self):
return '<Host %r>' % self.name
class Meta:
ordering = ('-id',)
class HostExtend(db.Model, ModelMixin):
__tablename__ = 'assets_hosts_extend'
@ -33,6 +35,9 @@ class HostExtend(db.Model, ModelMixin):
hosts = db.relationship(Host, backref=db.backref('host'))
class Meta:
ordering = ('-id',)
class HostExecTemplate(db.Model, ModelMixin):
__tablename__ = 'assets_hosts_exec_template'
@ -44,4 +49,7 @@ class HostExecTemplate(db.Model, ModelMixin):
tpl_content = db.Column(db.Text())
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):
return '<Environment %r>' % self.name
class Meta:
ordering = ('-id',)
class Service(db.Model, ModelMixin):
__tablename__ = 'configuration_services'
@ -24,6 +27,9 @@ class Service(db.Model, ModelMixin):
desc = db.Column(db.String(255))
group = db.Column(db.String(50))
class Meta:
ordering = ('-id',)
class ConfigValue(db.Model, ModelMixin):
__tablename__ = 'configuration_values'

View File

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

View File

@ -21,6 +21,9 @@ class Job(db.Model, ModelMixin):
def __repr__(self):
return '<schedule.Job name=%r trigger=%r>' % (self.name, self.trigger)
class Meta:
ordering = ('-id',)
class JobHistory(db.Model, ModelMixin):
__tablename__ = 'schedule_jobs_history'
@ -48,3 +51,6 @@ class JobHistory(db.Model, ModelMixin):
def __repr__(self):
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))
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))
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
TIME_ZONE = timezone('Asia/Shanghai')
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
#SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:redhat@127.0.0.1/doms'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'test.db')
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_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False
DOCKER_REGISTRY_SERVER = 'localhost:5000'
DOCKER_REGISTRY_AUTH = {'username': 'user', 'password': 'password'}
DOCKER_REGISTRY_SERVER = 'hub.qbangmang.com'
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
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 (1302, 'system_notify_add', '添加通知设置');
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 (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_sqlalchemy import SQLAlchemy
import config
from flask_simpleldap import LDAP
app = Flask(__name__)
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)
ldap = LDAP(app)

View File

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

View File

@ -1,31 +1,61 @@
<template>
<div class="login">
<el-row style="z-index: 1;height: 100%;">
<el-card class="login-box" element-loading-background="rgba(0, 0, 0, 0.8)">
<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>-->
<el-form-item prop="username">
<el-input v-model="form.username" :autofocus="true" placeholder="请输入用户">
<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="请输入密码" >
<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-card>
<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">
<!--<p class="login-box-msg">运维平台</p>-->
<el-form-item prop="username">
<el-input v-model="form.username" :autofocus="true" placeholder="请输入用户">
<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="请输入密码" >
<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-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>
</div>
</template>
<style>
@ -36,10 +66,17 @@
height: 100%;
position: fixed;
}
.login-box {
background: rgba(0, 0, 0, 0.5);
.title {
width: 100%;
font-size: 50px;
color: #ffffff;
text-align: center;
display: inline-block;
margin-bottom: 20px;
}
.box-container {
border: none;
width: 25%;
width: 30%;
position: absolute;
top: 50%;
left: 50%;
@ -50,16 +87,15 @@
color: #ffffff;
text-align: center;
}
.login-box .title {
color: #ffffff;
text-align: center;
}
</style>
<script>
export default {
data() {
return {
activeName: 'standard',
loading: false,
selectTab: 'standard',
error: '',
form: {
username: 'admin',
@ -83,6 +119,8 @@
return false
}
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 => {
localStorage.setItem('token', res.result['token']);
localStorage.setItem('is_supper', res.result['is_supper']);
@ -93,6 +131,9 @@
this.error = response.result
}).finally(() => this.loading = false)
})
},
handleClick(tab, event) {
this.selectTab = tab.name;
}
},
watch: {
@ -104,4 +145,4 @@
}
}
}
</script>
</script>

View File

@ -19,12 +19,12 @@
</el-col>
</el-row>
<el-table :data="tableData.data" stripe border v-loading="listLoading" style="width: 100%"
:default-sort="{prop: 'username', order: 'descending'}">
<el-table :data="tableData.data" stripe border v-loading="listLoading" style="width: 100%">
<el-table-column type="index" width="60"></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="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 label="状态" sortable width="90">
<template slot-scope="scope">