diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 228971d07..000000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index b749de2d9..983fedd49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ *.py[cod] .idea test.py - +.DS_Store +db.sqlite3 # C extensions *.so @@ -36,8 +37,9 @@ nosetests.xml .mr.developer.cfg .project .pydevproject -node_modules -logs -keys +*.log +logs/* +keys/* jumpserver.conf nohup.out +tmp/* diff --git a/README.md b/README.md index 2a40fba79..e733655c2 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,68 @@ #欢迎使用Jumpserver -**Jumpserver**是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能 +**Jumpserver** 是一款由python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能 +###截图: +首页 + +![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/index.jpeg) -> **统计管理** 统一管理用户 -> -> **授权** 授权用户登录特定主机 -> -> **审计** 审计用户操作 -> -> **web管理** 漂亮的web管理界面 +WebTerminal: -## 主要模块 -#### 用户管理 #### - 负责用户管理,添加用户,编辑用户,建立部门,建立用户组等 -#### 资产管理 #### - 负责资产管理,添加资产,编辑资产,建立IDC,建立用户组等 -#### 授权管理 #### - 负责授权用户登录某些特定主机,授权sudo,查看授权申请 -#### 日志审计 #### - 负责用户操作的审计,监控用户操作,统计用户操作记录,中断用户操作 -#### 上传下载 #### - 负责用户文件上传下载 +![webterminal](https://github.com/ibuler/static/raw/master/jumpserver3/webTerminal.gif) -[官网](http://www.jumpserver.org) +Web批量执行命令 + +![WebExecCommand](https://github.com/ibuler/static/raw/master/jumpserver3/webExec.gif) + +录像回放 + +![录像](https://github.com/ibuler/static/raw/master/jumpserver3/record.gif) + +跳转和批量命令 + +![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/connect.gif) + +命令统计 + +![跳转](https://github.com/ibuler/static/raw/master/jumpserver3/command.png) + +### 文档 + +* [访问wiki](https://github.com/ibuler/jumpserver/wiki) +* [快速安装](https://github.com/ibuler/jumpserver/wiki/快速安装) +* [名词解释](https://github.com/ibuler/jumpserver/wiki/名称解释) +* [快速开始](https://github.com/ibuler/jumpserver/wiki/快速开始) + +### 特点 + +* 完全真开源,GPL授权 +* Python编写,容易再次开发 +* 实现了跳板机基本功能,认证、授权、审计 +* 集成了Ansible,批量命令等 +* 支持WebTerminal +* Bootstrap编写,界面美观 +* 自动收集硬件信息 +* 录像回放 +* 命令搜索 +* 实时监控 +* 批量上传下载 + +### 其它 + +[Jumpserver官网](http://www.jumpserver.org) [demo站点](http://demo.jumpserver.org) -[更新log](http://laoguang.blog.51cto.com/6013350/1635853) +### 团队 + +* **广宏伟** ibuler +* **王墉** halcyon +* **陈尚委** 假想控 +* **喻茂峻** 紫川秀 +* **刘正** evanescunt +* **柯连春** 遍地节操 + -[部署文档](http://laoguang.blog.51cto.com/6013350/1636273) diff --git a/connect.py b/connect.py old mode 100644 new mode 100755 index dc74a6701..89dc43b16 --- a/connect.py +++ b/connect.py @@ -5,80 +5,241 @@ import sys reload(sys) sys.setdefaultencoding('utf8') -import socket import os import re -import select import time -import paramiko -import struct -import fcntl -import signal +import datetime import textwrap import getpass -import fnmatch import readline -import datetime -from multiprocessing import Pool +import django +import paramiko +import errno +import struct, fcntl, signal, socket, select +from io import open as copen +import uuid os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -from juser.models import User -from jlog.models import Log -from jumpserver.api import CONF, BASE_DIR, ServerError, user_perm_group_api, user_perm_group_hosts_api, get_user_host -from jumpserver.api import AssetAlias, get_connect_item +if django.get_version() != '1.6': + setup = django.setup() +from django.contrib.sessions.models import Session +from jumpserver.api import ServerError, User, Asset, PermRole, AssetGroup, get_object, mkdir, get_asset_info +from jumpserver.api import logger, Log, TtyLog, get_role_key, CRYPTOR, bash, get_tmp_dir +from jperm.perm_api import gen_resource, get_group_asset_perm, get_group_user_perm, user_have_perm, PermRole +from jumpserver.settings import LOG_DIR +from jperm.ansible_api import MyRunner +# from jlog.log_api import escapeString +from jlog.models import ExecLog, FileLog +login_user = get_object(User, username=getpass.getuser()) +remote_ip = os.popen("who -m | awk '{ print $5 }'").read().strip('()\n') try: import termios import tty except ImportError: - print '\033[1;31mOnly UnixLike supported.\033[0m' + print '\033[1;31m仅支持类Unix系统 Only unix like supported.\033[0m' time.sleep(3) sys.exit() -CONF.read(os.path.join(BASE_DIR, 'jumpserver.conf')) -LOG_DIR = os.path.join(BASE_DIR, 'logs') -SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys') -SERVER_KEY_DIR = os.path.join(SSH_KEY_DIR, 'server') -LOGIN_NAME = getpass.getuser() - -def color_print(msg, color='blue'): - """Print colorful string.""" +def color_print(msg, color='red', exits=False): + """ + Print colorful string. + 颜色打印字符或者退出 + """ color_msg = {'blue': '\033[1;36m%s\033[0m', 'green': '\033[1;32m%s\033[0m', - 'red': '\033[1;31m%s\033[0m'} - - print color_msg.get(color, 'blue') % msg + 'yellow': '\033[1;33m%s\033[0m', + 'red': '\033[1;31m%s\033[0m', + 'title': '\033[30;42m%s\033[0m', + 'info': '\033[32m%s\033[0m'} + msg = color_msg.get(color, 'red') % msg + print msg + if exits: + time.sleep(2) + sys.exit() + return msg -def color_print_exit(msg, color='red'): - """Print colorful string and exit.""" - color_print(msg, color=color) - time.sleep(2) - sys.exit() +def write_log(f, msg): + msg = re.sub(r'[\r\n]', '\r\n', msg) + f.write(msg) + f.flush() -def get_win_size(): - """This function use to get the size of the windows!""" - if 'TIOCGWINSZ' in dir(termios): - TIOCGWINSZ = termios.TIOCGWINSZ - else: - TIOCGWINSZ = 1074295912L # Assume - s = struct.pack('HHHH', 0, 0, 0, 0) - x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s) - return struct.unpack('HHHH', x)[0:2] +class Tty(object): + """ + A virtual tty class + 一个虚拟终端类,实现连接ssh和记录日志,基类 + """ + def __init__(self, user, asset, role, login_type='ssh'): + self.username = user.username + self.asset_name = asset.hostname + self.ip = None + self.port = 22 + self.ssh = None + self.channel = None + self.asset = asset + self.user = user + self.role = role + self.remote_ip = '' + self.login_type = login_type + self.vim_flag = False + self.ps1_pattern = re.compile('\[.*@.*\][\$#]') + self.vim_data = '' + @staticmethod + def is_output(strings): + newline_char = ['\n', '\r', '\r\n'] + for char in newline_char: + if char in strings: + return True + return False -def set_win_size(sig, data): - """This function use to set the window size of the terminal!""" - try: - win_size = get_win_size() - channel.resize_pty(height=win_size[0], width=win_size[1]) - except: - pass + @staticmethod + def remove_obstruct_char(cmd_str): + '''删除一些干扰的特殊符号''' + control_char = re.compile(r'\x07 | \x1b\[1P | \r ', re.X) + cmd_str = control_char.sub('',cmd_str.strip()) + patch_char = re.compile('\x08\x1b\[C') #删除方向左右一起的按键 + while patch_char.search(cmd_str): + cmd_str = patch_char.sub('', cmd_str.rstrip()) + return cmd_str + @staticmethod + def deal_backspace(match_str, result_command, pattern_str, backspace_num): + ''' + 处理删除确认键 + ''' + if backspace_num > 0: + if backspace_num > len(result_command): + result_command += pattern_str + result_command = result_command[0:-backspace_num] + else: + result_command = result_command[0:-backspace_num] + result_command += pattern_str + del_len = len(match_str)-3 + if del_len > 0: + result_command = result_command[0:-del_len] + return result_command, len(match_str) + + @staticmethod + def deal_replace_char(match_str,result_command,backspace_num): + ''' + 处理替换命令 + ''' + str_lists = re.findall(r'(?<=\x1b\[1@)\w',match_str) + tmp_str =''.join(str_lists) + result_command_list = list(result_command) + if len(tmp_str) > 1: + result_command_list[-backspace_num:-(backspace_num-len(tmp_str))] = tmp_str + elif len(tmp_str) > 0: + if result_command_list[-backspace_num] == ' ': + result_command_list.insert(-backspace_num, tmp_str) + else: + result_command_list[-backspace_num] = tmp_str + result_command = ''.join(result_command_list) + return result_command, len(match_str) + + def remove_control_char(self, result_command): + """ + 处理日志特殊字符 + """ + control_char = re.compile(r""" + \x1b[ #%()*+\-.\/]. | + \r | #匹配 回车符(CR) + (?:\x1b\[|\x9b) [ -?]* [@-~] | #匹配 控制顺序描述符(CSI)... Cmd + (?:\x1b\]|\x9d) .*? (?:\x1b\\|[\a\x9c]) | \x07 | #匹配 操作系统指令(OSC)...终止符或振铃符(ST|BEL) + (?:\x1b[P^_]|[\x90\x9e\x9f]) .*? (?:\x1b\\|\x9c) | #匹配 设备控制串或私讯或应用程序命令(DCS|PM|APC)...终止符(ST) + \x1b. #匹配 转义过后的字符 + [\x80-\x9f] | (?:\x1b\]0.*) | \[.*@.*\][\$#] | (.*mysql>.*) #匹配 所有控制字符 + """, re.X) + result_command = control_char.sub('', result_command.strip()) + + if not self.vim_flag: + if result_command.startswith('vi') or result_command.startswith('fg'): + self.vim_flag = True + return result_command.decode('utf8',"ignore") + else: + return '' + def deal_command(self, str_r): + """ + 处理命令中特殊字符 + """ + str_r = self.remove_obstruct_char(str_r) + + result_command = '' # 最后的结果 + backspace_num = 0 # 光标移动的个数 + reach_backspace_flag = False # 没有检测到光标键则为true + pattern_str = '' + while str_r: + tmp = re.match(r'\s*\w+\s*', str_r) + if tmp: + str_r = str_r[len(str(tmp.group(0))):] + if reach_backspace_flag: + pattern_str += str(tmp.group(0)) + continue + else: + result_command += str(tmp.group(0)) + continue + + tmp = re.match(r'\x1b\[K[\x08]*', str_r) + if tmp: + result_command, del_len = self.deal_backspace(str(tmp.group(0)), result_command, pattern_str, backspace_num) + reach_backspace_flag = False + backspace_num = 0 + pattern_str = '' + str_r = str_r[del_len:] + continue + + tmp = re.match(r'\x08+', str_r) + if tmp: + str_r = str_r[len(str(tmp.group(0))):] + if len(str_r) != 0: + if reach_backspace_flag: + result_command = result_command[0:-backspace_num] + pattern_str + pattern_str = '' + else: + reach_backspace_flag = True + backspace_num = len(str(tmp.group(0))) + continue + else: + break + + tmp = re.match(r'(\x1b\[1@\w)+', str_r) #处理替换的命令 + if tmp: + result_command,del_len = self.deal_replace_char(str(tmp.group(0)), result_command, backspace_num) + str_r = str_r[del_len:] + backspace_num = 0 + continue + + if reach_backspace_flag: + pattern_str += str_r[0] + else: + result_command += str_r[0] + str_r = str_r[1:] + + if backspace_num > 0: + result_command = result_command[0:-backspace_num] + pattern_str + + result_command = self.remove_control_char(result_command) + return result_command + + def get_log(self): + """ + Logging user command and output. + 记录用户的日志 + """ + tty_log_dir = os.path.join(LOG_DIR, 'tty') + date_today = datetime.datetime.now() + date_start = date_today.strftime('%Y%m%d') + time_start = date_today.strftime('%H%M%S') + today_connect_log_dir = os.path.join(tty_log_dir, date_start) + log_file_path = os.path.join(today_connect_log_dir, '%s_%s_%s' % (self.username, self.asset_name, time_start)) + +<<<<<<< HEAD def log_record(username, host): """Logging user command and output.""" connect_log_dir = os.path.join(LOG_DIR, 'connect') @@ -94,305 +255,570 @@ def log_record(username, host): ip_list = os.popen("who | grep %s | awk '{ print $5 }'" % pts).read().strip('()\n') if not os.path.isdir(today_connect_log_dir): +======= +>>>>>>> dev try: - os.makedirs(today_connect_log_dir) - os.chmod(today_connect_log_dir, 0777) + mkdir(os.path.dirname(today_connect_log_dir), mode=0777) + mkdir(today_connect_log_dir, mode=0777) except OSError: - raise ServerError('Create %s failed, Please modify %s permission.' % (today_connect_log_dir, connect_log_dir)) + logger.debug('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir)) + raise ServerError('创建目录 %s 失败,请修改%s目录权限' % (today_connect_log_dir, tty_log_dir)) - try: - log_file = open(log_file_path, 'a') - except IOError: - raise ServerError('Create logfile failed, Please modify %s permission.' % today_connect_log_dir) + try: + log_file_f = open(log_file_path + '.log', 'a') + log_time_f = open(log_file_path + '.time', 'a') + except IOError: + logger.debug('创建tty日志文件失败, 请修改目录%s权限' % today_connect_log_dir) + raise ServerError('创建tty日志文件失败, 请修改目录%s权限' % today_connect_log_dir) - log = Log(user=username, host=host, remote_ip=ip_list, dept_name=dept_name, - log_path=log_file_path, start_time=datetime.datetime.now(), pid=pid) - log_file.write('Starttime is %s\n' % datetime.datetime.now()) - log.save() - return log_file, log + if self.login_type == 'ssh': # 如果是ssh连接过来,记录connect.py的pid,web terminal记录为日志的id + pid = os.getpid() + self.remote_ip = remote_ip # 获取远端IP + else: + pid = 0 + log = Log(user=self.username, host=self.asset_name, remote_ip=self.remote_ip, login_type=self.login_type, + log_path=log_file_path, start_time=date_today, pid=pid) + log.save() + if self.login_type == 'web': + log.pid = log.id # 设置log id为websocket的id, 然后kill时干掉websocket + log.save() -def posix_shell(chan, username, host): - """ - Use paramiko channel connect server interactive. - """ - log_file, log = log_record(username, host) - old_tty = termios.tcgetattr(sys.stdin) - try: - tty.setraw(sys.stdin.fileno()) - tty.setcbreak(sys.stdin.fileno()) - chan.settimeout(0.0) + log_file_f.write('Start at %s\r\n' % datetime.datetime.now()) + return log_file_f, log_time_f, log - while True: - try: - r, w, e = select.select([chan, sys.stdin], [], []) - except: - pass + def get_connect_info(self): + """ + 获取需要登陆的主机的信息和映射用户的账号密码 + """ + asset_info = get_asset_info(self.asset) + role_key = get_role_key(self.user, self.role) # 获取角色的key,因为ansible需要权限是600,所以统一生成用户_角色key + role_pass = CRYPTOR.decrypt(self.role.password) + connect_info = {'user': self.user, 'asset': self.asset, 'ip': asset_info.get('ip'), + 'port': int(asset_info.get('port')), 'role_name': self.role.name, + 'role_pass': role_pass, 'role_key': role_key} + logger.debug(connect_info) + return connect_info - if chan in r: + def get_connection(self): + """ + 获取连接成功后的ssh + """ + connect_info = self.get_connect_info() + + # 发起ssh连接请求 Make a ssh connection + ssh = paramiko.SSHClient() + ssh.load_system_host_keys() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + role_key = connect_info.get('role_key') + if role_key and os.path.isfile(role_key): try: - x = chan.recv(1024) - if len(x) == 0: - break - sys.stdout.write(x) - sys.stdout.flush() - log_file.write(x) - log_file.flush() - except socket.timeout: + ssh.connect(connect_info.get('ip'), + port=connect_info.get('port'), + username=connect_info.get('role_name'), + password=connect_info.get('role_pass'), + key_filename=role_key, + look_for_keys=False) + return ssh + except (paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException): + logger.warning(u'使用ssh key %s 失败, 尝试只使用密码' % role_key) pass - if sys.stdin in r: - x = os.read(sys.stdin.fileno(), 1) - if len(x) == 0: - break - chan.send(x) + ssh.connect(connect_info.get('ip'), + port=connect_info.get('port'), + username=connect_info.get('role_name'), + password=connect_info.get('role_pass'), + allow_agent=False, + look_for_keys=False) - finally: - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) - log_file.write('Endtime is %s' % datetime.datetime.now()) - log_file.close() - log.is_finished = True - log.log_finished = False - log.end_time = datetime.datetime.now() - log.save() - print_prompt() - - -def get_user_hostgroup(username): - """Get the hostgroups of under the user control.""" - groups_attr = {} - group_all = user_perm_group_api(username) - for group in group_all: - groups_attr[group.name] = [group.id, group.comment] - return groups_attr - - -def get_user_hostgroup_host(username, gid): - """Get the hostgroup hosts of under the user control.""" - hosts_attr = {} - user = User.objects.get(username=username) - hosts = user_perm_group_hosts_api(gid) - for host in hosts: - alias = AssetAlias.objects.filter(user=user, host=host) - if alias and alias[0].alias != '': - hosts_attr[host.ip] = [host.id, host.ip, alias[0].alias] + except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: + raise ServerError('认证失败 Authentication Error.') + except socket.error: + raise ServerError('端口可能不对 Connect SSH Socket Port Error, Please Correct it.') else: - hosts_attr[host.ip] = [host.id, host.ip, host.comment] - return hosts_attr + self.ssh = ssh + return ssh -def verify_connect(username, part_ip): - ip_matched = [] - try: - hosts_attr = get_user_host(username) - hosts = hosts_attr.values() - except ServerError, e: - color_print(e, 'red') - return False - - for ip_info in hosts: - if part_ip in ip_info[1:] and part_ip: - ip_matched = [ip_info[1]] - break - for info in ip_info[1:]: - if part_ip in info: - ip_matched.append(ip_info[1]) - - ip_matched = list(set(ip_matched)) - if len(ip_matched) > 1: - for ip in ip_matched: - print '%-15s -- %s' % (ip, hosts_attr[ip][2]) - elif len(ip_matched) < 1: - color_print('No Permission or No host.', 'red') - else: - username, password, host, port = get_connect_item(username, ip_matched[0]) - connect(username, password, host, port, LOGIN_NAME) - - -def print_prompt(): - msg = """\033[1;32m### Welcome Use JumpServer To Login. ### \033[0m - 1) Type \033[32mIP or Part IP, Host Alias or Comments \033[0m To Login. - 2) Type \033[32mP/p\033[0m To Print The Servers You Available. - 3) Type \033[32mG/g\033[0m To Print The Server Groups You Available. - 4) Type \033[32mG/g(1-N)\033[0m To Print The Server Group Hosts You Available. - 5) Type \033[32mE/e\033[0m To Execute Command On Several Servers. - 6) Type \033[32mQ/q\033[0m To Quit. +class SshTty(Tty): """ - print textwrap.dedent(msg) - - -def print_user_host(username): - try: - hosts_attr = get_user_host(username) - except ServerError, e: - color_print(e, 'red') - return - hosts = hosts_attr.keys() - hosts.sort() - for ip in hosts: - print '%-15s -- %s' % (ip, hosts_attr[ip][2]) - print '' - - -def print_user_hostgroup(username): - group_attr = get_user_hostgroup(username) - groups = group_attr.keys() - for g in groups: - print "[%3s] %s -- %s" % (group_attr[g][0], g, group_attr[g][1]) - - -def print_user_hostgroup_host(username, gid): - pattern = re.compile(r'\d+') - match = pattern.match(gid) - if match: - hosts_attr = get_user_hostgroup_host(username, gid) - hosts = hosts_attr.keys() - hosts.sort() - for ip in hosts: - print '%-15s -- %s' % (ip, hosts_attr[ip][2]) - else: - color_print('No such group id, Please check it.', 'red') - - -def connect(username, password, host, port, login_name): + A virtual tty class + 一个虚拟终端类,实现连接ssh和记录日志 """ - Connect server. - """ - ps1 = "PS1='[\u@%s \W]\$ '\n" % host - login_msg = "clear;echo -e '\\033[32mLogin %s done. Enjoy it.\\033[0m'\n" % host - # Make a ssh connection - ssh = paramiko.SSHClient() - ssh.load_system_host_keys() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - try: - ssh.connect(host, port=port, username=username, password=password, compress=True) - except paramiko.ssh_exception.AuthenticationException, paramiko.ssh_exception.SSHException: - raise ServerError('Authentication Error.') - except socket.error: - raise ServerError('Connect SSH Socket Port Error, Please Correct it.') + @staticmethod + def get_win_size(): + """ + This function use to get the size of the windows! + 获得terminal窗口大小 + """ + if 'TIOCGWINSZ' in dir(termios): + TIOCGWINSZ = termios.TIOCGWINSZ + else: + TIOCGWINSZ = 1074295912L + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] - # Make a channel and set windows size - global channel - win_size = get_win_size() - channel = ssh.invoke_shell(height=win_size[0], width=win_size[1]) - try: - signal.signal(signal.SIGWINCH, set_win_size) - except: - pass + def set_win_size(self, sig, data): + """ + This function use to set the window size of the terminal! + 设置terminal窗口大小 + """ + try: + win_size = self.get_win_size() + self.channel.resize_pty(height=win_size[0], width=win_size[1]) + except Exception: + pass - # Set PS1 and msg it - channel.send(ps1) - channel.send(login_msg) + def posix_shell(self): + """ + Use paramiko channel connect server interactive. + 使用paramiko模块的channel,连接后端,进入交互式 + """ + log_file_f, log_time_f, log = self.get_log() + old_tty = termios.tcgetattr(sys.stdin) + pre_timestamp = time.time() + data = '' + input_mode = False + try: + tty.setraw(sys.stdin.fileno()) + tty.setcbreak(sys.stdin.fileno()) + self.channel.settimeout(0.0) - # Make ssh interactive tunnel - posix_shell(channel, login_name, host) + while True: + try: + r, w, e = select.select([self.channel, sys.stdin], [], []) + flag = fcntl.fcntl(sys.stdin, fcntl.F_GETFL, 0) + fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flag|os.O_NONBLOCK) + except Exception: + pass - # Shutdown channel socket - channel.close() - ssh.close() + if self.channel in r: + try: + x = self.channel.recv(10240) + if len(x) == 0: + break + if self.vim_flag: + self.vim_data += x + index = 0 + len_x = len(x) + while index < len_x: + try: + n = os.write(sys.stdout.fileno(), x[index:]) + sys.stdout.flush() + index += n + except OSError as msg: + if msg.errno == errno.EAGAIN: + continue + #sys.stdout.write(x) + #sys.stdout.flush() + now_timestamp = time.time() + log_time_f.write('%s %s\n' % (round(now_timestamp-pre_timestamp, 4), len(x))) + log_time_f.flush() + log_file_f.write(x) + log_file_f.flush() + pre_timestamp = now_timestamp + log_file_f.flush() + if input_mode and not self.is_output(x): + data += x -def remote_exec_cmd(ip, port, username, password, cmd): - try: - time.sleep(5) - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(ip, port, username, password, timeout=5) - stdin, stdout, stderr = ssh.exec_command("bash -l -c '%s'" % cmd) - out = stdout.readlines() - err = stderr.readlines() - color_print('%s:' % ip, 'blue') - for i in out: - color_print(" " * 4 + i.strip(), 'green') - for j in err: - color_print(" " * 4 + j.strip(), 'red') + except socket.timeout: + pass + + if sys.stdin in r: + x = os.read(sys.stdin.fileno(), 4096) + input_mode = True + if str(x) in ['\r', '\n', '\r\n']: + if self.vim_flag: + match = self.ps1_pattern.search(self.vim_data) + if match: + self.vim_flag = False + data = self.deal_command(data)[0:200] + if len(data) > 0: + TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() + else: + data = self.deal_command(data)[0:200] + if len(data) > 0: + TtyLog(log=log, datetime=datetime.datetime.now(), cmd=data).save() + data = '' + self.vim_data = '' + input_mode = False + + if len(x) == 0: + break + self.channel.send(x) + + finally: + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) + log_file_f.write('End time is %s' % datetime.datetime.now()) + log_file_f.close() + log_time_f.close() + log.is_finished = True + log.end_time = datetime.datetime.now() + log.save() + + def connect(self): + """ + Connect server. + 连接服务器 + """ + # 发起ssh连接请求 Make a ssh connection + ssh = self.get_connection() + + transport = ssh.get_transport() + transport.set_keepalive(30) + transport.use_compression(True) + + # 获取连接的隧道并设置窗口大小 Make a channel and set windows size + global channel + win_size = self.get_win_size() + #self.channel = channel = ssh.invoke_shell(height=win_size[0], width=win_size[1], term='xterm') + self.channel = channel = transport.open_session() + channel.get_pty(term='xterm', height=win_size[0], width=win_size[1]) + channel.invoke_shell() + try: + signal.signal(signal.SIGWINCH, self.set_win_size) + except: + pass + + self.posix_shell() + + # Shutdown channel socket + channel.close() ssh.close() - except Exception as e: - color_print(ip + ':', 'blue') - color_print(str(e), 'red') -def multi_remote_exec_cmd(hosts, username, cmd): - pool = Pool(processes=5) - for host in hosts: - username, password, ip, port = get_connect_item(username, host) - pool.apply_async(remote_exec_cmd, (ip, port, username, password, cmd)) - pool.close() - pool.join() +class Nav(object): + """ + 导航提示类 + """ + def __init__(self, user): + self.user = user + self.search_result = {} + self.user_perm = {} + @staticmethod + def print_nav(): + """ + Print prompt + 打印提示导航 + """ + msg = """\n\033[1;32m### 欢迎使用Jumpserver开源跳板机系统 ### \033[0m -def exec_cmd_servers(username): - color_print("You can choose in the following IP(s), Use glob or ips split by comma. q/Q to PreLayer.", 'green') - print_user_host(LOGIN_NAME) - while True: - hosts = [] - inputs = raw_input('\033[1;32mip(s)>: \033[0m') - if inputs in ['q', 'Q']: - break - get_hosts = get_user_host(username).keys() + 1) 输入 \033[32mID\033[0m 直接登录. + 2) 输入 \033[32m/\033[0m + \033[32mIP, 主机名 or 备注 \033[0m搜索. + 3) 输入 \033[32mP/p\033[0m 显示您有权限的主机. + 4) 输入 \033[32mG/g\033[0m 显示您有权限的主机组. + 5) 输入 \033[32mG/g\033[0m\033[0m + \033[32m组ID\033[0m 显示该组下主机. + 6) 输入 \033[32mE/e\033[0m 批量执行命令. + 7) 输入 \033[32mU/u\033[0m 批量上传文件. + 8) 输入 \033[32mD/d\033[0m 批量下载文件. + 9) 输入 \033[32mH/h\033[0m 帮助. + 0) 输入 \033[32mQ/q\033[0m 退出. + """ + print textwrap.dedent(msg) - if ',' in inputs: - ips_input = inputs.split(',') - for host in ips_input: - if host in get_hosts: - hosts.append(host) + def search(self, str_r=''): + gid_pattern = re.compile(r'^g\d+$') + # 获取用户授权的所有主机信息 + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + user_asset_all = self.user_perm.get('asset').keys() + # 搜索结果保存 + user_asset_search = [] + if str_r: + # 资产组组id匹配 + if gid_pattern.match(str_r): + gid = int(str_r.lstrip('g')) + # 获取资产组包含的资产 + user_asset_search = get_object(AssetGroup, id=gid).asset_set.all() + else: + # 匹配 ip, hostname, 备注 + for asset in user_asset_all: + if str_r in asset.ip or str_r in str(asset.hostname) or str_r in str(asset.comment): + user_asset_search.append(asset) else: - for host in get_hosts: - if fnmatch.fnmatch(host, inputs): - hosts.append(host.strip()) + # 如果没有输入就展现所有 + user_asset_search = user_asset_all - if len(hosts) == 0: - color_print("Check again, Not matched any ip!", 'red') - continue - else: - print "You matched ip: %s" % hosts - color_print("Input the Command , The command will be Execute on servers, q/Q to quit.", 'green') + self.search_result = dict(zip(range(len(user_asset_search)), user_asset_search)) + color_print('[%-3s] %-12s %-15s %-5s %-10s %s' % ('ID', u'主机名', 'IP', u'端口', u'系统用户', u'备注'), 'title') + for index, asset in self.search_result.items(): + # 获取该资产信息 + asset_info = get_asset_info(asset) + # 获取该资产包含的角色 + role = [str(role.name) for role in self.user_perm.get('asset').get(asset).get('role')] + print '[%-3s] %-15s %-15s %-5s %-10s %s' % (index, asset.hostname, asset.ip, asset_info.get('port'), + role, asset.comment) + print + + def print_asset_group(self): + """ + 打印用户授权的资产组 + """ + user_asset_group_all = get_group_user_perm(self.user).get('asset_group', []) + color_print('[%-3s] %-20s %s' % ('ID', '组名', '备注'), 'title') + for asset_group in user_asset_group_all: + print '[%-3s] %-15s %s' % (asset_group.id, asset_group.name, asset_group.comment) + print + + def exec_cmd(self): + """ + 批量执行命令 + """ while True: - cmd = raw_input('\033[1;32mCmd(s): \033[0m') - if cmd in ['q', 'Q']: + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + + roles = self.user_perm.get('role').keys() + if len(roles) > 1: # 授权角色数大于1 + color_print('[%-2s] %-15s' % ('ID', '系统用户'), 'info') + role_check = dict(zip(range(len(roles)), roles)) + + for i, r in role_check.items(): + print '[%-2s] %-15s' % (i, r.name) + print + print "请输入运行命令所关联系统用户的ID, q退出" + + try: + role_id = raw_input("\033[1;32mRole>:\033[0m ").strip() + if role_id == 'q': + break + except (IndexError, ValueError): + color_print('错误输入') + else: + role = role_check[int(role_id)] + elif len(roles) == 1: # 授权角色数为1 + role = roles[0] + assets = list(self.user_perm.get('role', {}).get(role).get('asset')) # 获取该用户,角色授权主机 + print "授权包含该系统用户的所有主机" + for asset in assets: + print ' %s' % asset.hostname + print + print "请输入主机名或ansile支持的pattern, 多个主机:分隔, q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': break - exec_log_dir = os.path.join(LOG_DIR, 'exec_cmds') - if not os.path.isdir(exec_log_dir): - os.mkdir(exec_log_dir) - os.chmod(exec_log_dir, 0777) - filename = "%s/%s.log" % (exec_log_dir, time.strftime('%Y%m%d')) - f = open(filename, 'a') - f.write("DateTime: %s User: %s Host: %s Cmds: %s\n" % - (time.strftime('%Y/%m/%d %H:%M:%S'), username, hosts, cmd)) - multi_remote_exec_cmd(hosts, username, cmd) + else: + res = gen_resource({'user': self.user, 'asset': assets, 'role': role}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配主机:" + for inv in runner.inventory.get_hosts(pattern=pattern): + print ' %s' % inv.name + asset_name_str += '%s ' % inv.name + print + + while True: + print "请输入执行的命令, 按q退出" + command = raw_input("\033[1;32mCmds>:\033[0m ").strip() + if command == 'q': + break + runner.run('shell', command, pattern=pattern) + ExecLog(host=asset_name_str, user=self.user.username, cmd=command, remote_ip=remote_ip, + result=runner.results).save() + for k, v in runner.results.items(): + if k == 'ok': + for host, output in v.items(): + color_print("%s => %s" % (host, 'Ok'), 'green') + print output + print + else: + for host, output in v.items(): + color_print("%s => %s" % (host, k), 'red') + color_print(output, 'red') + print + print "~o~ Task finished ~o~" + print + + def upload(self): + while True: + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + try: + print "进入批量上传模式" + print "请输入主机名或ansile支持的pattern, 多个主机:分隔 q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': + break + else: + assets = self.user_perm.get('asset').keys() + res = gen_resource({'user': self.user, 'asset': assets}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配主机:" + for inv in runner.inventory.get_hosts(pattern=pattern): + print inv.name + asset_name_str += '%s ' % inv.name + + if not asset_name_str: + color_print('没有匹配主机') + continue + tmp_dir = get_tmp_dir() + logger.debug('Upload tmp dir: %s' % tmp_dir) + os.chdir(tmp_dir) + bash('rz') + filename_str = ' '.join(os.listdir(tmp_dir)) + if not filename_str: + color_print("上传文件为空") + continue + logger.debug('上传文件: %s' % filename_str) + + runner = MyRunner(res) + runner.run('copy', module_args='src=%s dest=%s directory_mode' + % (tmp_dir, tmp_dir), pattern=pattern) + ret = runner.results + FileLog(user=self.user.name, host=asset_name_str, filename=filename_str, + remote_ip=remote_ip, type='upload', result=ret).save() + logger.debug('Upload file: %s' % ret) + if ret.get('failed'): + error = '上传目录: %s \n上传失败: [ %s ] \n上传成功 [ %s ]' % (tmp_dir, + ', '.join(ret.get('failed').keys()), + ', '.join(ret.get('ok').keys())) + color_print(error) + else: + msg = '上传目录: %s \n传送成功 [ %s ]' % (tmp_dir, ', '.join(ret.get('ok').keys())) + color_print(msg, 'green') + print + + except IndexError: + pass + + def download(self): + while True: + if not self.user_perm: + self.user_perm = get_group_user_perm(self.user) + try: + print "进入批量下载模式" + print "请输入主机名或ansile支持的pattern, 多个主机:分隔,q退出" + pattern = raw_input("\033[1;32mPattern>:\033[0m ").strip() + if pattern == 'q': + break + else: + assets = self.user_perm.get('asset').keys() + res = gen_resource({'user': self.user, 'asset': assets}, perm=self.user_perm) + runner = MyRunner(res) + asset_name_str = '' + print "匹配用户:\n" + for inv in runner.inventory.get_hosts(pattern=pattern): + asset_name_str += '%s ' % inv.name + print ' %s' % inv.name + if not asset_name_str: + color_print('没有匹配主机') + continue + print + while True: + tmp_dir = get_tmp_dir() + logger.debug('Download tmp dir: %s' % tmp_dir) + print "请输入文件路径(不支持目录)" + file_path = raw_input("\033[1;32mPath>:\033[0m ").strip() + if file_path == 'q': + break + + if not file_path: + print "文件路径为空" + continue + + runner.run('fetch', module_args='src=%s dest=%s' % (file_path, tmp_dir), pattern=pattern) + ret = runner.results + FileLog(user=self.user.name, host=asset_name_str, filename=file_path, type='download', + remote_ip=remote_ip, result=ret).save() + logger.debug('Download file result: %s' % ret) + os.chdir('/tmp') + tmp_dir_name = os.path.basename(tmp_dir) + if not os.listdir(tmp_dir): + color_print('下载全部失败') + continue + bash('tar czf %s.tar.gz %s && sz %s.tar.gz' % (tmp_dir, tmp_dir_name, tmp_dir)) + + if ret.get('failed'): + error = '文件名称: %s \n下载失败: [ %s ] \n下载成功 [ %s ]' % \ + ('%s.tar.gz' % tmp_dir_name, ', '.join(ret.get('failed').keys()), ', '.join(ret.get('ok').keys())) + color_print(error) + else: + msg = '文件名称: %s \n下载成功 [ %s ]' % ('%s.tar.gz' % tmp_dir_name, ', '.join(ret.get('ok').keys())) + color_print(msg, 'green') + print + except IndexError: + pass -if __name__ == '__main__': - print_prompt() +def main(): + """ + he he + 主程序 + """ + if not login_user: # 判断用户是否存在 + color_print(u'没有该用户,或许你是以root运行的 No that user.', exits=True) + gid_pattern = re.compile(r'^g\d+$') + nav = Nav(login_user) + nav.print_nav() + try: while True: try: - option = raw_input("\033[1;32mOpt or IP>:\033[0m ") + option = raw_input("\033[1;32mOpt or ID>:\033[0m ").strip() except EOFError: - print + nav.print_nav() continue except KeyboardInterrupt: sys.exit(0) - if option in ['P', 'p']: - print_user_host(LOGIN_NAME) + if option in ['P', 'p', '\n', '']: + nav.search() continue + if option.startswith('/') or gid_pattern.match(option): + nav.search(option.lstrip('/')) elif option in ['G', 'g']: - print_user_hostgroup(LOGIN_NAME) - continue - elif gid_pattern.match(option): - gid = option[1:].strip() - print_user_hostgroup_host(LOGIN_NAME, gid) + nav.print_asset_group() continue elif option in ['E', 'e']: - exec_cmd_servers(LOGIN_NAME) + nav.exec_cmd() + continue + elif option in ['U', 'u']: + nav.upload() + elif option in ['D', 'd']: + nav.download() + elif option in ['H', 'h']: + nav.print_nav() elif option in ['Q', 'q', 'exit']: sys.exit() else: try: - verify_connect(LOGIN_NAME, option) + asset = nav.search_result[int(option)] + roles = nav.user_perm.get('asset').get(asset).get('role') + if len(roles) > 1: + role_check = dict(zip(range(len(roles)), roles)) + print "\033[32m[ID] 系统用户\033[0m" + for index, role in role_check.items(): + print "[%-2s] %s" % (index, role.name) + print + print "授权系统用户超过1个,请输入ID, q退出" + try: + role_index = raw_input("\033[1;32mID>:\033[0m ").strip() + if role_index == 'q': + continue + else: + role = role_check[int(role_index)] + except IndexError: + color_print('请输入正确ID', 'red') + continue + elif len(roles) == 1: + role = list(roles)[0] + else: + color_print('没有映射用户', 'red') + continue + ssh_tty = SshTty(login_user, asset, role) + ssh_tty.connect() + except (KeyError, ValueError): + color_print('请输入正确ID', 'red') except ServerError, e: color_print(e, 'red') except IndexError: pass + +if __name__ == '__main__': + main() diff --git a/docs/AddUserAsset.py b/docs/AddUserAsset.py deleted file mode 100644 index f8a5ed63c..000000000 --- a/docs/AddUserAsset.py +++ /dev/null @@ -1,141 +0,0 @@ -#coding:utf-8 -import django -import os -import sys -import random -import datetime - -sys.path.append('../') -os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -#django.setup() - - -from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group -from jasset.models import Asset, IDC, BisGroup -from juser.models import UserGroup, DEPT, User -from jperm.models import CmdGroup -from jlog.models import Log - - -def install(): - IDC.objects.create(name='ALL', comment='ALL') - IDC.objects.create(name='默认', comment='默认') - DEPT.objects.create(name="默认", comment="默认部门") - DEPT.objects.create(name="超管部", comment="超级管理员部门") - dept = DEPT.objects.get(name='超管部') - dept2 = DEPT.objects.get(name='默认') - UserGroup.objects.create(name='ALL', dept=dept, comment='ALL') - UserGroup.objects.create(name='默认', dept=dept, comment='默认') - - BisGroup.objects.create(name='ALL', dept=dept, comment='ALL') - BisGroup.objects.create(name='默认', dept=dept, comment='默认') - - User(id=5000, username="admin", password=md5_crypt('admin'), - name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() - User(id=5001, username="group_admin", password=md5_crypt('group_admin'), - name='group_admin', email='group_admin@jumpserver.org', role='DA', is_active=True, dept=dept2).save() - - -def test_add_idc(): - for i in range(1, 20): - name = 'IDC' + str(i) - IDC.objects.create(name=name, comment='') - print 'Add: %s' % name - - -def test_add_dept(): - for i in range(1, 100): - name = 'DEPT' + str(i) - print "Add: %s" % name - DEPT.objects.create(name=name, comment=name) - - -def test_add_group(): - dept_all = DEPT.objects.all() - for i in range(1, 100): - name = 'UserGroup' + str(i) - UserGroup.objects.create(name=name, dept=random.choice(dept_all), comment=name) - print 'Add: %s' % name - - -def test_add_cmd_group(): - for i in range(1, 20): - name = 'CMD' + str(i) - cmd = '/sbin/ping%s, /sbin/ifconfig/' % str(i) - CmdGroup.objects.create(name=name, cmd=cmd, comment=name) - print 'Add: %s' % name - - -def test_add_user(): - for i in range(1, 500): - username = "test" + str(i) - dept_all = DEPT.objects.all() - group_all = UserGroup.objects.all() - group_all_id = [group.id for group in group_all] - db_add_user(username=username, - password=md5_crypt(username), - dept=random.choice(dept_all), - name=username, email='%s@jumpserver.org' % username, - groups=[random.choice(group_all_id) for i in range(1, 4)], role='CU', - ssh_key_pwd=CRYPTOR.encrypt(username), - ldap_pwd=CRYPTOR.encrypt(username), - is_active=True, - date_joined=datetime.datetime.now()) - print "Add: %s" % username - - -def test_add_asset_group(): - dept = DEPT.objects.get(name='默认') - for i in range(1, 20): - name = 'AssetGroup' + str(i) - group = BisGroup(name=name, dept=dept, comment=name) - group.save() - print 'Add: %s' % name - - -def test_add_asset(): - idc_all = IDC.objects.all() - test_idc = random.choice(idc_all) - bis_group_all = BisGroup.objects.all() - dept_all = DEPT.objects.all() - for i in range(1, 500): - ip = '192.168.5.' + str(i) - asset = Asset(ip=ip, port=22, login_type='L', idc=test_idc, is_active=True, comment='test') - asset.save() - asset.bis_group = [random.choice(bis_group_all) for i in range(2)] - asset.dept = [random.choice(dept_all) for i in range(2)] - print "Add: %s" % ip - - -def test_add_log(): - li_date = [] - today = datetime.date.today() - oneday = datetime.timedelta(days=1) - for i in range(0, 7): - today = today-oneday - li_date.append(today) - user_list = ['马云', '马化腾', '丁磊', '周鸿祎', '雷军', '柳传志', '陈天桥', '李彦宏', '李开复', '罗永浩'] - for i in range(1, 1000): - user = random.choice(user_list) - ip = random.randint(1, 20) - start_time = random.choice(li_date) - end_time = datetime.datetime.now() - log_path = '/var/log/jumpserver/test.log' - host = '192.168.1.' + str(ip) - Log.objects.create(user=user, host=host, remote_ip='8.8.8.8', dept_name='运维部', log_path=log_path, pid=168, start_time=start_time, - is_finished=1, log_finished=1, end_time=end_time) - - -if __name__ == '__main__': - #install() - #test_add_dept() - #test_add_group() - #test_add_user() - #test_add_idc() - #test_add_asset_group() - test_add_asset() - #test_add_log() - - - - diff --git a/docs/__init__.py b/docs/__init__.py deleted file mode 100644 index bfd53d39f..000000000 --- a/docs/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'Hudie' diff --git a/docs/developer_doc.txt b/docs/developer_doc.txt deleted file mode 100644 index d24cacdcd..000000000 --- a/docs/developer_doc.txt +++ /dev/null @@ -1,36 +0,0 @@ -# coding: utf8 - -Jumpserver开发者文档 - -开发规范: - 1. 遵守PE8规范 1) 命名规范 2) 导入模块规范 3) 空行规范 4) 长度规范 - 2. 缩进统一4个空格 - 3. 变量命名明了易懂多个单词下划线隔开 - 4. 注释到位 - - -框架说明: - 1. 项目名称 Jumpserver - 2. APP: - juser 用户管理 - jasset 资产管理(设备管理) - jpermission 授权管理 - jlog 日志管理 - 3. connect.py 用户登录入口程序 - 4. logs 日志保存目录 - 5. jumpserver.conf 配置文件 - 6. docs 文档目录 - 7. static 静态文件目录 - 8. templates 模板目录 - - -connect.py逻辑说明: - 用户登录系统,运行该脚本,p调用get_user_host函数查看有权限的服务器ip - 输入部分IP,verify_connect匹配该部分ip,如果是匹配到多个,就显示ip - 匹配到0了就显示没有权限或者主机, - 匹配到1个则继续 - 查询该服务器是否支持ldap 如果是,获得ldap用户密码登陆 - 如果否,查询授权表,查看该服务器授权的角色,并返回对应账号密码,登陆 - connect函数是登陆函数,采用paramiko 使用channel登陆,posix_shell 来完成交互,并记录日志 - signal模块来完成窗口改变导致的tty大小随之改变 - PyCrypt是对称加密类 \ No newline at end of file diff --git a/docs/install.py b/docs/install.py deleted file mode 100644 index a032bf327..000000000 --- a/docs/install.py +++ /dev/null @@ -1,135 +0,0 @@ -#coding:utf-8 -import django -import os -import sys -import random -import datetime - -sys.path.append('../') -os.environ['DJANGO_SETTINGS_MODULE'] = 'jumpserver.settings' -#django.setup() - - -from juser.views import db_add_user, md5_crypt, CRYPTOR, db_add_group -from jasset.models import Asset, IDC, BisGroup -from juser.models import UserGroup, DEPT, User -from jasset.views import jasset_group_add -from jperm.models import CmdGroup -from jlog.models import Log - - -def install(): - IDC.objects.create(name='ALL', comment='ALL') - IDC.objects.create(name='默认', comment='默认') - DEPT.objects.create(name="默认", comment="默认部门") - DEPT.objects.create(name="超管部", comment="超级管理员部门") - dept = DEPT.objects.get(name='超管部') - dept2 = DEPT.objects.get(name='默认') - UserGroup.objects.create(name='ALL', dept=dept, comment='ALL') - UserGroup.objects.create(name='默认', dept=dept, comment='默认') - - BisGroup.objects.create(name='ALL', dept=dept, comment='ALL') - BisGroup.objects.create(name='默认', dept=dept, comment='默认') - - User(id=5000, username="admin", password=md5_crypt('admin'), - name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() - User(id=5001, username="group_admin", password=md5_crypt('group_admin'), - name='group_admin', email='group_admin@jumpserver.org', role='DA', is_active=True, dept=dept2).save() - - -def test_add_idc(): - for i in range(1, 20): - name = 'IDC' + str(i) - IDC.objects.create(name=name, comment='') - print 'Add: %s' % name - - -def test_add_dept(): - for i in range(1, 100): - name = 'DEPT' + str(i) - print "Add: %s" % name - DEPT.objects.create(name=name, comment=name) - - -def test_add_group(): - dept_all = DEPT.objects.all() - for i in range(1, 100): - name = 'UserGroup' + str(i) - UserGroup.objects.create(name=name, dept=random.choice(dept_all), comment=name) - print 'Add: %s' % name - - -def test_add_cmd_group(): - for i in range(1, 20): - name = 'CMD' + str(i) - cmd = '/sbin/ping%s, /sbin/ifconfig/' % str(i) - CmdGroup.objects.create(name=name, cmd=cmd, comment=name) - print 'Add: %s' % name - - -def test_add_user(): - for i in range(1, 500): - username = "test" + str(i) - dept_all = DEPT.objects.all() - group_all = UserGroup.objects.all() - group_all_id = [group.id for group in group_all] - db_add_user(username=username, - password=md5_crypt(username), - dept=random.choice(dept_all), - name=username, email='%s@jumpserver.org' % username, - groups=[random.choice(group_all_id) for i in range(1, 4)], role='CU', - ssh_key_pwd=CRYPTOR.encrypt(username), - ldap_pwd=CRYPTOR.encrypt(username), - is_active=True, - date_joined=datetime.datetime.now()) - print "Add: %s" % username - - -def test_add_asset_group(): - dept = DEPT.objects.get(name='默认') - for i in range(1, 20): - name = 'AssetGroup' + str(i) - group = BisGroup(name=name, dept=dept, comment=name) - group.save() - print 'Add: %s' % name - - -def test_add_asset(): - idc_all = IDC.objects.all() - test_idc = random.choice(idc_all) - bis_group_all = BisGroup.objects.all() - dept_all = DEPT.objects.all() - for i in range(1, 500): - ip = '192.168.1.' + str(i) - asset = Asset(ip=ip, port=22, login_type='L', idc=test_idc, is_active=True, comment='test') - asset.save() - asset.bis_group = [random.choice(bis_group_all) for i in range(2)] - asset.dept = [random.choice(dept_all) for i in range(2)] - print "Add: %s" % ip - - -def test_add_log(): - li_date = [] - today = datetime.date.today() - oneday = datetime.timedelta(days=1) - for i in range(0, 7): - today = today-oneday - li_date.append(today) - user_list = ['马云', '马化腾', '丁磊', '周鸿祎', '雷军', '柳传志', '陈天桥', '李彦宏', '李开复', '罗永浩'] - for i in range(1, 1000): - user = random.choice(user_list) - ip = random.randint(1, 20) - start_time = random.choice(li_date) - end_time = datetime.datetime.now() - log_path = '/var/log/jumpserver/test.log' - host = '192.168.1.' + str(ip) - Log.objects.create(user=user, host=host, log_path=log_path, pid=168, start_time=start_time, - is_finished=1, log_finished=1, end_time=end_time) - - -if __name__ == '__main__': - install() - - - - diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index d0d83cafc..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -sphinx-me==0.3 -django==1.6 -python-ldap==2.4.19 -pycrypto==2.6.1 -paramiko==1.15.2 -ecdsa==0.13 -MySQL-python==1.2.5 -django-uuidfield==0.5.0 -psutil==2.2.1 \ No newline at end of file diff --git a/docs/zzjumpserver.sh b/docs/zzjumpserver.sh deleted file mode 100644 index 98598ff18..000000000 --- a/docs/zzjumpserver.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -if [ "$USER" == "admin" ] || [ "$USER" == "root" ] || [ "$USER" == "" ];then - echo "" -else - python /opt/jumpserver/connect.py - if [ $USER == 'guanghongwei' ];then - echo - else - exit 3 - echo - fi -fi diff --git a/jasset/models.py b/jasset/models.py index 416c4ddfa..2851b7d56 100644 --- a/jasset/models.py +++ b/jasset/models.py @@ -1,54 +1,111 @@ +# coding: utf-8 + import datetime from django.db import models -from juser.models import User, UserGroup, DEPT +from juser.models import User, UserGroup + +ASSET_ENV = ( + (1, U'生产环境'), + (2, U'测试环境') + ) + +ASSET_STATUS = ( + (1, u"已使用"), + (2, u"未使用"), + (3, u"报废") + ) + +ASSET_TYPE = ( + (1, u"物理机"), + (2, u"虚拟机"), + (3, u"交换机"), + (4, u"路由器"), + (5, u"防火墙"), + (6, u"Docker"), + (7, u"其他") + ) -class IDC(models.Model): - name = models.CharField(max_length=40, unique=True) - comment = models.CharField(max_length=80, blank=True, null=True) - - def __unicode__(self): - return self.name - - -class BisGroup(models.Model): +class AssetGroup(models.Model): GROUP_TYPE = ( ('P', 'PRIVATE'), ('A', 'ASSET'), ) name = models.CharField(max_length=80, unique=True) - dept = models.ForeignKey(DEPT) comment = models.CharField(max_length=160, blank=True, null=True) def __unicode__(self): return self.name +class IDC(models.Model): + name = models.CharField(max_length=32, verbose_name=u'机房名称') + bandwidth = models.CharField(max_length=32, blank=True, null=True, default='', verbose_name=u'机房带宽') + linkman = models.CharField(max_length=16, blank=True, null=True, default='', verbose_name=u'联系人') + phone = models.CharField(max_length=32, blank=True, null=True, default='', verbose_name=u'联系电话') + address = models.CharField(max_length=128, blank=True, null=True, default='', verbose_name=u"机房地址") + network = models.TextField(blank=True, null=True, default='', verbose_name=u"IP地址段") + date_added = models.DateField(auto_now=True, null=True) + operator = models.CharField(max_length=32, blank=True, default='', null=True, verbose_name=u"运营商") + comment = models.CharField(max_length=128, blank=True, default='', null=True, verbose_name=u"备注") + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = u"IDC机房" + verbose_name_plural = verbose_name + + class Asset(models.Model): - LOGIN_TYPE_CHOICES = ( - ('L', 'LDAP'), - ('M', 'MAP'), - ) - ip = models.IPAddressField(unique=True) - port = models.IntegerField(max_length=6) - idc = models.ForeignKey(IDC) - bis_group = models.ManyToManyField(BisGroup) - dept = models.ManyToManyField(DEPT) - login_type = models.CharField(max_length=1, choices=LOGIN_TYPE_CHOICES, default='L') - username = models.CharField(max_length=20, blank=True, null=True) - password = models.CharField(max_length=80, blank=True, null=True) - date_added = models.DateTimeField(auto_now=True, default=datetime.datetime.now(), null=True) - is_active = models.BooleanField(default=True) - comment = models.CharField(max_length=100, blank=True, null=True) + """ + asset modle + """ + ip = models.CharField(max_length=32, blank=True, null=True, verbose_name=u"主机IP") + other_ip = models.CharField(max_length=255, blank=True, null=True, verbose_name=u"其他IP") + hostname = models.CharField(unique=True, max_length=128, verbose_name=u"主机名") + port = models.IntegerField(blank=True, null=True, verbose_name=u"端口号") + group = models.ManyToManyField(AssetGroup, blank=True, verbose_name=u"所属主机组") + username = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"管理用户名") + password = models.CharField(max_length=64, blank=True, null=True, verbose_name=u"密码") + use_default_auth = models.BooleanField(default=True, verbose_name=u"使用默认管理账号") + idc = models.ForeignKey(IDC, blank=True, null=True, on_delete=models.SET_NULL, verbose_name=u'机房') + mac = models.CharField(max_length=20, blank=True, null=True, verbose_name=u"MAC地址") + remote_ip = models.CharField(max_length=16, blank=True, null=True, verbose_name=u'远控卡IP') + brand = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'硬件厂商型号') + cpu = models.CharField(max_length=64, blank=True, null=True, verbose_name=u'CPU') + memory = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'内存') + disk = models.CharField(max_length=128, blank=True, null=True, verbose_name=u'硬盘') + system_type = models.CharField(max_length=32, blank=True, null=True, verbose_name=u"系统类型") + system_version = models.CharField(max_length=8, blank=True, null=True, verbose_name=u"系统版本号") + system_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=u"系统平台") + cabinet = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'机柜号') + position = models.IntegerField(blank=True, null=True, verbose_name=u'机器位置') + number = models.CharField(max_length=32, blank=True, null=True, verbose_name=u'资产编号') + status = models.IntegerField(choices=ASSET_STATUS, blank=True, null=True, default=1, verbose_name=u"机器状态") + asset_type = models.IntegerField(choices=ASSET_TYPE, blank=True, null=True, verbose_name=u"主机类型") + env = models.IntegerField(choices=ASSET_ENV, blank=True, null=True, verbose_name=u"运行环境") + sn = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"SN编号") + date_added = models.DateTimeField(auto_now=True, null=True) + is_active = models.BooleanField(default=True, verbose_name=u"是否激活") + comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=u"备注") def __unicode__(self): return self.ip +class AssetRecord(models.Model): + asset = models.ForeignKey(Asset) + username = models.CharField(max_length=30, null=True) + alert_time = models.DateTimeField(auto_now_add=True) + content = models.TextField(null=True, blank=True) + comment = models.TextField(null=True, blank=True) + + class AssetAlias(models.Model): user = models.ForeignKey(User) - host = models.ForeignKey(Asset) + asset = models.ForeignKey(Asset) alias = models.CharField(max_length=100, blank=True, null=True) def __unicode__(self): - return self.comment \ No newline at end of file + return self.alias diff --git a/jasset/urls.py b/jasset/urls.py index da52529b6..7daa9be20 100644 --- a/jasset/urls.py +++ b/jasset/urls.py @@ -3,26 +3,22 @@ from django.conf.urls import patterns, include, url from jasset.views import * urlpatterns = patterns('', - url(r'^host_add/$', host_add), - url(r"^host_add_multi/$", host_add_batch), - url(r'^host_list/$', host_list), - url(r'^search/$', host_search), - url(r"^host_detail/$", host_detail), - url(r"^dept_host_ajax/$", dept_host_ajax), - url(r"^show_all_ajax/$", show_all_ajax), - url(r'^idc_add/$', idc_add), - url(r'^idc_list/$', idc_list), - url(r'^idc_edit/$', idc_edit), - url(r'^idc_detail/$', idc_detail), - url(r'^idc_del/$', idc_del), - url(r'^group_add/$', group_add), - url(r'^group_edit/$', group_edit), - url(r'^group_list/$', group_list), - url(r'^group_detail/$', group_detail), - url(r'^group_del_host/$', group_del_host), - url(r'^group_del/$', group_del), - url(r'^host_del/(\w+)/$', host_del), - url(r'^host_edit/$', view_splitter, {'su': host_edit, 'adm': host_edit_adm}), - url(r'^host_edit/batch/$', host_edit_batch), - url(r'^host_edit_common/batch/$', host_edit_common_batch), + url(r'^asset/add/$', asset_add, name='asset_add'), + url(r"^asset/add_batch/$", asset_add_batch, name='asset_add_batch'), + url(r'^asset/list/$', asset_list, name='asset_list'), + url(r'^asset/del/$', asset_del, name='asset_del'), + url(r"^asset/detail/$", asset_detail, name='asset_detail'), + url(r'^asset/edit/$', asset_edit, name='asset_edit'), + url(r'^asset/edit_batch/$', asset_edit_batch, name='asset_edit_batch'), + url(r'^asset/update/$', asset_update, name='asset_update'), + url(r'^asset/update_batch/$', asset_update_batch, name='asset_update_batch'), + url(r'^asset/upload/$', asset_upload, name='asset_upload'), + url(r'^group/del/$', group_del, name='asset_group_del'), + url(r'^group/add/$', group_add, name='asset_group_add'), + url(r'^group/list/$', group_list, name='asset_group_list'), + url(r'^group/edit/$', group_edit, name='asset_group_edit'), + url(r'^idc/add/$', idc_add, name='idc_add'), + url(r'^idc/list/$', idc_list, name='idc_list'), + url(r'^idc/edit/$', idc_edit, name='idc_edit'), + url(r'^idc/del/$', idc_del, name='idc_del'), ) \ No newline at end of file diff --git a/jasset/views.py b/jasset/views.py index 22c72f8e5..a3eef0801 100644 --- a/jasset/views.py +++ b/jasset/views.py @@ -1,589 +1,519 @@ # coding:utf-8 -import ast - from django.db.models import Q -from django.template import RequestContext -from django.shortcuts import get_object_or_404 - -from jperm.models import Perm +from jasset.asset_api import * from jumpserver.api import * - -cryptor = PyCrypt(KEY) +from jumpserver.models import Setting +from jasset.forms import AssetForm, IdcForm +from jasset.models import Asset, IDC, AssetGroup, ASSET_TYPE, ASSET_STATUS +from jperm.perm_api import get_group_asset_perm, get_group_user_perm -class RaiseError(Exception): - pass - - -def my_render(template, data, request): - return render_to_response(template, data, context_instance=RequestContext(request)) - - -def get_host_groups(groups): - """ 获取主机所属的组类 """ - ret = [] - for group_id in groups: - group = BisGroup.objects.filter(id=group_id) - if group: - group = group[0] - ret.append(group) - group_all = get_object_or_404(BisGroup, name='ALL') - ret.append(group_all) - return ret - - -def get_host_depts(depts): - """ 获取主机所属的部门类 """ - ret = [] - for dept_id in depts: - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - ret.append(dept) - return ret - - -def db_host_insert(host_info, username='', password=''): - """ 添加主机时数据库操作函数 """ - ip, port, idc, jtype, group, dept, active, comment = host_info - idc = IDC.objects.filter(id=idc) - if idc: - idc = idc[0] - if jtype == 'M': - password = cryptor.encrypt(password) - a = Asset(ip=ip, port=port, - login_type=jtype, idc=idc, - is_active=int(active), - comment=comment, - username=username, - password=password) - else: - a = Asset(ip=ip, port=port, - login_type=jtype, idc=idc, - is_active=int(active), - comment=comment) - a.save() - - all_group = BisGroup.objects.get(name='ALL') - groups = get_host_groups(group) - groups.append(all_group) - - depts = get_host_depts(dept) - - a.bis_group = groups - a.dept = depts - a.save() - - -def db_host_update(host_info, username='', password=''): - """ 修改主机时数据库操作函数 """ - ip, port, idc, jtype, group, dept, active, comment, host = host_info - idc = IDC.objects.filter(id=idc) - if idc: - idc = idc[0] - groups = get_host_groups(group) - depts = get_host_depts(dept) - host.ip = ip - host.port = port - host.login_type = jtype - host.idc = idc - host.is_active = int(active) - host.comment = comment - - if jtype == 'M': - if password != host.password: - password = cryptor.encrypt(password) - host.password = password - host.username = username - host.password = password - host.save() - host.bis_group = groups - host.dept = depts - host.save() - - -def batch_host_edit(host_info, j_user='', j_password=''): - """ 批量修改主机函数 """ - j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment = host_info - groups, depts = [], [] - is_active = {u'是': '1', u'否': '2'} - login_types = {'LDAP': 'L', 'MAP': 'M'} - a = Asset.objects.get(id=j_id) - if '...' in j_group[0].split(): - groups = a.bis_group.all() - else: - for group in j_group[0].split(): - c = BisGroup.objects.get(name=group.strip()) - groups.append(c) - - if '...' in j_dept[0].split(): - depts = a.dept.all() - else: - for d in j_dept[0].split(): - p = DEPT.objects.get(name=d.strip()) - depts.append(p) - - j_type = login_types[j_type] - j_idc = IDC.objects.get(name=j_idc) - if j_type == 'M': - if a.password != j_password: - j_password = cryptor.decrypt(j_password) - a.ip = j_ip - a.port = j_port - a.login_type = j_type - a.idc = j_idc - a.is_active = j_active - a.comment = j_comment - a.username = j_user - a.password = j_password - else: - a.ip = j_ip - a.port = j_port - a.idc = j_idc - a.login_type = j_type - a.is_active = is_active[j_active] - a.comment = j_comment - a.save() - a.bis_group = groups - a.dept = depts - a.save() - - -def db_host_delete(request, host_id): - """ 删除主机操作 """ - if is_group_admin(request) and not validate(request, asset=[host_id]): - return httperror(request, '删除失败, 您无权删除!') - - asset = Asset.objects.filter(id=host_id) - if asset: - asset.delete() - else: - return httperror(request, '删除失败, 没有此主机!') - - -def db_idc_delete(request, idc_id): - """ 删除IDC操作 """ - if idc_id == 1: - return httperror(request, '删除失败, 默认IDC不能删除!') - - default_idc = IDC.objects.get(id=1) - - idc = IDC.objects.filter(id=idc_id) - if idc: - idc_class = idc[0] - idc_class.asset_set.update(idc=default_idc) - idc.delete() - else: - return httperror(request, '删除失败, 没有这个IDC!') - - -@require_admin -def host_add(request): - """ 添加主机 """ - header_title, path1, path2 = u'添加主机', u'资产管理', u'添加主机' - login_types = {'L': 'LDAP', 'M': 'MAP'} - eidc = IDC.objects.exclude(name='ALL') - if is_super_user(request): - edept = DEPT.objects.all() - egroup = BisGroup.objects.exclude(name='ALL') - elif is_group_admin(request): - dept = get_session_user_info(request)[5] - egroup = dept.bisgroup_set.all() +@require_role('admin') +def group_add(request): + """ + Group add view + 添加资产组 + """ + header_title, path1, path2 = u'添加资产组', u'资产管理', u'添加资产组' + asset_all = Asset.objects.all() if request.method == 'POST': - j_ip = request.POST.get('j_ip') - j_idc = request.POST.get('j_idc') - j_port = request.POST.get('j_port') - j_type = request.POST.get('j_type') - j_group = request.POST.getlist('j_group') - j_active = request.POST.get('j_active') - j_comment = request.POST.get('j_comment') + name = request.POST.get('name', '') + asset_select = request.POST.getlist('asset_select', []) + comment = request.POST.get('comment', '') - if is_super_user(request): - j_dept = request.POST.getlist('j_dept') - host_info = [j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment] - elif is_group_admin(request): - j_dept = request.POST.get('j_dept') - host_info = [j_ip, j_port, j_idc, j_type, j_group, [j_dept], j_active, j_comment] + try: + if not name: + emg = u'组名不能为空' + raise ServerError(emg) - if is_group_admin(request) and not validate(request, asset_group=j_group, edept=[j_dept]): - return httperror(request, u'添加失败,您无权操作!') + asset_group_test = get_object(AssetGroup, name=name) + if asset_group_test: + emg = u"该组名 %s 已存在" % name + raise ServerError(emg) + + except ServerError: + pass - if Asset.objects.filter(ip=str(j_ip)): - emg = u'该IP %s 已存在!' % j_ip - return my_render('jasset/host_add.html', locals(), request) - if j_type == 'M': - j_user = request.POST.get('j_user') - j_password = request.POST.get('j_password', '') - db_host_insert(host_info, j_user, j_password) else: - db_host_insert(host_info) - smg = u'主机 %s 添加成功' % j_ip + db_add_group(name=name, comment=comment, asset_select=asset_select) + smg = u"主机组 %s 添加成功" % name - return my_render('jasset/host_add.html', locals(), request) + return my_render('jasset/group_add.html', locals(), request) -@require_admin -def host_add_batch(request): - """ 批量添加主机 """ - header_title, path1, path2 = u'批量添加主机', u'资产管理', u'批量添加主机' - login_types = {'LDAP': 'L', 'MAP': 'M'} - active_types = {'激活': 1, '禁用': 0} - dept_id = get_user_dept(request) +@require_role('admin') +def group_edit(request): + """ + Group edit view + 编辑资产组 + """ + header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组' + group_id = request.GET.get('id', '') + group = get_object(AssetGroup, id=group_id) + + asset_all = Asset.objects.all() + asset_select = Asset.objects.filter(group=group) + asset_no_select = [a for a in asset_all if a not in asset_select] + if request.method == 'POST': - multi_hosts = request.POST.get('j_multi').split('\n') - for host in multi_hosts: - if host == '': - break - j_ip, j_port, j_type, j_idc, j_groups, j_depts, j_active, j_comment = host.split() - j_active = active_types[str(j_active)] - j_group = ast.literal_eval(j_groups) - j_dept = ast.literal_eval(j_depts) + name = request.POST.get('name', '') + asset_select = request.POST.getlist('asset_select', []) + comment = request.POST.get('comment', '') - if j_type not in ['LDAP', 'MAP']: - return httperror(request, u'没有%s这种登录方式!' %j_type) + try: + if not name: + emg = u'组名不能为空' + raise ServerError(emg) - j_type = login_types[j_type] - idc = IDC.objects.filter(name=j_idc) - if idc: - j_idc = idc[0].id - else: - return httperror(request, '添加失败, 没有%s这个IDC' % j_idc) + if group.name != name: + asset_group_test = get_object(AssetGroup, name=name) + if asset_group_test: + emg = u"该组名 %s 已存在" % name + raise ServerError(emg) - group_ids, dept_ids = [], [] - for group_name in j_group: - group = BisGroup.objects.filter(name=group_name) - if group: - group_id = group[0].id - else: - return httperror(request, '添加失败, 没有%s这个主机组' % group_name) - group_ids.append(group_id) + except ServerError: + pass - for dept_name in j_dept: - dept = DEPT.objects.filter(name=dept_name) - if dept: - dept_id = dept[0].id - else: - return httperror(request, '添加失败, 没有%s这个部门' % dept_name) - dept_ids.append(dept_id) + else: + group.asset_set.clear() + db_update_group(id=group_id, name=name, comment=comment, asset_select=asset_select) + smg = u"主机组 %s 添加成功" % name - if is_group_admin(request) and not validate(request, asset_group=group_ids, edept=dept_ids): - return httperror(request, '添加失败, 没有%s这个主机组' % group_name) + return HttpResponseRedirect(reverse('asset_group_list')) - if Asset.objects.filter(ip=str(j_ip)): - return httperror(request, '添加失败, 改IP%s已存在' % j_ip) - - host_info = [j_ip, j_port, j_idc, j_type, group_ids, dept_ids, j_active, j_comment] - db_host_insert(host_info) - - smg = u'批量添加添加成功' - return my_render('jasset/host_add_multi.html', locals(), request) - - return my_render('jasset/host_add_multi.html', locals(), request) + return my_render('jasset/group_edit.html', locals(), request) -@require_admin -def host_edit_batch(request): - """ 批量修改主机 """ - if request.method == 'POST': - len_table = request.POST.get('len_table') - for i in range(int(len_table)): - j_id = "editable[" + str(i) + "][j_id]" - j_ip = "editable[" + str(i) + "][j_ip]" - j_port = "editable[" + str(i) + "][j_port]" - j_dept = "editable[" + str(i) + "][j_dept]" - j_idc = "editable[" + str(i) + "][j_idc]" - j_type = "editable[" + str(i) + "][j_type]" - j_group = "editable[" + str(i) + "][j_group]" - j_active = "editable[" + str(i) + "][j_active]" - j_comment = "editable[" + str(i) + "][j_comment]" - - j_id = request.POST.get(j_id).strip() - j_ip = request.POST.get(j_ip).strip() - j_port = request.POST.get(j_port).strip() - j_dept = request.POST.getlist(j_dept) - j_idc = request.POST.get(j_idc).strip() - j_type = request.POST.get(j_type).strip() - j_group = request.POST.getlist(j_group) - j_active = request.POST.get(j_active).strip() - j_comment = request.POST.get(j_comment).strip() - - host_info = [j_id, j_ip, j_idc, j_port, j_type, j_group, j_dept, j_active, j_comment] - batch_host_edit(host_info) - - return HttpResponseRedirect('/jasset/host_list/') - - -@require_login -def host_edit_common_batch(request): - """ 普通用户批量修改主机别名 """ - u = get_session_user_info(request)[2] - if request.method == 'POST': - len_table = request.POST.get('len_table') - for i in range(int(len_table)): - j_id = "editable[" + str(i) + "][j_id]" - j_alias = "editable[" + str(i) + "][j_alias]" - j_id = request.POST.get(j_id, '').strip() - j_alias = request.POST.get(j_alias, '').strip() - a = Asset.objects.get(id=j_id) - asset_alias = AssetAlias.objects.filter(user=u, host=a) - if asset_alias: - asset_alias = asset_alias[0] - asset_alias.alias = j_alias - asset_alias.save() - else: - AssetAlias.objects.create(user=u, host=a, alias=j_alias) - return my_render('jasset/host_list_common.html', locals(), request) - - -@require_login -def host_list(request): - """ 列出主机 """ - header_title, path1, path2 = u'查看主机', u'资产管理', u'查看主机' +@require_role('admin') +def group_list(request): + """ + list asset group + 列出资产组 + """ + header_title, path1, path2 = u'查看资产组', u'资产管理', u'查看资产组' keyword = request.GET.get('keyword', '') - dept_id = get_session_user_info(request)[3] - dept = DEPT.objects.get(id=dept_id) - did = request.GET.get('did', '') - gid = request.GET.get('gid', '') - sid = request.GET.get('sid', '') - user_id = get_session_user_info(request)[0] + asset_group_list = AssetGroup.objects.all() + group_id = request.GET.get('id') + if group_id: + asset_group_list = asset_group_list.filter(id=group_id) + if keyword: + asset_group_list = asset_group_list.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)) - post_all = Asset.objects.all().order_by('ip') - post_keyword_all = Asset.objects.filter(Q(ip__contains=keyword) | - Q(idc__name__contains=keyword) | - Q(bis_group__name__contains=keyword) | - Q(comment__contains=keyword)).distinct().order_by('ip') - if did: - if is_common_user(request): - return httperror(request, u'您无权查看!') + asset_group_list, p, asset_groups, page_range, current_page, show_first, show_end = pages(asset_group_list, request) + return my_render('jasset/group_list.html', locals(), request) - if is_group_admin(request): - user, dept = get_session_user_dept(request) + +@require_role('admin') +def group_del(request): + """ + Group delete view + 删除主机组 + """ + group_ids = request.GET.get('id', '') + group_id_list = group_ids.split(',') + + for group_id in group_id_list: + AssetGroup.objects.filter(id=group_id).delete() + + return HttpResponse(u'删除成功') + + +@require_role('admin') +def asset_add(request): + """ + Asset add view + 添加资产 + """ + header_title, path1, path2 = u'添加资产', u'资产管理', u'添加资产' + asset_group_all = AssetGroup.objects.all() + af = AssetForm() + default_setting = get_object(Setting, name='default') + default_port = default_setting.field2 if default_setting else '' + if request.method == 'POST': + af_post = AssetForm(request.POST) + ip = request.POST.get('ip', '') + hostname = request.POST.get('hostname', '') + is_active = True if request.POST.get('is_active') == '1' else False + use_default_auth = request.POST.get('use_default_auth', '') + try: + if Asset.objects.filter(hostname=str(hostname)): + error = u'该主机名 %s 已存在!' % hostname + raise ServerError(error) + + except ServerError: + pass else: - dept = DEPT.objects.get(id=did) - posts = dept.asset_set.all() - return my_render('jasset/host_list_nop.html', locals(), request) + if af_post.is_valid(): + asset_save = af_post.save(commit=False) + if not use_default_auth: + password = request.POST.get('password', '') + password_encode = CRYPTOR.encrypt(password) + asset_save.password = password_encode + if not ip: + asset_save.ip = hostname + asset_save.is_active = True if is_active else False + asset_save.save() + af_post.save_m2m() - elif gid: - if is_common_user(request): - return httperror(request, u'您无权查看!') - - elif is_group_admin(request) and not validate(request, user_group=[gid]): - return httperror(request, u'您无权查看!') - - posts = [] - user_group = UserGroup.objects.filter(id=gid) - if user_group: - perms = Perm.objects.filter(user_group=user_group) - for perm in perms: - for post in perm.asset_group.asset_set.all(): - posts.append(post) - posts = list(set(posts)) - else: - return httperror(request, u'没有这个小组!') - return my_render('jasset/host_list_nop.html', locals(), request) - - elif sid: - if is_common_user(request): - return httperror(request, u'您无权查看!') - - elif is_group_admin(request) and not validate(request, user_group=[sid]): - return httperror(request, u'您无权查看!') - - posts, asset_groups = [], [] - user_group = UserGroup.objects.filter(id=int(sid)) - if user_group: - user_group = user_group[0] - for perm in user_group.sudoperm_set.all(): - asset_groups.extend(perm.asset_group.all()) - - for asset_group in asset_groups: - posts.extend(asset_group.asset_set.all()) - posts = list(set(posts)) - else: - return httperror(request, u'没有这个sudo授权!') - return my_render('jasset/host_list_nop.html', locals(), request) - - else: - if is_super_user(request): - if keyword: - posts = post_keyword_all + msg = u'主机 %s 添加成功' % hostname else: - posts = post_all - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return my_render('jasset/host_list.html', locals(), request) + esg = u'主机 %s 添加失败' % hostname - elif is_group_admin(request): - if keyword: - posts = post_keyword_all.filter(dept=dept) - else: - posts = post_all.filter(dept=dept) - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return my_render('jasset/host_list.html', locals(), request) - - elif is_common_user(request): - user_id, username = get_session_user_info(request)[0:2] - posts = user_perm_asset_api(username) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return my_render('jasset/host_list_common.html', locals(), request) + return my_render('jasset/asset_add.html', locals(), request) -@require_admin -def host_del(request, offset): - """ 删除主机 """ - if offset == 'multi': - len_list = request.POST.get("len_list") - for i in range(int(len_list)): - key = "id_list[" + str(i) + "]" - host_id = request.POST.get(key) - db_host_delete(request, host_id) - else: - db_host_delete(request, offset) - - return HttpResponseRedirect('/jasset/host_list/') +@require_role('admin') +def asset_add_batch(request): + header_title, path1, path2 = u'添加资产', u'资产管理', u'批量添加' + return my_render('jasset/asset_add_batch.html', locals(), request) -@require_super_user -def host_edit(request): - """ 修改主机 """ - header_title, path1, path2 = u'修改主机', u'资产管理', u'修改主机' - actives = {1: u'激活', 0: u'禁用'} - login_types = {'L': 'LDAP', 'M': 'MAP'} - eidc = IDC.objects.all() - egroup = BisGroup.objects.exclude(name='ALL') - edept = DEPT.objects.all() - host_id = request.GET.get('id', '') - post = Asset.objects.filter(id=int(host_id)) - if post: - post = post[0] - else: - return httperror(request, '没有此主机!') - - e_group = post.bis_group.all() - e_dept = post.dept.all() +@require_role('admin') +def asset_del(request): + """ + del a asset + 删除主机 + """ + asset_id = request.GET.get('id', '') + if asset_id: + Asset.objects.filter(id=asset_id).delete() if request.method == 'POST': - j_ip = request.POST.get('j_ip', '') - j_idc = request.POST.get('j_idc', '') - j_port = request.POST.get('j_port', '') - j_type = request.POST.get('j_type', '') - j_dept = request.POST.getlist('j_dept', '') - j_group = request.POST.getlist('j_group', '') - j_active = request.POST.get('j_active', '') - j_comment = request.POST.get('j_comment', '') + asset_batch = request.GET.get('arg', '') + asset_id_all = str(request.POST.get('asset_id_all', '')) - host_info = [j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment, post] - if j_type == 'M': - j_user = request.POST.get('j_user') - j_password = request.POST.get('j_password') - db_host_update(host_info, j_user, j_password) + if asset_batch: + for asset_id in asset_id_all.split(','): + asset = get_object(Asset, id=asset_id) + asset.delete() + + return HttpResponse(u'删除成功') + + +@require_role(role='super') +def asset_edit(request): + """ + edit a asset + 修改主机 + """ + header_title, path1, path2 = u'修改资产', u'资产管理', u'修改资产' + + asset_id = request.GET.get('id', '') + username = request.user.username + asset = get_object(Asset, id=asset_id) + if asset: + password_old = asset.password + # asset_old = copy_model_instance(asset) + af = AssetForm(instance=asset) + if request.method == 'POST': + af_post = AssetForm(request.POST, instance=asset) + ip = request.POST.get('ip', '') + hostname = request.POST.get('hostname', '') + password = request.POST.get('password', '') + is_active = True if request.POST.get('is_active') == '1' else False + use_default_auth = request.POST.get('use_default_auth', '') + try: + asset_test = get_object(Asset, hostname=hostname) + if asset_test and asset_id != unicode(asset_test.id): + emg = u'该主机名 %s 已存在!' % hostname + raise ServerError(emg) + except ServerError: + pass else: - db_host_update(host_info) + if af_post.is_valid(): + af_save = af_post.save(commit=False) + if use_default_auth: + af_save.username = '' + af_save.password = '' + af_save.port = None + else: + if password: + password_encode = CRYPTOR.encrypt(password) + af_save.password = password_encode + else: + af_save.password = password_old + af_save.is_active = True if is_active else False + af_save.save() + af_post.save_m2m() + # asset_new = get_object(Asset, id=asset_id) + # asset_diff_one(asset_old, asset_new) + info = asset_diff(af_post.__dict__.get('initial'), request.POST) + db_asset_alert(asset, username, info) - smg = u'主机 %s 修改成功' % j_ip - return HttpResponseRedirect('/jasset/host_detail/?id=%s' % host_id) + smg = u'主机 %s 修改成功' % ip + else: + emg = u'主机 %s 修改失败' % ip + return my_render('jasset/error.html', locals(), request) + return HttpResponseRedirect(reverse('asset_detail')+'?id=%s' % asset_id) - return my_render('jasset/host_edit.html', locals(), request) + return my_render('jasset/asset_edit.html', locals(), request) -@require_admin -def host_edit_adm(request): - """ 部门管理员修改主机 """ - header_title, path1, path2 = u'修改主机', u'资产管理', u'修改主机' - actives = {1: u'激活', 0: u'禁用'} - login_types = {'L': 'LDAP', 'M': 'MAP'} - eidc = IDC.objects.all() - dept = get_session_user_info(request)[5] - egroup = BisGroup.objects.exclude(name='ALL').filter(dept=dept) - host_id = request.GET.get('id', '') - post = Asset.objects.filter(id=int(host_id)) - if post: - post = post[0] +@require_role('user') +def asset_list(request): + """ + asset list view + """ + header_title, path1, path2 = u'查看资产', u'资产管理', u'查看资产' + username = request.user.username + user_perm = request.session['role_id'] + idc_all = IDC.objects.filter() + asset_group_all = AssetGroup.objects.all() + asset_types = ASSET_TYPE + asset_status = ASSET_STATUS + idc_name = request.GET.get('idc', '') + group_name = request.GET.get('group', '') + asset_type = request.GET.get('asset_type', '') + status = request.GET.get('status', '') + keyword = request.GET.get('keyword', '') + export = request.GET.get("export", False) + group_id = request.GET.get("group_id", '') + idc_id = request.GET.get("idc_id", '') + asset_id_all = request.GET.getlist("id", '') + + if group_id: + group = get_object(AssetGroup, id=group_id) + if group: + asset_find = Asset.objects.filter(group=group) + elif idc_id: + idc = get_object(IDC, id=idc_id) + if idc: + asset_find = Asset.objects.filter(idc=idc) else: - return httperror(request, '没有此主机!') + if user_perm != 0: + asset_find = Asset.objects.all() + else: + asset_id_all = [] + user = get_object(User, username=username) + asset_perm = get_group_user_perm(user) if user else {'asset': ''} + user_asset_perm = asset_perm['asset'].keys() + for asset in user_asset_perm: + asset_id_all.append(asset.id) + asset_find = Asset.objects.filter(pk__in=asset_id_all) + asset_group_all = list(asset_perm['asset_group']) - e_group = post.bis_group.all() + if idc_name: + asset_find = asset_find.filter(idc__name__contains=idc_name) + + if group_name: + asset_find = asset_find.filter(group__name__contains=group_name) + + if asset_type: + asset_find = asset_find.filter(asset_type__contains=asset_type) + + if status: + asset_find = asset_find.filter(status__contains=status) + + if keyword: + asset_find = asset_find.filter( + Q(hostname__contains=keyword) | + Q(other_ip__contains=keyword) | + Q(ip__contains=keyword) | + Q(remote_ip__contains=keyword) | + Q(comment__contains=keyword) | + Q(username__contains=keyword) | + Q(group__name__contains=keyword) | + Q(cpu__contains=keyword) | + Q(memory__contains=keyword) | + Q(disk__contains=keyword) | + Q(brand__contains=keyword) | + Q(cabinet__contains=keyword) | + Q(sn__contains=keyword) | + Q(system_type__contains=keyword) | + Q(system_version__contains=keyword)) + + if export: + if asset_id_all: + asset_find = [] + for asset_id in asset_id_all: + asset = get_object(Asset, id=asset_id) + if asset: + asset_find.append(asset) + s = write_excel(asset_find) + if s[0]: + file_name = s[1] + smg = u'excel文件已生成,请点击下载!' + return my_render('jasset/asset_excel_download.html', locals(), request) + assets_list, p, assets, page_range, current_page, show_first, show_end = pages(asset_find, request) + if user_perm != 0: + return my_render('jasset/asset_list.html', locals(), request) + else: + return my_render('jasset/asset_cu_list.html', locals(), request) + + +@require_role('admin') +def asset_edit_batch(request): + af = AssetForm() + name = request.user.username + asset_group_all = AssetGroup.objects.all() if request.method == 'POST': - j_ip = request.POST.get('j_ip') - j_idc = request.POST.get('j_idc') - j_port = request.POST.get('j_port') - j_type = request.POST.get('j_type') - j_dept = request.POST.getlist('j_dept') - j_group = request.POST.getlist('j_group') - j_active = request.POST.get('j_active') - j_comment = request.POST.get('j_comment') + env = request.POST.get('env', '') + idc_id = request.POST.get('idc', '') + port = request.POST.get('port', '') + use_default_auth = request.POST.get('use_default_auth', '') + username = request.POST.get('username', '') + password = request.POST.get('password', '') + group = request.POST.getlist('group', []) + cabinet = request.POST.get('cabinet', '') + comment = request.POST.get('comment', '') + asset_id_all = unicode(request.GET.get('asset_id_all', '')) + asset_id_all = asset_id_all.split(',') + for asset_id in asset_id_all: + alert_list = [] + asset = get_object(Asset, id=asset_id) + if asset: + if env: + if asset.env != env: + asset.env = env + alert_list.append([u'运行环境', asset.env, env]) + if idc_id: + idc = get_object(IDC, id=idc_id) + name_old = asset.idc.name if asset.idc else u'' + if idc and idc.name != name_old: + asset.idc = idc + alert_list.append([u'机房', name_old, idc.name]) + if port: + if unicode(asset.port) != port: + asset.port = port + alert_list.append([u'端口号', asset.port, port]) - host_info = [j_ip, j_port, j_idc, j_type, j_group, j_dept, j_active, j_comment] + if use_default_auth: + if use_default_auth == 'default': + asset.use_default_auth = 1 + asset.username = '' + asset.password = '' + alert_list.append([u'使用默认管理账号', asset.use_default_auth, u'默认']) + elif use_default_auth == 'user_passwd': + asset.use_default_auth = 0 + asset.username = username + password_encode = CRYPTOR.encrypt(password) + asset.password = password_encode + alert_list.append([u'使用默认管理账号', asset.use_default_auth, username]) + if group: + group_new, group_old, group_new_name, group_old_name = [], asset.group.all(), [], [] + for group_id in group: + g = get_object(AssetGroup, id=group_id) + if g: + group_new.append(g) + if not set(group_new) < set(group_old): + group_instance = list(set(group_new) | set(group_old)) + for g in group_instance: + group_new_name.append(g.name) + for g in group_old: + group_old_name.append(g.name) + asset.group = group_instance + alert_list.append([u'主机组', ','.join(group_old_name), ','.join(group_new_name)]) + if cabinet: + if asset.cabinet != cabinet: + asset.cabinet = cabinet + alert_list.append([u'机柜号', asset.cabinet, cabinet]) + if comment: + if asset.comment != comment: + asset.comment = comment + alert_list.append([u'备注', asset.comment, comment]) + asset.save() - if not validate(request, asset_group=j_group, edept=j_dept): - emg = u'修改失败,您无权操作!' - return my_render('jasset/host_edit.html', locals(), request) + if alert_list: + recode_name = unicode(name) + ' - ' + u'批量' + AssetRecord.objects.create(asset=asset, username=recode_name, content=alert_list) + return my_render('jasset/asset_update_status.html', locals(), request) - if j_type == 'M': - j_user = request.POST.get('j_user') - j_password = request.POST.get('j_password') - db_host_update(host_info, j_user, j_password, post) - else: - db_host_update(host_info, post) - - smg = u'主机 %s 修改成功' % j_ip - return HttpResponseRedirect('/jasset/host_detail/?id=%s' % host_id) - - return my_render('jasset/host_edit.html', locals(), request) + return my_render('jasset/asset_edit_batch.html', locals(), request) -@require_login -def host_detail(request): - """ 主机详情 """ +@require_role('admin') +def asset_detail(request): + """ + Asset detail view + """ header_title, path1, path2 = u'主机详细信息', u'资产管理', u'主机详情' - host_id = request.GET.get('id', '') - post = Asset.objects.filter(id=host_id) - if not post: - return httperror(request, '没有此主机!') - post = post[0] + asset_id = request.GET.get('id', '') + asset = get_object(Asset, id=asset_id) + perm_info = get_group_asset_perm(asset) + log = Log.objects.filter(host=asset.hostname) + if perm_info: + user_perm = [] + for perm, value in perm_info.items(): + if perm == 'user': + for user, role_dic in value.items(): + user_perm.append([user, role_dic.get('role', '')]) + elif perm == 'user_group' or perm == 'rule': + user_group_perm = value + print perm_info - if is_group_admin(request) and not validate(request, asset=[host_id]): - return httperror(request, '您无权查看!') + asset_record = AssetRecord.objects.filter(asset=asset).order_by('-alert_time') - elif is_common_user(request): - username = get_session_user_info(request)[1] - user_permed_hosts = user_perm_asset_api(username) - if post not in user_permed_hosts: - return httperror(request, '您无权查看!') + return my_render('jasset/asset_detail.html', locals(), request) + + +@require_role('admin') +def asset_update(request): + """ + Asset update host info via ansible view + """ + asset_id = request.GET.get('id', '') + asset = get_object(Asset, id=asset_id) + name = request.user.username + if not asset: + return HttpResponseRedirect(reverse('asset_detail')+'?id=%s' % asset_id) else: - log_all = Log.objects.filter(host=post.ip) - log, log_more = log_all[:10], log_all[10:] - user_permed_list = asset_perm_api(post) - - return my_render('jasset/host_detail.html', locals(), request) + asset_ansible_update([asset], name) + return HttpResponseRedirect(reverse('asset_detail')+'?id=%s' % asset_id) -@require_super_user +@require_role('admin') +def asset_update_batch(request): + if request.method == 'POST': + arg = request.GET.get('arg', '') + name = unicode(request.user.username) + ' - ' + u'自动更新' + if arg == 'all': + asset_list = Asset.objects.all() + else: + asset_list = [] + asset_id_all = unicode(request.POST.get('asset_id_all', '')) + asset_id_all = asset_id_all.split(',') + for asset_id in asset_id_all: + asset = get_object(Asset, id=asset_id) + if asset: + asset_list.append(asset) + asset_ansible_update(asset_list, name) + return HttpResponse(u'批量更新成功!') + return HttpResponse(u'批量更新成功!') + + +@require_role('admin') def idc_add(request): - """ 添加IDC """ + """ + IDC add view + """ header_title, path1, path2 = u'添加IDC', u'资产管理', u'添加IDC' if request.method == 'POST': - j_idc = request.POST.get('j_idc') - j_comment = request.POST.get('j_comment') - if IDC.objects.filter(name=j_idc): - emg = u'该IDC已存在!' - return my_render('jasset/idc_add.html', locals(), request) - else: - smg = u'IDC:%s添加成功' % j_idc - IDC.objects.create(name=j_idc, comment=j_comment) + idc_form = IdcForm(request.POST) + if idc_form.is_valid(): + idc_name = idc_form.cleaned_data['name'] + if IDC.objects.filter(name=idc_name): + emg = u'添加失败, 此IDC %s 已存在!' % idc_name + return my_render('jasset/idc_add.html', locals(), request) + else: + idc_form.save() + smg = u'IDC: %s添加成功' % idc_name + return HttpResponseRedirect(reverse('idc_list')) + else: + idc_form = IdcForm() return my_render('jasset/idc_add.html', locals(), request) -@require_admin +@require_role('admin') def idc_list(request): - """ 列出IDC """ + """ + IDC list view + """ header_title, path1, path2 = u'查看IDC', u'资产管理', u'查看IDC' - dept_id = get_user_dept(request) - dept = DEPT.objects.get(id=dept_id) + posts = IDC.objects.all() keyword = request.GET.get('keyword', '') if keyword: posts = IDC.objects.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)) @@ -593,338 +523,48 @@ def idc_list(request): return my_render('jasset/idc_list.html', locals(), request) -@require_super_user +@require_role('admin') def idc_edit(request): - """ 修改IDC """ + """ + IDC edit view + """ header_title, path1, path2 = u'编辑IDC', u'资产管理', u'编辑IDC' idc_id = request.GET.get('id', '') - idc = IDC.objects.filter(id=idc_id) - if int(idc_id) == 1: - return httperror(request, u'默认IDC不能编辑!') - if idc: - idc = idc[0] - default = IDC.objects.get(id=1).asset_set.all() - eposts = Asset.objects.filter(idc=idc).order_by('ip') - posts = [g for g in default if g not in eposts] - else: - return httperror(request, u'此IDC不存在') - + idc = get_object(IDC, id=idc_id) if request.method == 'POST': - idc_id = request.POST.get('id') - j_idc = request.POST.get('j_idc') - j_hosts = request.POST.getlist('j_hosts') - j_comment = request.POST.get('j_comment') - idc_default = request.POST.getlist('idc_default') - - idc = IDC.objects.filter(id=idc_id) - if idc: - idc.update(name=j_idc, comment=j_comment) - for host_id in j_hosts: - Asset.objects.filter(id=host_id).update(idc=idc[0]) - - i = IDC.objects.get(id=1) - for host in idc_default: - g = Asset.objects.filter(id=host).update(idc=i) - else: - return httperror(request, u'此IDC不存在') - - return HttpResponseRedirect('/jasset/idc_list/?id=%s' % idc_id) - - return my_render('jasset/idc_edit.html', locals(), request) - - -@require_admin -def idc_detail(request): - """ IDC详情 """ - header_title, path1, path2 = u'IDC详情', u'资产管理', u'IDC详情' - login_types = {'L': 'LDAP', 'M': 'MAP'} - idc_id = request.GET.get('id', '') - idc_filter = IDC.objects.filter(id=idc_id) - if idc_filter: - idc = idc_filter[0] + idc_form = IdcForm(request.POST, instance=idc) + if idc_form.is_valid(): + idc_form.save() + return HttpResponseRedirect(reverse('idc_list')) else: - return httperror(request, '没有此IDC') - dept = get_session_user_info(request)[5] - if is_super_user(request): - posts = Asset.objects.filter(idc=idc).order_by('ip') - elif is_group_admin(request): - posts = Asset.objects.filter(idc=idc, dept=dept).order_by('ip') - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - - return my_render('jasset/idc_detail.html', locals(), request) + idc_form = IdcForm(instance=idc) + return my_render('jasset/idc_edit.html', locals(), request) -@require_super_user +@require_role('admin') def idc_del(request): - """ 删除IDC """ - offset = request.GET.get('id', '') - if offset == 'multi': - len_list = request.POST.get("len_list") - for i in range(int(len_list)): - key = "id_list[" + str(i) + "]" - idc_id = request.POST.get(key) - db_idc_delete(request, int(idc_id)) - else: - db_idc_delete(request, int(offset)) - return HttpResponseRedirect('/jasset/idc_list/') + """ + IDC delete view + """ + idc_ids = request.GET.get('id', '') + idc_id_list = idc_ids.split(',') + + for idc_id in idc_id_list: + IDC.objects.filter(id=idc_id).delete() + + return HttpResponseRedirect(reverse('idc_list')) -@require_admin -def group_add(request): - """ 添加主机组 """ - header_title, path1, path2 = u'添加主机组', u'资产管理', u'添加主机组' - if is_super_user(request): - posts = Asset.objects.all() - edept = DEPT.objects.all() - elif is_group_admin(request): - dept_id = get_user_dept(request) - dept = DEPT.objects.get(id=dept_id) - posts = Asset.objects.filter(dept=dept) - edept = get_session_user_info(request)[5] - +@require_role('admin') +def asset_upload(request): + """ + Upload asset excel file view + """ if request.method == 'POST': - j_group = request.POST.get('j_group', '') - j_dept = request.POST.get('j_dept', '') - j_hosts = request.POST.getlist('j_hosts', '') - j_comment = request.POST.get('j_comment', '') - - try: - if is_group_admin(request) and not validate(request, asset=j_hosts, edept=[j_dept]): - emg = u'添加失败, 您无权操作!' - raise RaiseError - - elif BisGroup.objects.filter(name=j_group): - emg = u'添加失败, 该主机组已存在!' - raise RaiseError - - except RaiseError: - pass - + excel_file = request.FILES.get('file_name', '') + ret = excel_to_db(excel_file) + if ret: + smg = u'批量添加成功' else: - j_dept = DEPT.objects.filter(id=j_dept)[0] - group = BisGroup.objects.create(name=j_group, dept=j_dept, comment=j_comment) - for host in j_hosts: - g = Asset.objects.get(id=host) - group.asset_set.add(g) - smg = u'主机组 %s 添加成功' % j_group - - return my_render('jasset/group_add.html', locals(), request) - - -@require_admin -def group_list(request): - """ 列出主机组 """ - header_title, path1, path2 = u'查看主机组', u'资产管理', u'查看主机组' - dept_id = get_user_dept(request) - dept = DEPT.objects.get(id=dept_id) - keyword = request.GET.get('keyword', '') - gid = request.GET.get('gid') - sid = request.GET.get('sid') - if gid: - if is_common_user(request): - return httperror(request, u'您无权查看!') - - elif is_group_admin(request) and not validate(request, user_group=[gid]): - return httperror(request, u'您无权查看!') - - posts = [] - user_group = UserGroup.objects.filter(id=gid) - if user_group: - user_group = user_group[0] - perms = Perm.objects.filter(user_group=user_group) - for perm in perms: - posts.append(perm.asset_group) - - elif sid: - if is_common_user(request): - return httperror(request, u'您无权查看!') - - elif is_group_admin(request) and not validate(request, user_group=[sid]): - return httperror(request, u'您无权查看!') - - posts = [] - user_group = UserGroup.objects.filter(id=sid) - if user_group: - user_group = user_group[0] - for perm in user_group.sudoperm_set.all(): - posts.extend(perm.asset_group.all()) - posts = list(set(posts)) - else: - return httperror(request, u'没有此sudo授权!') - - else: - if is_super_user(request): - if keyword: - posts = BisGroup.objects.exclude(name='ALL').filter( - Q(name__contains=keyword) | Q(comment__contains=keyword)) - else: - posts = BisGroup.objects.exclude(name='ALL').order_by('id') - elif is_group_admin(request): - if keyword: - posts = BisGroup.objects.filter(Q(name__contains=keyword) | Q(comment__contains=keyword)).filter( - dept=dept) - else: - posts = BisGroup.objects.filter(dept=dept).order_by('id') - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return my_render('jasset/group_list.html', locals(), request) - - -@require_admin -def group_edit(request): - """ 修改主机组 """ - header_title, path1, path2 = u'编辑主机组', u'资产管理', u'编辑主机组' - group_id = request.GET.get('id', '') - group = BisGroup.objects.filter(id=group_id) - if group: - group = group[0] - else: - httperror(request, u'没有这个主机组!') - - host_all = Asset.objects.all() - dept_id = get_session_user_info(request)[3] - eposts = Asset.objects.filter(bis_group=group) - - if is_group_admin(request) and not validate(request, asset_group=[group_id]): - return httperror(request, '编辑失败, 您无权操作!') - dept = DEPT.objects.filter(id=group.dept.id) - if dept: - dept = dept[0] - else: - return httperror(request, u'没有这个部门!') - - all_dept = dept.asset_set.all() - posts = [g for g in all_dept if g not in eposts] - - if request.method == 'POST': - j_group = request.POST.get('j_group', '') - j_hosts = request.POST.getlist('j_hosts', '') - j_dept = request.POST.get('j_dept', '') - j_comment = request.POST.get('j_comment', '') - - j_dept = DEPT.objects.filter(id=int(j_dept)) - j_dept = j_dept[0] - - group.asset_set.clear() - for host in j_hosts: - g = Asset.objects.get(id=host) - group.asset_set.add(g) - BisGroup.objects.filter(id=group_id).update(name=j_group, dept=j_dept, comment=j_comment) - smg = u'主机组%s修改成功' % j_group - return HttpResponseRedirect('/jasset/group_list') - - return my_render('jasset/group_edit.html', locals(), request) - - -@require_admin -def group_detail(request): - """ 主机组详情 """ - header_title, path1, path2 = u'主机组详情', u'资产管理', u'主机组详情' - login_types = {'L': 'LDAP', 'M': 'MAP'} - dept = get_session_user_info(request)[5] - group_id = request.GET.get('id', '') - group = BisGroup.objects.get(id=group_id) - if is_super_user(request): - posts = Asset.objects.filter(bis_group=group).order_by('ip') - - elif is_group_admin(request): - if not validate(request, asset_group=[group_id]): - return httperror(request, u'您无权查看!') - posts = Asset.objects.filter(bis_group=group).filter(dept=dept).order_by('ip') - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return my_render('jasset/group_detail.html', locals(), request) - - -@require_admin -def group_del_host(request): - """ 主机组中剔除主机, 并不删除真实主机 """ - if request.method == 'POST': - group_id = request.POST.get('group_id') - offset = request.GET.get('id', '') - group = BisGroup.objects.get(id=group_id) - if offset == 'group': - len_list = request.POST.get("len_list") - for i in range(int(len_list)): - key = "id_list[" + str(i) + "]" - jid = request.POST.get(key) - g = Asset.objects.get(id=jid) - group.asset_set.remove(g) - - else: - offset = request.GET.get('id', '') - group_id = request.GET.get('gid', '') - group = BisGroup.objects.get(id=group_id) - g = Asset.objects.get(id=offset) - group.asset_set.remove(g) - - return HttpResponseRedirect('/jasset/group_detail/?id=%s' % group.id) - - -@require_admin -def group_del(request): - """ 删除主机组 """ - offset = request.GET.get('id', '') - if offset == 'multi': - len_list = request.POST.get("len_list") - for i in range(int(len_list)): - key = "id_list[" + str(i) + "]" - gid = request.POST.get(key) - if is_group_admin(request) and not validate(request, asset_group=[gid]): - return httperror(request, '删除失败, 您无权删除!') - BisGroup.objects.filter(id=gid).delete() - else: - gid = int(offset) - if is_group_admin(request) and not validate(request, asset_group=[gid]): - return httperror(request, '删除失败, 您无权删除!') - BisGroup.objects.filter(id=gid).delete() - return HttpResponseRedirect('/jasset/group_list/') - - -@require_admin -def dept_host_ajax(request): - """ 添加主机组时, 部门联动主机异步 """ - dept_id = request.GET.get('id', '') - if dept_id not in ['1', '2']: - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - hosts = dept.asset_set.all() - else: - hosts = Asset.objects.all() - - return my_render('jasset/dept_host_ajax.html', locals(), request) - - -def show_all_ajax(request): - """ 批量修改主机时, 部门和组全部显示 """ - env = request.GET.get('env', '') - get_id = request.GET.get('id', '') - host = Asset.objects.filter(id=get_id) - if host: - host = host[0] - return my_render('jasset/show_all_ajax.html', locals(), request) - - -@require_login -def host_search(request): - """ 搜索主机 """ - keyword = request.GET.get('keyword') - login_types = {'L': 'LDAP', 'M': 'MAP'} - dept = get_session_user_info(request)[5] - post_all = Asset.objects.filter(Q(ip__contains=keyword) | - Q(idc__name__contains=keyword) | - Q(bis_group__name__contains=keyword) | - Q(comment__contains=keyword)).distinct().order_by('ip') - if is_super_user(request): - posts = post_all - - elif is_group_admin(request): - posts = post_all.filter(dept=dept) - - elif is_common_user(request): - user_id, username = get_session_user_info(request)[0:2] - post_perm = user_perm_asset_api(username) - posts = list(set(post_all) & set(post_perm)) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - - return my_render('jasset/host_search.html', locals(), request) \ No newline at end of file + emg = u'批量添加失败,请检查格式.' + return my_render('jasset/asset_add_batch.html', locals(), request) diff --git a/jlog/models.py b/jlog/models.py index 3d2a319df..c8ffd77a2 100644 --- a/jlog/models.py +++ b/jlog/models.py @@ -3,14 +3,13 @@ from django.db import models class Log(models.Model): user = models.CharField(max_length=20, null=True) - host = models.CharField(max_length=20, null=True) + host = models.CharField(max_length=200, null=True) remote_ip = models.CharField(max_length=100) - dept_name = models.CharField(max_length=20) + login_type = models.CharField(max_length=100) log_path = models.CharField(max_length=100) start_time = models.DateTimeField(null=True) - pid = models.IntegerField(max_length=10) + pid = models.IntegerField() is_finished = models.BooleanField(default=False) - log_finished = models.BooleanField(default=False) end_time = models.DateTimeField(null=True) def __unicode__(self): @@ -20,4 +19,31 @@ class Log(models.Model): class Alert(models.Model): msg = models.CharField(max_length=20) time = models.DateTimeField(null=True) - is_finished = models.BigIntegerField(default=False) \ No newline at end of file + is_finished = models.BigIntegerField(default=False) + + +class TtyLog(models.Model): + log = models.ForeignKey(Log) + datetime = models.DateTimeField(auto_now=True) + cmd = models.CharField(max_length=200) + + +class ExecLog(models.Model): + user = models.CharField(max_length=100) + host = models.TextField() + cmd = models.TextField() + remote_ip = models.CharField(max_length=100) + result = models.TextField(default='') + datetime = models.DateTimeField(auto_now=True) + + +class FileLog(models.Model): + user = models.CharField(max_length=100) + host = models.TextField() + filename = models.TextField() + type = models.CharField(max_length=20) + remote_ip = models.CharField(max_length=100) + result = models.TextField(default='') + datetime = models.DateTimeField(auto_now=True) + + diff --git a/jlog/urls.py b/jlog/urls.py index 0b6810d3c..0790c9307 100644 --- a/jlog/urls.py +++ b/jlog/urls.py @@ -3,9 +3,9 @@ from django.conf.urls import patterns, include, url from jlog.views import * urlpatterns = patterns('', - url(r'^$', log_list), - url(r'^log_list/(\w+)/$', log_list), - url(r'^log_kill/', log_kill), - url(r'^history/$', log_history), - url(r'^search/$', log_search), -) \ No newline at end of file + url(r'^list/(\w+)/$', log_list, name='log_list'), + url(r'^detail/(\w+)/$', log_detail, name='log_detail'), + url(r'^history/$', log_history, name='log_history'), + url(r'^log_kill/', log_kill, name='log_kill'), + url(r'^record/$', log_record, name='log_record'), + ) \ No newline at end of file diff --git a/jlog/views.py b/jlog/views.py index 0eb74f815..b8cc089d5 100644 --- a/jlog/views.py +++ b/jlog/views.py @@ -4,78 +4,93 @@ from django.template import RequestContext from django.shortcuts import render_to_response from jumpserver.api import * -from jasset.views import httperror +from jperm.perm_api import user_have_perm from django.http import HttpResponseNotFound +from jlog.log_api import renderTemplate -CONF = ConfigParser() -CONF.read('%s/jumpserver.conf' % BASE_DIR) +from jlog.models import Log, ExecLog, FileLog +from jumpserver.settings import WEB_SOCKET_HOST -def get_user_info(request, offset): - """ 获取用户信息及环境 """ - env_dic = {'online': 0, 'offline': 1} - env = env_dic[offset] - keyword = request.GET.get('keyword', '') - user_info = get_session_user_info(request) - user_id, username = user_info[0:2] - dept_id, dept_name = user_info[3:5] - ret = [request, keyword, env, username, dept_name] - - return ret - - -def get_user_log(ret_list): - """ 获取不同类型用户日志记录 """ - request, keyword, env, username, dept_name = ret_list - post_all = Log.objects.filter(is_finished=env).order_by('-start_time') - post_keyword_all = Log.objects.filter(Q(user__contains=keyword) | - Q(host__contains=keyword)) \ - .filter(is_finished=env).order_by('-start_time') - - if is_super_user(request): - if keyword: - posts = post_keyword_all - else: - posts = post_all - - elif is_group_admin(request): - if keyword: - posts = post_keyword_all.filter(dept_name=dept_name) - else: - posts = post_all.filter(dept_name=dept_name) - - elif is_common_user(request): - if keyword: - posts = post_keyword_all.filter(user=username) - else: - posts = post_all.filter(user=username) - - return posts - - -@require_login +@require_role('admin') def log_list(request, offset): """ 显示日志 """ - header_title, path1, path2 = u'查看日志', u'查看日志', u'在线用户' - keyword = request.GET.get('keyword', '') - web_socket_host = CONF.get('websocket', 'web_socket_host') - posts = get_user_log(get_user_info(request, offset)) + header_title, path1 = u'审计', u'操作审计' + date_seven_day = request.GET.get('start', '') + date_now_str = request.GET.get('end', '') + username_list = request.GET.getlist('username', []) + host_list = request.GET.getlist('host', []) + cmd = request.GET.get('cmd', '') + + if offset == 'online': + keyword = request.GET.get('keyword', '') + posts = Log.objects.filter(is_finished=False).order_by('-start_time') + if keyword: + posts = posts.filter(Q(user__icontains=keyword) | Q(host__icontains=keyword) | + Q(login_type_icontains=keyword)) + + elif offset == 'exec': + posts = ExecLog.objects.all().order_by('-id') + keyword = request.GET.get('keyword', '') + if keyword: + posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(cmd__icontains=keyword)) + elif offset == 'file': + posts = FileLog.objects.all().order_by('-id') + keyword = request.GET.get('keyword', '') + if keyword: + posts = posts.filter(Q(user__icontains=keyword)|Q(host__icontains=keyword)|Q(filename__icontains=keyword)) + else: + posts = Log.objects.filter(is_finished=True).order_by('-start_time') + username_all = set([log.user for log in Log.objects.all()]) + ip_all = set([log.host for log in Log.objects.all()]) + + if date_seven_day and date_now_str: + datetime_start = datetime.datetime.strptime(date_seven_day + ' 00:00:01', '%m/%d/%Y %H:%M:%S') + datetime_end = datetime.datetime.strptime(date_now_str + ' 23:59:59', '%m/%d/%Y %H:%M:%S') + posts = posts.filter(start_time__gte=datetime_start).filter(start_time__lte=datetime_end) + + if username_list: + posts = posts.filter(user__in=username_list) + + if host_list: + posts = posts.filter(host__in=host_list) + + if cmd: + log_id_list = set([log.log_id for log in TtyLog.objects.filter(cmd__contains=cmd)]) + posts = posts.filter(id__in=log_id_list) + + if not date_seven_day: + date_now = datetime.datetime.now() + date_now_str = date_now.strftime('%m/%d/%Y') + date_seven_day = (date_now + datetime.timedelta(days=-7)).strftime('%m/%d/%Y') + contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) + web_monitor_uri = 'ws://%s/monitor' % WEB_SOCKET_HOST + web_kill_uri = 'http://%s/kill' % WEB_SOCKET_HOST + session_id = request.session.session_key return render_to_response('jlog/log_%s.html' % offset, locals(), context_instance=RequestContext(request)) -@require_admin +@require_role('admin') +def log_detail(request): + return my_render('jlog/exec_detail.html', locals(), request) + + +@require_role('admin') def log_kill(request): """ 杀掉connect进程 """ pid = request.GET.get('id', '') log = Log.objects.filter(pid=pid) if log: log = log[0] +<<<<<<< HEAD dept_name = log.dept_name deptname = get_session_user_info(request)[4] if is_group_admin(request) and dept_name != deptname: return httperror(request, u'Kill失败, 您无权操作!') +======= +>>>>>>> dev try: os.kill(int(pid), 9) except OSError: @@ -86,35 +101,56 @@ def log_kill(request): return HttpResponseNotFound(u'没有此进程!') -@require_login +@require_role('admin') def log_history(request): """ 命令历史记录 """ + log_id = request.GET.get('id', 0) + log = Log.objects.filter(id=log_id) + if log: + log = log[0] + tty_logs = log.ttylog_set.all() + + if tty_logs: + content = '' + for tty_log in tty_logs: + content += '%s: %s\n' % (tty_log.datetime.strftime('%Y-%m-%d %H:%M:%S'), tty_log.cmd) + return HttpResponse(content) + + return HttpResponse('无日志记录!') + + +@require_role('admin') +def log_record(request): log_id = request.GET.get('id', 0) log = Log.objects.filter(id=int(log_id)) if log: log = log[0] - dept_name = log.dept_name - deptname = get_session_user_info(request)[4] - if is_group_admin(request) and dept_name != deptname: - return httperror(request, '查看失败, 您无权查看!') - - elif is_common_user(request): - return httperror(request, '查看失败, 您无权查看!') - - log_his = "%s.his" % log.log_path - if os.path.isfile(log_his): - f = open(log_his) - content = f.read() + log_file = log.log_path + '.log' + log_time = log.log_path + '.time' + if os.path.isfile(log_file) and os.path.isfile(log_time): + content = renderTemplate(log_file, log_time) return HttpResponse(content) else: - return httperror(request, '无日志记录, 请查看日志处理脚本是否开启!') + return HttpResponse('无日志记录!') -@require_login -def log_search(request): - """ 日志搜索 """ - offset = request.GET.get('env', '') - keyword = request.GET.get('keyword', '') - posts = get_user_log(get_user_info(request, offset)) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return render_to_response('jlog/log_search.html', locals(), context_instance=RequestContext(request)) +@require_role('admin') +def log_detail(request, offset): + log_id = request.GET.get('id') + if offset == 'exec': + log = get_object(ExecLog, id=log_id) + assets_hostname = log.host.split(' ') + try: + result = eval(str(log.result)) + except (SyntaxError, NameError): + result = {} + return my_render('jlog/exec_detail.html', locals(), request) + elif offset == 'file': + log = get_object(FileLog, id=log_id) + assets_hostname = log.host.split(' ') + file_list = log.filename.split(' ') + try: + result = eval(str(log.result)) + except (SyntaxError, NameError): + result = {} + return my_render('jlog/file_detail.html', locals(), request) diff --git a/jperm/models.py b/jperm/models.py index c29cb8e54..425d01410 100644 --- a/jperm/models.py +++ b/jperm/models.py @@ -1,54 +1,60 @@ import datetime -from uuidfield import UUIDField - from django.db import models -from juser.models import UserGroup, DEPT -from jasset.models import Asset, BisGroup +from jasset.models import Asset, AssetGroup +from juser.models import User, UserGroup -class Perm(models.Model): - user_group = models.ForeignKey(UserGroup) - asset_group = models.ForeignKey(BisGroup) - - def __unicode__(self): - return '%s_%s' % (self.user_group.name, self.asset_group.name) +class PermLog(models.Model): + datetime = models.DateTimeField(auto_now_add=True) + action = models.CharField(max_length=100, null=True, blank=True, default='') + results = models.CharField(max_length=1000, null=True, blank=True, default='') + is_success = models.BooleanField(default=False) + is_finish = models.BooleanField(default=False) -class CmdGroup(models.Model): - name = models.CharField(max_length=50, unique=True) - cmd = models.CharField(max_length=999) - dept = models.ForeignKey(DEPT) - comment = models.CharField(blank=True, null=True, max_length=50) +class PermSudo(models.Model): + name = models.CharField(max_length=100, unique=True) + date_added = models.DateTimeField(auto_now=True) + commands = models.TextField() + comment = models.CharField(max_length=100, null=True, blank=True, default='') def __unicode__(self): return self.name -class SudoPerm(models.Model): - user_group = models.ForeignKey(UserGroup) - user_runas = models.CharField(max_length=100) - asset_group = models.ManyToManyField(BisGroup) - cmd_group = models.ManyToManyField(CmdGroup) - comment = models.CharField(max_length=30, null=True, blank=True) +class PermRole(models.Model): + name = models.CharField(max_length=100, unique=True) + comment = models.CharField(max_length=100, null=True, blank=True, default='') + password = models.CharField(max_length=100) + key_path = models.CharField(max_length=100) + date_added = models.DateTimeField(auto_now=True) + sudo = models.ManyToManyField(PermSudo, related_name='perm_role') def __unicode__(self): - return self.user_group.name + return self.name -class Apply(models.Model): - uuid = UUIDField(auto=True) - applyer = models.CharField(max_length=20) - admin = models.CharField(max_length=20) - approver = models.CharField(max_length=20) - dept = models.CharField(max_length=20) - bisgroup = models.CharField(max_length=500) - asset = models.CharField(max_length=500) - comment = models.TextField(blank=True, null=True) - status = models.IntegerField(max_length=2) - date_add = models.DateTimeField(null=True) - date_end = models.DateTimeField(null=True) - read = models.IntegerField(max_length=2) +class PermRule(models.Model): + date_added = models.DateTimeField(auto_now=True) + name = models.CharField(max_length=100, unique=True) + comment = models.CharField(max_length=100) + asset = models.ManyToManyField(Asset, related_name='perm_rule') + asset_group = models.ManyToManyField(AssetGroup, related_name='perm_rule') + user = models.ManyToManyField(User, related_name='perm_rule') + user_group = models.ManyToManyField(UserGroup, related_name='perm_rule') + role = models.ManyToManyField(PermRole, related_name='perm_rule') def __unicode__(self): - return self.applyer + return self.name + + +class PermPush(models.Model): + asset = models.ForeignKey(Asset, related_name='perm_push') + role = models.ForeignKey(PermRole, related_name='perm_push') + is_public_key = models.BooleanField(default=False) + is_password = models.BooleanField(default=False) + success = models.BooleanField(default=False) + result = models.TextField(default='') + date_added = models.DateTimeField(auto_now=True) + diff --git a/jperm/urls.py b/jperm/urls.py index 41a7b26ed..5fd3320a6 100644 --- a/jperm/urls.py +++ b/jperm/urls.py @@ -2,32 +2,21 @@ from django.conf.urls import patterns, include, url from jperm.views import * urlpatterns = patterns('jperm.views', - # Examples: - # url(r'^$', 'jumpserver.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - - (r'^perm_edit/$', view_splitter, {'su': perm_edit, 'adm': perm_edit_adm}), - (r'^dept_perm_edit/$', 'dept_perm_edit'), - (r'^perm_list/$', view_splitter, {'su': perm_list, 'adm': perm_list_adm}), - (r'^dept_perm_list/$', 'dept_perm_list'), - (r'^perm_user_detail/$', 'perm_user_detail'), - (r'^perm_detail/$', 'perm_detail'), - (r'^perm_del/$', 'perm_del'), - (r'^perm_asset_detail/$', 'perm_asset_detail'), - (r'^sudo_list/$', view_splitter, {'su': sudo_list, 'adm': sudo_list_adm}), - (r'^sudo_del/$', 'sudo_del'), - (r'^sudo_edit/$', view_splitter, {'su': sudo_edit, 'adm': sudo_edit_adm}), - (r'^sudo_refresh/$', 'sudo_refresh'), - (r'^sudo_detail/$', 'sudo_detail'), - (r'^cmd_add/$', view_splitter, {'su': cmd_add, 'adm': cmd_add_adm}), - (r'^cmd_list/$', 'cmd_list'), - (r'^cmd_del/$', 'cmd_del'), - (r'^cmd_edit/$', 'cmd_edit'), - (r'^cmd_detail/$', 'cmd_detail'), - (r'^apply/$', 'perm_apply'), - (r'^apply_show/(\w+)/$', 'perm_apply_log'), - (r'^apply_exec/$', 'perm_apply_exec'), - (r'^apply_info/$', 'perm_apply_info'), - (r'^apply_del/$', 'perm_apply_del'), - (r'^apply_search/$', 'perm_apply_search'), -) + url(r'^rule/list/$', perm_rule_list, name='rule_list'), + url(r'^rule/add/$', perm_rule_add, name='rule_add'), + url(r'^rule/detail/$', perm_rule_detail, name='rule_detail'), + url(r'^rule/edit/$', perm_rule_edit, name='rule_edit'), + url(r'^rule/del/$', perm_rule_delete, name='rule_del'), + url(r'^role/list/$', perm_role_list, name='role_list'), + url(r'^role/add/$', perm_role_add, name='role_add'), + url(r'^role/del/$', perm_role_delete, name='role_del'), + url(r'^role/detail/$', perm_role_detail, name='role_detail'), + url(r'^role/edit/$', perm_role_edit, name='role_edit'), + url(r'^role/push/$', perm_role_push, name='role_push'), + url(r'^role/recycle/$', perm_role_recycle, name='role_recycle'), + url(r'^role/get/$', perm_role_get, name='role_get'), + url(r'^sudo/list/$', perm_sudo_list, name='sudo_list'), + url(r'^sudo/add/$', perm_sudo_add, name='sudo_add'), + url(r'^sudo/del/$', perm_sudo_delete, name='sudo_del'), + url(r'^sudo/edit/$', perm_sudo_edit, name='sudo_edit'), + ) diff --git a/jperm/views.py b/jperm/views.py index 4f38f09b9..e42faec06 100644 --- a/jperm/views.py +++ b/jperm/views.py @@ -1,184 +1,188 @@ -# coding: utf-8 -import sys +# -*- coding: utf-8 -*- -reload(sys) -sys.setdefaultencoding('utf8') - -from django.shortcuts import render_to_response -from django.template import RequestContext -from jperm.models import Perm, SudoPerm, CmdGroup, Apply from django.db.models import Q -from jumpserver.api import * +from paramiko import SSHException +from jperm.perm_api import * + +from juser.models import User, UserGroup +from jasset.models import Asset, AssetGroup +from jperm.models import PermRole, PermRule, PermSudo, PermPush +from jumpserver.models import Setting + +from jperm.utils import gen_keys +from jperm.ansible_api import MyTask +from jperm.perm_api import get_role_info, get_role_push_host +from jumpserver.api import my_render, get_object, CRYPTOR + +# 设置PERM APP Log +from jumpserver.settings import LOG_LEVEL +logger = set_log(LOG_LEVEL, filename='jumpserver_perm.log') -def asset_cmd_groups_get(asset_groups_select='', cmd_groups_select=''): - asset_groups_select_list = [] - cmd_groups_select_list = [] - - for asset_group_id in asset_groups_select: - asset_groups_select_list.extend(BisGroup.objects.filter(id=asset_group_id)) - - for cmd_group_id in cmd_groups_select: - cmd_groups_select_list.extend(CmdGroup.objects.filter(id=cmd_group_id)) - - return asset_groups_select_list, cmd_groups_select_list - - -@require_admin -def perm_add(request): - header_title, path1, path2 = u'主机授权添加', u'授权管理', u'授权添加' - - if request.method == 'GET': - user_groups = UserGroup.objects.filter(id__gt=2) - asset_groups = BisGroup.objects.all() - - else: - name = request.POST.get('name', '') - user_groups_select = request.POST.getlist('user_groups_select') - asset_groups_select = request.POST.getlist('asset_groups_select') - comment = request.POST.get('comment', '') - - user_groups, asset_groups = user_asset_cmd_groups_get(user_groups_select, asset_groups_select, '')[0:2] - - perm = Perm(name=name, comment=comment) - perm.save() - - perm.user_group = user_groups - perm.asset_group = asset_groups - msg = '添加成功' - return render_to_response('jperm/perm_add.html', locals(), context_instance=RequestContext(request)) - - -def dept_add_asset(dept_id, asset_list): - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - new_perm_asset = [] - for asset_id in asset_list: - asset = Asset.objects.filter(id=asset_id) - new_perm_asset.extend(asset) - - dept.asset_set.clear() - dept.asset_set = new_perm_asset - - -@require_super_user -def dept_perm_edit(request): - header_title, path1, path2 = u'部门授权添加', u'授权管理', u'部门授权添加' - if request.method == 'GET': - dept_id = request.GET.get('id', '') - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - asset_all = Asset.objects.all() - asset_select = dept.asset_set.all() - assets = [asset for asset in asset_all if asset not in asset_select] - else: - dept_id = request.POST.get('dept_id') - asset_select = request.POST.getlist('asset_select') - dept_add_asset(dept_id, asset_select) - return HttpResponseRedirect('/jperm/dept_perm_list/') - return render_to_response('jperm/dept_perm_edit.html', locals(), context_instance=RequestContext(request)) - - -@require_super_user -def perm_list(request): - header_title, path1, path2 = u'小组授权', u'授权管理', u'授权详情' +@require_role('admin') +def perm_rule_list(request): + """ + list rule page + 授权规则列表 + """ + # 渲染数据 + header_title, path1, path2 = "授权规则", "规则管理", "查看规则" + # 获取所有规则 + rules_list = PermRule.objects.all() + rule_id = request.GET.get('id') + # TODO: 搜索和分页 keyword = request.GET.get('search', '') - uid = request.GET.get('uid', '') - agid = request.GET.get('agid', '') + if rule_id: + rules_list = rules_list.filter(id=rule_id) + if keyword: - contact_list = UserGroup.objects.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) - else: - contact_list = UserGroup.objects.all().order_by('name') + rules_list = rules_list.filter(Q(name=keyword)) - if uid: - user = User.objects.filter(id=uid) - print user - if user: - user = user[0] - contact_list = contact_list.filter(user=user) + rules_list, p, rules, page_range, current_page, show_first, show_end = pages(rules_list, request) - if agid: - contact_list_confirm = [] - asset_group = BisGroup.objects.filter(id=agid) - if asset_group: - asset_group = asset_group[0] - for user_group in contact_list: - if asset_group in user_group_perm_asset_group_api(user_group): - contact_list_confirm.append(user_group) - contact_list = contact_list_confirm - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('jperm/perm_list.html', locals(), context_instance=RequestContext(request)) + return my_render('jperm/perm_rule_list.html', locals(), request) -@require_admin -def perm_list_adm(request): - header_title, path1, path2 = u'小组授权', u'授权管理', u'授权详情' - keyword = request.GET.get('search', '') - uid = request.GET.get('uid', '') - agid = request.GET.get('agid', '') - user, dept = get_session_user_dept(request) - contact_list = dept.usergroup_set.all().order_by('name') - if keyword: - contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) +@require_role('admin') +def perm_rule_detail(request): + """ + rule detail page + 授权详情 + """ + # 渲染数据 + header_title, path1, path2 = "授权规则", "规则管理", "规则详情" - if uid: - user = User.objects.filter(id=uid) - print user - if user: - user = user[0] - contact_list = contact_list.filter(user=user) + # 根据rule_id 取得rule对象 + try: + if request.method == "GET": + rule_id = request.GET.get("id") + if not rule_id: + raise ServerError("Rule Detail - no rule id get") + rule_obj = PermRule.objects.get(id=rule_id) + user_obj = rule_obj.user.all() + user_group_obj = rule_obj.user_group.all() + asset_obj = rule_obj.asset.all() + asset_group_obj = rule_obj.asset_group.all() + roles_name = [role.name for role in rule_obj.role.all()] - if agid: - contact_list_confirm = [] - asset_group = BisGroup.objects.filter(id=agid) - if asset_group: - asset_group = asset_group[0] - for user_group in contact_list: - if asset_group in user_group_perm_asset_group_api(user_group): - contact_list_confirm.append(user_group) - contact_list = contact_list_confirm + # 渲染数据 + roles_name = ','.join(roles_name) + rule = rule_obj + users = user_obj + user_groups = user_group_obj + assets = asset_obj + asset_groups = asset_group_obj + except ServerError, e: + logger.warning(e) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('jperm/perm_list.html', locals(), context_instance=RequestContext(request)) + return my_render('jperm/perm_rule_detail.html', locals(), request) -@require_super_user -def dept_perm_list(request): - header_title, path1, path2 = '查看部门', '授权管理', '部门授权' - keyword = request.GET.get('search') - if keyword: - contact_list = DEPT.objects.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)).order_by('name') - else: - contact_list = DEPT.objects.filter(id__gt=2) +def perm_rule_add(request): + """ + add rule page + 添加授权 + """ + # 渲染数据 + header_title, path1, path2 = "授权规则", "规则管理", "添加规则" - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) + # 渲染数据, 获取所有 用户,用户组,资产,资产组,用户角色, 用于添加授权规则 + users = User.objects.all() + user_groups = UserGroup.objects.all() + assets = Asset.objects.all() + asset_groups = AssetGroup.objects.all() + roles = PermRole.objects.all() - return render_to_response('jperm/dept_perm_list.html', locals(), context_instance=RequestContext(request)) + if request.method == 'POST': + # 获取用户选择的 用户,用户组,资产,资产组,用户角色 + users_select = request.POST.getlist('user', []) # 需要授权用户 + user_groups_select = request.POST.getlist('user_group', []) # 需要授权用户组 + assets_select = request.POST.getlist('asset', []) # 需要授权资产 + asset_groups_select = request.POST.getlist('asset_group', []) # 需要授权资产组 + roles_select = request.POST.getlist('role', []) # 需要授权角色 + rule_name = request.POST.get('name') + rule_comment = request.POST.get('comment') + + try: + rule = get_object(PermRule, name=rule_name) + + if rule: + raise ServerError(u'授权规则 %s 已存在' % rule_name) + + if not rule_name or not roles_select: + raise ServerError(u'系统用户名称和规则名称不能为空') + + # 获取需要授权的主机列表 + assets_obj = [Asset.objects.get(id=asset_id) for asset_id in assets_select] + asset_groups_obj = [AssetGroup.objects.get(id=group_id) for group_id in asset_groups_select] + group_assets_obj = [] + for asset_group in asset_groups_obj: + group_assets_obj.extend(list(asset_group.asset_set.all())) + calc_assets = set(group_assets_obj) | set(assets_obj) # 授权资产和资产组包含的资产 + + # 获取需要授权的用户列表 + users_obj = [User.objects.get(id=user_id) for user_id in users_select] + user_groups_obj = [UserGroup.objects.get(id=group_id) for group_id in user_groups_select] + + # 获取授予的角色列表 + roles_obj = [PermRole.objects.get(id=role_id) for role_id in roles_select] + need_push_asset = set() + + for role in roles_obj: + asset_no_push = get_role_push_host(role=role)[1] # 获取某角色已经推送的资产 + need_push_asset.update(set(calc_assets) & set(asset_no_push)) + if need_push_asset: + raise ServerError(u'没有推送系统用户 %s 的主机 %s' + % (role.name, ','.join([asset.hostname for asset in need_push_asset]))) + + # 仅授权成功的,写回数据库(授权规则,用户,用户组,资产,资产组,用户角色) + rule = PermRule(name=rule_name, comment=rule_comment) + rule.save() + rule.user = users_obj + rule.user_group = user_groups_obj + rule.asset = assets_obj + rule.asset_group = asset_groups_obj + rule.role = roles_obj + rule.save() + + msg = u"添加授权规则:%s" % rule.name + return HttpResponseRedirect(reverse('rule_list')) + except ServerError, e: + error = e + return my_render('jperm/perm_rule_add.html', locals(), request) -def perm_group_update(user_group_id, asset_groups_id_list): - user_group = UserGroup.objects.filter(id=user_group_id) - if user_group: - user_group = user_group[0] - old_asset_group = [perm.asset_group for perm in user_group.perm_set.all()] - new_asset_group = [] +@require_role('admin') +def perm_rule_edit(request): + """ + edit rule page + """ + # 渲染数据 + header_title, path1, path2 = "授权规则", "规则管理", "添加规则" - for asset_group_id in asset_groups_id_list: - new_asset_group.extend(BisGroup.objects.filter(id=asset_group_id)) + # 根据rule_id 取得rule对象 + rule_id = request.GET.get("id") + rule = get_object(PermRule, id=rule_id) - del_asset_group = [asset_group for asset_group in old_asset_group if asset_group not in new_asset_group] - add_asset_group = [asset_group for asset_group in new_asset_group if asset_group not in old_asset_group] + # 渲染数据, 获取所选的rule对象 - for asset_group in del_asset_group: - Perm.objects.filter(user_group=user_group, asset_group=asset_group).delete() - - for asset_group in add_asset_group: - Perm(user_group=user_group, asset_group=asset_group).save() + users = User.objects.all() + user_groups = UserGroup.objects.all() + assets = Asset.objects.all() + asset_groups = AssetGroup.objects.all() + roles = PermRole.objects.all() + if request.method == 'POST' and rule_id: + # 获取用户选择的 用户,用户组,资产,资产组,用户角色 + rule_name = request.POST.get('name') + rule_comment = request.POST.get("comment") + users_select = request.POST.getlist('user', []) + user_groups_select = request.POST.getlist('user_group', []) + assets_select = request.POST.getlist('asset', []) + asset_groups_select = request.POST.getlist('asset_group', []) + roles_select = request.POST.getlist('role', []) +<<<<<<< HEAD @require_super_user def perm_edit(request): if request.method == 'GET': @@ -278,234 +282,114 @@ def sudo_ldap_add(user_group, user_runas, asset_groups_select, asset_all = False for asset_group in asset_groups_select: assets.extend(asset_group.asset_set.all()) +======= + try: + if not rule_name or not roles_select: + raise ServerError(u'系统用户和关联系统用户不能为空') - if user_group.name == 'ALL': - user_all = True - users = [] - else: - user_all = False - users = user_group.user_set.all() + assets_obj = [Asset.objects.get(id=asset_id) for asset_id in assets_select] + asset_groups_obj = [AssetGroup.objects.get(id=group_id) for group_id in asset_groups_select] + group_assets_obj = [] + for asset_group in asset_groups_obj: + group_assets_obj.extend(list(asset_group.asset_set.all())) + calc_assets = set(group_assets_obj) | set(assets_obj) # 授权资产和资产组包含的资产 - for cmd_group in cmd_groups_select: - cmds.extend(cmd_group.cmd.split(',')) + # 获取需要授权的用户列表 + users_obj = [User.objects.get(id=user_id) for user_id in users_select] + user_groups_obj = [UserGroup.objects.get(id=group_id) for group_id in user_groups_select] - if user_all: - users_name = ['ALL'] - else: - users_name = list(set([user.username for user in users])) + # 获取授予的角色列表 + roles_obj = [PermRole.objects.get(id=role_id) for role_id in roles_select] + need_push_asset = set() + for role in roles_obj: + asset_no_push = get_role_push_host(role=role)[1] # 获取某角色已经推送的资产 + need_push_asset.update(set(calc_assets) & set(asset_no_push)) + if need_push_asset: + raise ServerError(u'没有推送系统用户 %s 的主机 %s' + % (role.name, ','.join([asset.hostname for asset in need_push_asset]))) - if asset_all: - assets_ip = ['ALL'] - else: - assets_ip = list(set([asset.ip for asset in assets])) + # 仅授权成功的,写回数据库(授权规则,用户,用户组,资产,资产组,用户角色) + rule.user = users_obj + rule.user_group = user_groups_obj + rule.asset = assets_obj + rule.asset_group = asset_groups_obj + rule.role = roles_obj + rule.name = rule_name + rule.comment = rule_comment + rule.save() + msg = u"更新授权规则:%s成功" % rule.name +>>>>>>> dev - name = 'sudo%s' % user_group.id - sudo_dn = 'cn=%s,ou=Sudoers,%s' % (name, LDAP_BASE_DN) - sudo_attr = {'objectClass': ['top', 'sudoRole'], - 'cn': ['%s' % name], - 'sudoCommand': unicode2str(cmds), - 'sudoHost': unicode2str(assets_ip), - 'sudoOption': ['!authenticate'], - 'sudoRunAsUser': unicode2str(user_runas), - 'sudoUser': unicode2str(users_name)} - ldap_conn.delete(sudo_dn) - ldap_conn.add(sudo_dn, sudo_attr) + except ServerError, e: + error = e + + return my_render('jperm/perm_rule_edit.html', locals(), request) -def sudo_update(user_group, user_runas, asset_groups_select, cmd_groups_select, comment): - asset_groups_select_list, cmd_groups_select_list = \ - asset_cmd_groups_get(asset_groups_select, cmd_groups_select) - sudo_perm = user_group.sudoperm_set.all() - if sudo_perm: - sudo_perm.update(user_runas=user_runas, comment=comment) - sudo_perm = sudo_perm[0] - sudo_perm.asset_group = asset_groups_select_list - sudo_perm.cmd_group = cmd_groups_select_list - else: - sudo_perm = SudoPerm(user_group=user_group, user_runas=user_runas, comment=comment) - sudo_perm.save() - sudo_perm.asset_group = asset_groups_select_list - sudo_perm.cmd_group = cmd_groups_select_list - - sudo_ldap_add(user_group, user_runas, asset_groups_select_list, cmd_groups_select_list) - - -@require_super_user -def sudo_list(request): - header_title, path1, path2 = u'Sudo授权', u'权限管理', u'Sudo权限详情' - keyword = request.GET.get('search', '') - contact_list = UserGroup.objects.all().order_by('name') - if keyword: - contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('jperm/sudo_list.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def sudo_list_adm(request): - header_title, path1, path2 = u'Sudo授权', u'权限管理', u'Sudo权限详情' - keyword = request.GET.get('search', '') - user, dept = get_session_user_dept(request) - contact_list = dept.usergroup_set.all().order_by('name') - if keyword: - contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('jperm/sudo_list.html', locals(), context_instance=RequestContext(request)) - - -@require_super_user -def sudo_edit(request): - header_title, path1, path2 = u'Sudo授权', u'授权管理', u'Sudo授权' - - if request.method == 'GET': - user_group_id = request.GET.get('id', '0') - user_group = UserGroup.objects.filter(id=user_group_id) - asset_group_all = BisGroup.objects.filter() - cmd_group_all = CmdGroup.objects.all() - if user_group: - user_group = user_group[0] - sudo_perm = user_group.sudoperm_set.all() - if sudo_perm: - sudo_perm = sudo_perm[0] - asset_group_permed = sudo_perm.asset_group.all() - cmd_group_permed = sudo_perm.cmd_group.all() - user_runas = sudo_perm.user_runas - comment = sudo_perm.comment - else: - asset_group_permed = [] - cmd_group_permed = [] - - asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] - cmd_groups = [cmd_group for cmd_group in cmd_group_all if cmd_group not in cmd_group_permed] - - else: - user_group_id = request.POST.get('user_group_id', '') - users_runas = request.POST.get('runas') if request.POST.get('runas') else 'root' - asset_groups_select = request.POST.getlist('asset_groups_select') - cmd_groups_select = request.POST.getlist('cmd_groups_select') - comment = request.POST.get('comment', '') - user_group = UserGroup.objects.filter(id=user_group_id) - if user_group: - user_group = user_group[0] - if LDAP_ENABLE: - sudo_update(user_group, users_runas, asset_groups_select, cmd_groups_select, comment) - msg = '修改成功' - - return HttpResponseRedirect('/jperm/sudo_list/') - - return render_to_response('jperm/sudo_edit.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def sudo_edit_adm(request): - header_title, path1, path2 = u'Sudo授权', u'授权管理', u'Sudo授权' - user, dept = get_session_user_dept(request) - if request.method == 'GET': - user_group_id = request.GET.get('id', '0') - if not validate(request, user_group=[user_group_id]): - return render_to_response('/jperm/sudo_list/') - user_group = UserGroup.objects.filter(id=user_group_id) - asset_group_all = dept.bisgroup_set.all() - cmd_group_all = dept.cmdgroup_set.all() - if user_group: - user_group = user_group[0] - sudo_perm = user_group.sudoperm_set.all() - if sudo_perm: - sudo_perm = sudo_perm[0] - asset_group_permed = sudo_perm.asset_group.all() - cmd_group_permed = sudo_perm.cmd_group.all() - user_runas = sudo_perm.user_runas - comment = sudo_perm.comment - else: - asset_group_permed = [] - cmd_group_permed = [] - - asset_groups = [asset_group for asset_group in asset_group_all if asset_group not in asset_group_permed] - cmd_groups = [cmd_group for cmd_group in cmd_group_all if cmd_group not in cmd_group_permed] - - else: - user_group_id = request.POST.get('user_group_id', '') - users_runas = request.POST.get('runas', 'root') - asset_groups_select = request.POST.getlist('asset_groups_select') - cmd_groups_select = request.POST.getlist('cmd_groups_select') - comment = request.POST.get('comment', '') - user_group = UserGroup.objects.filter(id=user_group_id) - if not validate(request, user_group=[user_group_id], asset_group=asset_groups_select): - return render_to_response('/jperm/sudo_list/') - if user_group: - user_group = user_group[0] - if LDAP_ENABLE: - sudo_update(user_group, users_runas, asset_groups_select, cmd_groups_select, comment) - msg = '修改成功' - - return HttpResponseRedirect('/jperm/sudo_list/') - return render_to_response('jperm/sudo_edit.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def sudo_detail(request): - header_title, path1, path2 = u'Sudo授权详情', u'授权管理', u'授权详情' - user_group_id = request.GET.get('id') - user_group = UserGroup.objects.filter(id=user_group_id) - if user_group: - asset_groups = [] - cmd_groups = [] - user_group = user_group[0] - users = user_group.user_set.all() - group_user_num = len(users) - - for perm in user_group.sudoperm_set.all(): - asset_groups.extend(perm.asset_group.all()) - cmd_groups.extend(perm.cmd_group.all()) - - print asset_groups - return render_to_response('jperm/sudo_detail.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def sudo_refresh(request): - sudo_perm_all = SudoPerm.objects.all() - for sudo_perm in sudo_perm_all: - user_group = sudo_perm.user_group - user_runas = sudo_perm.user_runas - asset_groups_select = sudo_perm.asset_group.all() - cmd_groups_select = sudo_perm.cmd_group.all() - sudo_ldap_add(user_group, user_runas, asset_groups_select, cmd_groups_select) - return HttpResponse('刷新sudo授权成功') - - -@require_super_user -def cmd_add(request): - header_title, path1, path2 = u'sudo命令添加', u'授权管理', u'命令组添加' - dept_all = DEPT.objects.all() - +@require_role('admin') +def perm_rule_delete(request): + """ + use to delete rule + :param request: + :return: + """ if request.method == 'POST': - name = request.POST.get('name') - dept_id = request.POST.get('dept_id') - cmd = ','.join(request.POST.get('cmd').split('\n')) - comment = request.POST.get('comment') - dept = DEPT.objects.filter(id=dept_id) + # 根据rule_id 取得rule对象 + rule_id = request.POST.get("id") + rule_obj = PermRule.objects.get(id=rule_id) + rule_obj.delete() + return HttpResponse(u"删除授权规则:%s" % rule_obj.name) + else: + return HttpResponse(u"不支持该操作") + + +@require_role('admin') +def perm_role_list(request): + """ + list role page + """ + # 渲染数据 + header_title, path1, path2 = "系统用户", "系统用户管理", "查看系统用户" + + # 获取所有系统角色 + roles_list = PermRole.objects.all() + role_id = request.GET.get('id') + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + roles_list = roles_list.filter(Q(name=keyword)) + + if role_id: + roles_list = roles_list.filter(id=role_id) + + roles_list, p, roles, page_range, current_page, show_first, show_end = pages(roles_list, request) + + return my_render('jperm/perm_role_list.html', locals(), request) + + +@require_role('admin') +def perm_role_add(request): + """ + add role page + """ + # 渲染数据 + header_title, path1, path2 = "系统用户", "系统用户管理", "添加系统用户" + sudos = PermSudo.objects.all() + + if request.method == "POST": + # 获取参数: name, comment + name = request.POST.get("role_name", "") + comment = request.POST.get("role_comment", "") + password = request.POST.get("role_password", "") + key_content = request.POST.get("role_key", "") + sudo_ids = request.POST.getlist('sudo_name') try: - if CmdGroup.objects.filter(name=name): - error = '%s 命令组已存在' - raise ServerError(error) - - if not dept: - error = u"部门不能为空" - raise ServerError(error) - except ServerError, e: - pass - else: - dept = dept[0] - CmdGroup.objects.create(name=name, dept=dept, cmd=cmd, comment=comment) - msg = u'命令组添加成功' - return HttpResponseRedirect('/jperm/cmd_list/') - - return render_to_response('jperm/sudo_cmd_add.html', locals(), context_instance=RequestContext(request)) + if get_object(PermRole, name=name): + raise ServerError('已经存在该用户 %s' % name) + default = get_object(Setting, name='default') +<<<<<<< HEAD @require_admin def cmd_add_adm(request): @@ -564,252 +448,391 @@ def cmd_edit(request): if not cmd_group: error = '没有该命令组' +======= + if password: + encrypt_pass = CRYPTOR.encrypt(password) + else: + encrypt_pass = CRYPTOR.encrypt(CRYPTOR.gen_rand_pass(20)) + # 生成随机密码,生成秘钥对 + sudos_obj = [get_object(PermSudo, id=sudo_id) for sudo_id in sudo_ids] + if key_content: + key_path = gen_keys(key=key_content) + else: + key_path = gen_keys() + logger.debug('generate role key: %s' % key_path) + role = PermRole(name=name, comment=comment, password=encrypt_pass, key_path=key_path) + role.save() + role.sudo = sudos_obj + msg = u"添加系统用户: %s" % name + return HttpResponseRedirect(reverse('role_list')) +>>>>>>> dev except ServerError, e: - pass - else: - cmd_group.update(name=name, cmd=cmd, dept=dept[0], comment=comment) - return HttpResponseRedirect('/jperm/cmd_list/') - return render_to_response('jperm/sudo_cmd_add.html', locals(), context_instance=RequestContext(request)) + error = e + + return my_render('jperm/perm_role_add.html', locals(), request) -@require_admin -def cmd_list(request): - header_title, path1, path2 = u'sudo命令查看', u'权限管理', u'Sudo命令添加' - - if is_super_user(request): - cmd_groups = contact_list = CmdGroup.objects.all() +@require_role('admin') +def perm_role_delete(request): + """ + delete role page + """ + if request.method == "POST": + # 获取参数删除的role对象 + role_id = request.POST.get("id") + role = get_object(PermRole, id=role_id) + role_key = role.key_path + # 删除推送到主机上的role + recycle_assets = [push.asset for push in role.perm_push.all() if push.success] + logger.debug(u"delete role %s - delete_assets: %s" % (role.name, recycle_assets)) + if recycle_assets: + recycle_resource = gen_resource(recycle_assets) + task = MyTask(recycle_resource) + msg = task.del_user(get_object(PermRole, id=role_id).name) + logger.info(u"delete role %s - execute delete user: %s" % (role.name, msg)) + # TODO: 判断返回结果,处理异常 + # 删除存储的秘钥,以及目录 + key_files = os.listdir(role_key) + for key_file in key_files: + os.remove(os.path.join(role_key, key_file)) + os.rmdir(role_key) + logger.info(u"delete role %s - delete role key directory: %s" % (role.name, role_key)) + # 数据库里删除记录 TODO: 判断返回结果,处理异常 + role.delete() + return HttpResponse(u"删除系统用户: %s" % role.name) else: - user, dept = get_session_user_dept(request) - cmd_groups = contact_list = dept.cmdgroup_set.all() - p = paginator = Paginator(contact_list, 10) + return HttpResponse(u"不支持该操作") + + +@require_role('admin') +def perm_role_detail(request): + """ + the role detail page + the role_info data like: + {'asset_groups': [], + 'assets': [], + 'rules': [], + '': [], + '': []} + """ + # 渲染数据 + header_title, path1, path2 = "系统用户", "系统用户管理", "系统用户详情" try: - page = int(request.GET.get('page', '1')) - except ValueError: - page = 1 + if request.method == "GET": + role_id = request.GET.get("id") + if not role_id: + raise ServerError("not role id") + role = get_object(PermRole, id=role_id) + role_info = get_role_info(role_id) - try: - contacts = paginator.page(page) - except (EmptyPage, InvalidPage): - contacts = paginator.page(paginator.num_pages) - return render_to_response('jperm/sudo_cmd_list.html', locals(), context_instance=RequestContext(request)) + # 渲染数据 + rules = role_info.get("rules") + assets = role_info.get("assets") + asset_groups = role_info.get("asset_groups") + users = role_info.get("users") + user_groups = role_info.get("user_groups") + pushed_asset, need_push_asset = get_role_push_host(get_object(PermRole, id=role_id)) + except ServerError, e: + logger.warning(e) + + return my_render('jperm/perm_role_detail.html', locals(), request) -@require_admin -def cmd_del(request): - cmd_group_id = request.GET.get('id') - cmd_group = CmdGroup.objects.filter(id=cmd_group_id) +@require_role('admin') +def perm_role_edit(request): + """ + edit role page + """ + # 渲染数据 + header_title, path1, path2 = "系统用户", "系统用户管理", "系统用户编辑" - if cmd_group: - cmd_group[0].delete() - return HttpResponseRedirect('/jperm/cmd_list/') + # 渲染数据 + role_id = request.GET.get("id") + role = PermRole.objects.get(id=role_id) + role_pass = CRYPTOR.decrypt(role.password) + sudo_all = PermSudo.objects.all() + role_sudos = role.sudo.all() + sudo_all = PermSudo.objects.all() + if request.method == "GET": + return my_render('jperm/perm_role_edit.html', locals(), request) + + if request.method == "POST": + # 获取 POST 数据 + role_name = request.POST.get("role_name") + role_password = request.POST.get("role_password") + role_comment = request.POST.get("role_comment") + role_sudo_names = request.POST.getlist("sudo_name") + role_sudos = [PermSudo.objects.get(id=sudo_id) for sudo_id in role_sudo_names] + key_content = request.POST.get("role_key", "") + + try: + if not role: + raise ServerError('该系统用户不能存在') + + if role_password: + encrypt_pass = CRYPTOR.encrypt(role_password) + role.password = encrypt_pass + # 生成随机密码,生成秘钥对 + if key_content: + try: + key_path = gen_keys(key=key_content, key_path_dir=role.key_path) + except SSHException: + raise ServerError('输入的密钥不合法') + logger.debug('Recreate role key: %s' % role.key_path) + # 写入数据库 + role.name = role_name + role.comment = role_comment + role.sudo = role_sudos + + role.save() + msg = u"更新系统用户: %s" % role.name + return HttpResponseRedirect(reverse('role_list')) + except ServerError, e: + error = e + + return my_render('jperm/perm_role_edit.html', locals(), request) -@require_admin -def cmd_detail(request): - cmd_ids = request.GET.get('id').split(',') - cmds = [] - if len(cmd_ids) == 1: - if cmd_ids[0]: - cmd_id = cmd_ids[0] +@require_role('admin') +def perm_role_push(request): + """ + the role push page + """ + # 渲染数据 + header_title, path1, path2 = "系统用户", "系统用户管理", "系统用户推送" + role_id = request.GET.get('id') + asset_ids = request.GET.get('asset_id') + role = get_object(PermRole, id=role_id) + assets = Asset.objects.all() + asset_groups = AssetGroup.objects.all() + if asset_ids: + need_push_asset = [get_object(Asset, id=asset_id) for asset_id in asset_ids.split(',')] + + if request.method == "POST": + # 获取推荐角色的名称列表 + # 计算出需要推送的资产列表 + asset_ids = request.POST.getlist("assets") + asset_group_ids = request.POST.getlist("asset_groups") + assets_obj = [Asset.objects.get(id=asset_id) for asset_id in asset_ids] + asset_groups_obj = [AssetGroup.objects.get(id=asset_group_id) for asset_group_id in asset_group_ids] + group_assets_obj = [] + for asset_group in asset_groups_obj: + group_assets_obj.extend(asset_group.asset_set.all()) + calc_assets = list(set(assets_obj) | set(group_assets_obj)) + push_resource = gen_resource(calc_assets) + + # 调用Ansible API 进行推送 + password_push = True if request.POST.get("use_password") else False + key_push = True if request.POST.get("use_publicKey") else False + task = MyTask(push_resource) + ret = {} + + # 因为要先建立用户,所以password 是必选项,而push key是在 password也完成的情况下的 可选项 + # 1. 以秘钥 方式推送角色 + if key_push: + ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password)) + ret["key_push"] = task.push_key(role.name, os.path.join(role.key_path, 'id_rsa.pub')) + + # 2. 推送账号密码 + elif password_push: + ret["pass_push"] = task.add_user(role.name, CRYPTOR.decrypt(role.password)) + + # 3. 推送sudo配置文件 + if password_push or key_push: + sudo_list = set([sudo for sudo in role.sudo.all()]) # set(sudo1, sudo2, sudo3) + if sudo_list: + ret['sudo'] = task.push_sudo_file([role], sudo_list) + + logger.debug('推送role结果: %s' % ret) + success_asset = {} + failed_asset = {} + logger.debug(ret) + for push_type, result in ret.items(): + if result.get('failed'): + for hostname, info in result.get('failed').items(): + if hostname in failed_asset.keys(): + if info in failed_asset.get(hostname): + failed_asset[hostname] += info + else: + failed_asset[hostname] = info + + for push_type, result in ret.items(): + if result.get('ok'): + for hostname, info in result.get('ok').items(): + if hostname in failed_asset.keys(): + continue + elif hostname in success_asset.keys(): + if str(info) in success_asset.get(hostname, ''): + success_asset[hostname] += str(info) + else: + success_asset[hostname] = str(info) + + # 推送成功 回写push表 + for asset in calc_assets: + push_check = PermPush.objects.filter(role=role, asset=asset) + if push_check: + func = push_check.update + else: + def func(**kwargs): + PermPush(**kwargs).save() + + if failed_asset.get(asset.hostname): + func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=False, + result=failed_asset.get(asset.hostname)) + else: + func(is_password=password_push, is_public_key=key_push, role=role, asset=asset, success=True) + + if not failed_asset: + msg = u'系统用户 %s 推送成功[ %s ]' % (role.name, ','.join(success_asset.keys())) else: - cmd_id = 1 - cmd_group = CmdGroup.objects.filter(id=cmd_id) - if cmd_group: - cmd_group = cmd_group[0] - cmds.extend(cmd_group.cmd.split(',')) - cmd_group_name = cmd_group.name + error = u'系统用户 %s 推送失败 [ %s ], 推送成功 [ %s ]' % (role.name, + ','.join(failed_asset.keys()), + ','.join(success_asset.keys())) + return my_render('jperm/perm_role_push.html', locals(), request) + + +@require_role('admin') +def perm_sudo_list(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "查看别名" + + # 获取所有sudo 命令别名 + sudos_list = PermSudo.objects.all() + + # TODO: 搜索和分页 + keyword = request.GET.get('search', '') + if keyword: + sudos_list = sudos_list.filter(Q(name=keyword)) + + sudos_list, p, sudos, page_range, current_page, show_first, show_end = pages(sudos_list, request) + + return my_render('jperm/perm_sudo_list.html', locals(), request) + + +@require_role('admin') +def perm_sudo_add(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "添加别名" + + if request.method == "POST": + # 获取参数: name, comment + name = request.POST.get("sudo_name").strip().upper() + comment = request.POST.get("sudo_comment").strip() + commands = request.POST.get("sudo_commands").strip() + + pattern = re.compile(r'[ \n,\r]') + commands = ', '.join(list_drop_str(pattern.split(commands), u'')) + logger.debug(u'添加sudo %s: %s' % (name, commands)) + + if get_object(PermSudo, name=name): + error = 'Sudo别名 %s已经存在' % name + else: + sudo = PermSudo(name=name.strip(), comment=comment, commands=commands) + sudo.save() + msg = u"添加Sudo命令别名: %s" % name + # 渲染数据 + + return my_render('jperm/perm_sudo_add.html', locals(), request) + + +@require_role('admin') +def perm_sudo_edit(request): + """ + list sudo commands alias + :param request: + :return: + """ + # 渲染数据 + header_title, path1, path2 = "Sudo命令", "别名管理", "编辑别名" + + sudo_id = request.GET.get("id") + sudo = PermSudo.objects.get(id=sudo_id) + + if request.method == "POST": + name = request.POST.get("sudo_name").upper() + commands = request.POST.get("sudo_commands") + comment = request.POST.get("sudo_comment") + + pattern = re.compile(r'[ \n,\r]') + commands = ', '.join(list_drop_str(pattern.split(commands), u'')).strip() + logger.debug(u'添加sudo %s: %s' % (name, commands)) + + sudo.name = name.strip() + sudo.commands = commands + sudo.comment = comment + sudo.save() + + msg = u"更新命令别名: %s" % name + + return my_render('jperm/perm_sudo_edit.html', locals(), request) + + +@require_role('admin') +def perm_sudo_delete(request): + """ + list sudo commands alias + :param request: + :return: + """ + if request.method == "POST": + # 获取参数删除的role对象 + sudo_id = request.POST.get("id") + sudo = PermSudo.objects.get(id=sudo_id) + # 数据库里删除记录 + sudo.delete() + return HttpResponse(u"删除系统用户: %s" % sudo.name) else: - cmd_groups = [] - for cmd_id in cmd_ids: - cmd_groups.extend(CmdGroup.objects.filter(id=cmd_id)) - for cmd_group in cmd_groups: - cmds.extend(cmd_group.cmd.split(',')) - - cmds_str = ', '.join(cmds) - - return render_to_response('jperm/sudo_cmd_detail.html', locals(), context_instance=RequestContext(request)) + return HttpResponse(u"不支持该操作") -@require_login -def perm_apply(request): - """ 权限申请 """ - header_title, path1, path2 = u'主机权限申请', u'权限管理', u'申请主机' - user_id, username = get_session_user_info(request)[0:2] - name = User.objects.get(id=user_id).username - dept_id, deptname, dept = get_session_user_info(request)[3:6] - perm_host = user_perm_asset_api(username) - all_host = Asset.objects.filter(dept=dept) +@require_role('admin') +def perm_role_recycle(request): + role_id = request.GET.get('role_id') + asset_ids = request.GET.get('asset_id').split(',') - perm_group = user_perm_group_api(username) - all_group = dept.bisgroup_set.all() + # 仅有推送的角色才回收 + assets = [get_object(Asset, id=asset_id) for asset_id in asset_ids] + recycle_assets = [] + for asset in assets: + if True in [push.success for push in asset.perm_push.all()]: + recycle_assets.append(asset) + recycle_resource = gen_resource(recycle_assets) + task = MyTask(recycle_resource) + # TODO: 判断返回结果,处理异常 + msg = task.del_user(get_object(PermRole, id=role_id).name) - posts = [g for g in all_host if g not in perm_host] - egroup = [d for d in all_group if d not in perm_group] + for asset_id in asset_ids: + asset = get_object(Asset, id=asset_id) + assets.append(asset) + role = get_object(PermRole, id=role_id) + PermPush.objects.filter(asset=asset, role=role).delete() - dept_da = User.objects.filter(dept_id=dept_id, role='DA') - admin = User.objects.get(name='admin') - - if request.method == 'POST': - applyer = request.POST.get('applyer') - dept = request.POST.get('dept') - da = request.POST.get('da') - group = request.POST.getlist('group') - hosts = request.POST.getlist('hosts') - comment = request.POST.get('comment') - if not da: - return httperror(request, u'请选择管理员!') - da = User.objects.get(id=da) - mail_address = da.email - mail_title = '%s - 权限申请' % username - group_lis = ', '.join(group) - hosts_lis = ', '.join(hosts) - time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - a = Apply.objects.create(applyer=applyer, admin=da, dept=dept, bisgroup=group, date_add=datetime.datetime.now(), - asset=hosts, status=0, comment=comment, read=0) - uuid = a.uuid - url = "http://%s:%s/jperm/apply_exec/?uuid=%s" % (SEND_IP, SEND_PORT, uuid) - mail_msg = """ - Hi,%s: - 有新的权限申请, 详情如下: - 申请人: %s - 申请主机组: %s - 申请的主机: %s - 申请时间: %s - 申请说明: %s - 请及时审批, 审批完成后, 点击以下链接或登录授权管理-权限审批页面点击确认键,告知申请人。 - - %s - """ % (da.username, applyer, group_lis, hosts_lis, time_now, comment, url) - - send_mail(mail_title, mail_msg, MAIL_FROM, [mail_address], fail_silently=False) - smg = "提交成功,已发邮件至 %s 通知部门管理员。" % mail_address - return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) - return render_to_response('jperm/perm_apply.html', locals(), context_instance=RequestContext(request)) + return HttpResponse('删除成功') -@require_admin -def perm_apply_exec(request): - """ 确认权限 """ - header_title, path1, path2 = u'主机权限申请', u'权限管理', u'审批完成' - uuid = request.GET.get('uuid') - user_id = request.session.get('user_id') - approver = User.objects.get(id=user_id).name - if uuid: - p_apply = Apply.objects.filter(uuid=str(uuid)) - q_apply = Apply.objects.get(uuid=str(uuid)) - if q_apply.status == 1: - smg = '此权限已经审批完成, 请勿重复审批, 十秒钟后返回首页' - return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) - else: - user = User.objects.get(username=q_apply.applyer) - mail_address = user.email - time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') - p_apply.update(status=1, approver=approver, date_end=time_now) - mail_title = '%s - 权限审批完成' % q_apply.applyer - mail_msg = """ - Hi,%s: - 您所申请的权限已由 %s 在 %s 审批完成, 请登录验证。 - """ % (q_apply.applyer, q_apply.approver, time_now) - send_mail(mail_title, mail_msg, MAIL_FROM, [mail_address], fail_silently=False) - smg = '授权完成, 已邮件通知申请人, 十秒钟后返回首页' - return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) +@require_role('user') +def perm_role_get(request): + asset_id = request.GET.get('id', 0) + if asset_id: + asset = get_object(Asset, id=asset_id) + if asset: + role = user_have_perm(request.user, asset=asset) + logger.debug('#' + ','.join([i.name for i in role]) + '#') + return HttpResponse(','.join([i.name for i in role])) else: - smg = '没有此授权记录, 十秒钟后返回首页' - return render_to_response('jperm/perm_apply_exec.html', locals(), context_instance=RequestContext(request)) - - -def get_apply_posts(request, status, username, dept_name, keyword=None): - """ 获取申请记录 """ - post_all = Apply.objects.filter(status=status).order_by('-date_add') - post_keyword_all = Apply.objects.filter(Q(applyer__contains=keyword) | - Q(approver__contains=keyword)) \ - .filter(status=status).order_by('-date_add') - - if is_super_user(request): - if keyword: - posts = post_keyword_all - else: - posts = post_all - elif is_group_admin(request): - if keyword: - posts = post_keyword_all.filter(dept=dept_name) - else: - posts = post_all.filter(dept=dept_name) - elif is_common_user(request): - if keyword: - posts = post_keyword_all.filter(applyer=username) - else: - posts = post_all.filter(applyer=username) - - return posts - - -@require_login -def perm_apply_log(request, offset): - """ 申请记录 """ - header_title, path1, path2 = u'权限申请记录', u'权限管理', u'申请记录' - keyword = request.GET.get('keyword', '') - user_id = get_session_user_info(request)[0] - username = User.objects.get(id=user_id).name - dept_name = get_session_user_info(request)[4] - status_dic = {'online': 0, 'offline': 1} - status = status_dic[offset] - posts = get_apply_posts(request, status, username, dept_name, keyword) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return render_to_response('jperm/perm_log_%s.html' % offset, locals(), context_instance=RequestContext(request)) - - -@require_login -def perm_apply_info(request): - """ 申请信息详情 """ - uuid = request.GET.get('uuid', '') - post = Apply.objects.filter(uuid=uuid) - username = get_session_user_info(request)[1] - if post: - post = post[0] - if post.read == 0 and post.applyer != username: - post.read = 1 - post.save() - else: - return httperror(request, u'没有这个申请记录!') - - return render_to_response('jperm/perm_apply_info.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def perm_apply_del(request): - """ 删除日志记录 """ - uuid = request.GET.get('uuid') - u_apply = Apply.objects.filter(uuid=uuid) - if u_apply: - u_apply.delete() - return HttpResponseRedirect('/jperm/apply_show/online/') - - -@require_login -def perm_apply_search(request): - """ 申请搜索 """ - keyword = request.GET.get('keyword') - offset = request.GET.get('env') - username = get_session_user_info(request)[1] - dept_name = get_session_user_info(request)[3] - status_dic = {'online': 0, 'offline': 1} - status = status_dic[offset] - posts = get_apply_posts(request, status, username, dept_name, keyword) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(posts, request) - return render_to_response('jperm/perm_apply_search.html', locals(), context_instance=RequestContext(request)) - - - - - - - - - - - - + roles = get_group_user_perm(request.user).get('role').keys() + return HttpResponse(','.join(i.name for i in roles)) + return HttpResponse('error') diff --git a/jumpserver.conf b/jumpserver.conf index 49f135076..fe8099605 100644 --- a/jumpserver.conf +++ b/jumpserver.conf @@ -1,10 +1,7 @@ -#coding: utf8 - [base] -ip = 192.168.20.209 -port = 80 +url = http://192.168.244.129 key = 88aaaf7ffe3c6c04 - +log = debug [db] host = 127.0.0.1 @@ -13,22 +10,20 @@ user = jumpserver password = mysql234 database = jumpserver - -[ldap] -ldap_enable = 1 -host_url = ldap://127.0.0.1:389 -base_dn = dc=jumpserver, dc=org -root_dn = cn=admin,dc=jumpserver,dc=org -root_pw = secret234 - - [websocket] -web_socket_host = 192.168.40.140:3000 - +web_socket_host = 192.168.244.129:3000 [mail] +mail_enable = 1 email_host = smtp.qq.com email_port = 25 +<<<<<<< HEAD email_host_user = 1152704203@qq.com email_host_password = Hudie117... email_use_tls = False +======= +email_host_user = ibuler@qq.com +email_host_password = Hudie117...qq +email_use_tls = True + +>>>>>>> dev diff --git a/jumpserver/api.py b/jumpserver/api.py index 4a3a6bde7..747cbce00 100644 --- a/jumpserver/api.py +++ b/jumpserver/api.py @@ -1,27 +1,31 @@ # coding: utf-8 - -from django.http import HttpResponseRedirect -import json -import os -from ConfigParser import ConfigParser -import getpass +import os, sys, time, re from Crypto.Cipher import AES +import crypt +import pwd from binascii import b2a_hex, a2b_hex -import ldap -from ldap import modlist import hashlib import datetime +import random import subprocess +import uuid +import json +import logging + +from settings import * from django.core.paginator import Paginator, EmptyPage, InvalidPage from django.http import HttpResponse, Http404 +from django.template import RequestContext +from juser.models import User, UserGroup +from jlog.models import Log, TtyLog +from jasset.models import Asset, AssetGroup +from jperm.models import PermRule, PermRole +from jumpserver.models import Setting +from django.http import HttpResponseRedirect from django.shortcuts import render_to_response -from juser.models import User, UserGroup, DEPT -from jasset.models import Asset, BisGroup, IDC -from jlog.models import Log -from jasset.models import AssetAlias -from django.core.exceptions import ObjectDoesNotExist from django.core.mail import send_mail +<<<<<<< HEAD import json @@ -105,64 +109,208 @@ if LDAP_ENABLE: def md5_crypt(string): return hashlib.new("md5", string).hexdigest() +======= +from django.core.urlresolvers import reverse + + +def set_log(level, filename='jumpserver.log'): + """ + return a log file object + 根据提示设置log打印 + """ + log_file = os.path.join(LOG_DIR, filename) + if not os.path.isfile(log_file): + os.mknod(log_file) + os.chmod(log_file, 0777) + log_level_total = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARN, 'error': logging.ERROR, + 'critical': logging.CRITICAL} + logger_f = logging.getLogger('jumpserver') + logger_f.setLevel(logging.DEBUG) + fh = logging.FileHandler(log_file) + fh.setLevel(log_level_total.get(level, logging.DEBUG)) + formatter = logging.Formatter('%(asctime)s - %(filename)s - %(levelname)s - %(message)s') + fh.setFormatter(formatter) + logger_f.addHandler(fh) + return logger_f + + +def list_drop_str(a_list, a_str): + for i in a_list: + if i == a_str: + a_list.remove(a_str) + return a_list + + +def get_asset_info(asset): + """ + 获取资产的相关管理账号端口等信息 + """ + default = get_object(Setting, name='default') + info = {'hostname': asset.hostname, 'ip': asset.ip} + if asset.use_default_auth: + if default: + info['port'] = int(default.field2) + info['username'] = default.field1 + try: + info['password'] = CRYPTOR.decrypt(default.field3) + except ServerError: + pass + if os.path.isfile(default.field4): + info['ssh_key'] = default.field4 + else: + info['port'] = int(asset.port) + info['username'] = asset.username + info['password'] = CRYPTOR.decrypt(asset.password) + + return info + + +def get_role_key(user, role): + """ + 由于role的key的权限是所有人可以读的, ansible执行命令等要求为600,所以拷贝一份到特殊目录 + :param user: + :param role: + :return: self key path + """ + user_role_key_dir = os.path.join(KEY_DIR, 'user') + user_role_key_path = os.path.join(user_role_key_dir, '%s_%s.pem' % (user.username, role.name)) + mkdir(user_role_key_dir, mode=0777) + if not os.path.isfile(user_role_key_path): + with open(os.path.join(role.key_path, 'id_rsa')) as fk: + with open(user_role_key_path, 'w') as fu: + fu.write(fk.read()) + logger.debug(u"创建新的系统用户key %s, Owner: %s" % (user_role_key_path, user.username)) + chown(user_role_key_path, user.username) + os.chmod(user_role_key_path, 0600) + return user_role_key_path + + +def chown(path, user, group=''): + if not group: + group = user + try: + uid = pwd.getpwnam(user).pw_uid + gid = pwd.getpwnam(group).pw_gid + os.chown(path, uid, gid) + except KeyError: + pass +>>>>>>> dev def page_list_return(total, current=1): + """ + page + 分页,返回本次分页的最小页数到最大页数列表 + """ min_page = current - 2 if current - 4 > 0 else 1 max_page = min_page + 4 if min_page + 4 < total else total - return range(min_page, max_page+1) + return range(min_page, max_page + 1) -def pages(posts, r): - """分页公用函数""" - contact_list = posts - p = paginator = Paginator(contact_list, 10) +def pages(post_objects, request): + """ + page public function , return page's object tuple + 分页公用函数,返回分页的对象元组 + """ + paginator = Paginator(post_objects, 20) try: - current_page = int(r.GET.get('page', '1')) + current_page = int(request.GET.get('page', '1')) except ValueError: current_page = 1 - page_range = page_list_return(len(p.page_range), current_page) + page_range = page_list_return(len(paginator.page_range), current_page) try: - contacts = paginator.page(current_page) + page_objects = paginator.page(current_page) except (EmptyPage, InvalidPage): - contacts = paginator.page(paginator.num_pages) + page_objects = paginator.page(paginator.num_pages) if current_page >= 5: show_first = 1 else: show_first = 0 - if current_page <= (len(p.page_range) - 3): + + if current_page <= (len(paginator.page_range) - 3): show_end = 1 else: show_end = 0 - return contact_list, p, contacts, page_range, current_page, show_first, show_end + # 所有对象, 分页器, 本页对象, 所有页码, 本页页码,是否显示第一页,是否显示最后一页 + return post_objects, paginator, page_objects, page_range, current_page, show_first, show_end class PyCrypt(object): - """This class used to encrypt and decrypt password.""" + """ + This class used to encrypt and decrypt password. + 加密类 + """ def __init__(self, key): self.key = key self.mode = AES.MODE_CBC - def encrypt(self, text): - cryptor = AES.new(self.key, self.mode, b'0000000000000000') - length = 16 + @staticmethod + def gen_rand_pass(length, especial=False): + """ + random password + 随机生成密码 + """ + salt_key = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' + symbol = '!@$%^&*()_' + salt_list = [] + if especial: + for i in range(length - 4): + salt_list.append(random.choice(salt_key)) + for i in range(4): + salt_list.append(random.choice(symbol)) + else: + for i in range(length): + salt_list.append(random.choice(salt_key)) + salt = ''.join(salt_list) + return salt + + @staticmethod + def md5_crypt(string): + """ + md5 encrypt method + md5非对称加密方法 + """ + return hashlib.new("md5", string).hexdigest() + + @staticmethod + def gen_sha512(salt, password): + """ + generate sha512 format password + 生成sha512加密密码 + """ + return crypt.crypt(password, '$6$%s$' % salt) + + def encrypt(self, passwd=None, length=32): + """ + encrypt gen password + 对称加密之加密生成密码 + """ + if not passwd: + passwd = self.gen_rand_pass() + + cryptor = AES.new(self.key, self.mode, b'8122ca7d906ad5e1') try: - count = len(text) + count = len(passwd) except TypeError: raise ServerError('Encrypt password error, TYpe error.') + add = (length - (count % length)) - text += ('\0' * add) - ciphertext = cryptor.encrypt(text) - return b2a_hex(ciphertext) + passwd += ('\0' * add) + cipher_text = cryptor.encrypt(passwd) + return b2a_hex(cipher_text) def decrypt(self, text): - cryptor = AES.new(self.key, self.mode, b'0000000000000000') + """ + decrypt pass base the same key + 对称加密之解密,同一个加密随机数 + """ + cryptor = AES.new(self.key, self.mode, b'8122ca7d906ad5e1') try: plain_text = cryptor.decrypt(a2b_hex(text)) except TypeError: @@ -170,95 +318,103 @@ class PyCrypt(object): return plain_text.rstrip('\0') -CRYPTOR = PyCrypt(KEY) - - class ServerError(Exception): + """ + self define exception + 自定义异常 + """ pass def get_object(model, **kwargs): - try: - the_object = model.objects.get(**kwargs) - except ObjectDoesNotExist: - raise ServerError('Object get %s failed.' % str(kwargs.values())) + """ + use this function for query + 使用改封装函数查询数据库 + """ + for value in kwargs.values(): + if not value: + return None + + the_object = model.objects.filter(**kwargs) + if len(the_object) == 1: + the_object = the_object[0] + else: + the_object = None return the_object -def require_login(func): - """要求登录的装饰器""" - def _deco(request, *args, **kwargs): - if not request.session.get('user_id'): - return HttpResponseRedirect('/login/') - return func(request, *args, **kwargs) +def require_role(role='user'): + """ + decorator for require user role in ["super", "admin", "user"] + 要求用户是某种角色 ["super", "admin", "user"]的装饰器 + """ + + def _deco(func): + def __deco(request, *args, **kwargs): + request.session['pre_url'] = request.path + if not request.user.is_authenticated(): + return HttpResponseRedirect(reverse('login')) + if role == 'admin': + # if request.session.get('role_id', 0) < 1: + if request.user.role == 'CU': + return HttpResponseRedirect(reverse('index')) + elif role == 'super': + # if request.session.get('role_id', 0) < 2: + if request.user.role in ['CU', 'GA']: + return HttpResponseRedirect(reverse('index')) + return func(request, *args, **kwargs) + + return __deco + return _deco -def require_super_user(func): - def _deco(request, *args, **kwargs): - if not request.session.get('user_id'): - return HttpResponseRedirect('/login/') - - if request.session.get('role_id', 0) != 2: - return HttpResponseRedirect('/') - return func(request, *args, **kwargs) - return _deco - - -def require_admin(func): - def _deco(request, *args, **kwargs): - if not request.session.get('user_id'): - return HttpResponseRedirect('/login/') - - if request.session.get('role_id', 0) < 1: - return HttpResponseRedirect('/') - return func(request, *args, **kwargs) - return _deco - - -def is_super_user(request): - if request.session.get('role_id') == 2: +def is_role_request(request, role='user'): + """ + require this request of user is right + 要求请求角色正确 + """ + role_all = {'user': 'CU', 'admin': 'GA', 'super': 'SU'} + if request.user.role == role_all.get(role, 'CU'): return True else: return False -def is_group_admin(request): - if request.session.get('role_id') == 1: - return True - else: - return False - - -def is_common_user(request): - if request.session.get('role_id') == 0: - return True - else: - return False - - -@require_login def get_session_user_dept(request): - user_id = request.session.get('user_id', 0) - user = User.objects.filter(id=user_id) - if user: - user = user[0] - dept = user.dept - return user, dept + """ + get department of the user in session + 获取session中用户的部门 + """ + # user_id = request.session.get('user_id', 0) + # print '#' * 20 + # print user_id + # user = User.objects.filter(id=user_id) + # if user: + # user = user[0] + # return user, None + return request.user, None -@require_login +@require_role def get_session_user_info(request): - user_id = request.session.get('user_id', 0) - user = User.objects.filter(id=user_id) - if user: - user = user[0] - dept = user.dept - return [user.id, user.username, user, dept.id, dept.name, dept] + """ + get the user info of the user in session, for example id, username etc. + 获取用户的信息 + """ + # user_id = request.session.get('user_id', 0) + # user = get_object(User, id=user_id) + # if user: + # return [user.id, user.username, user] + return [request.user.id, request.user.username, request.user] def get_user_dept(request): - user_id = request.session.get('user_id') + """ + get the user dept id + 获取用户的部门id + """ + user_id = request.user.id if user_id: user_dept = User.objects.get(id=user_id).dept return user_dept.id @@ -273,129 +429,28 @@ def api_user(request): def view_splitter(request, su=None, adm=None): - if is_super_user(request): + """ + for different user use different view + 视图分页器 + """ + if is_role_request(request, 'super'): return su(request) - elif is_group_admin(request): + elif is_role_request(request, 'admin'): return adm(request) else: - return HttpResponseRedirect('/login/') - - -def user_group_perm_asset_group_api(user_group): - asset_group_list = [] - perm_list = user_group.perm_set.all() - for perm in perm_list: - asset_group_list.append(perm.asset_group) - return asset_group_list - - -def user_perm_group_api(username): - if username: - user = User.objects.get(username=username) - perm_list = [] - user_group_all = user.group.all() - for user_group in user_group_all: - perm_list.extend(user_group.perm_set.all()) - - asset_group_list = [] - for perm in perm_list: - asset_group_list.append(perm.asset_group) - return asset_group_list - - -def user_perm_group_hosts_api(gid): - hostgroup = BisGroup.objects.filter(id=gid) - if hostgroup: - return hostgroup[0].asset_set.all() - else: - return [] - - -def user_perm_asset_api(username): - user = User.objects.filter(username=username) - if user: - user = user[0] - asset_list = [] - asset_group_list = user_perm_group_api(user) - for asset_group in asset_group_list: - asset_list.extend(asset_group.asset_set.all()) - asset_list = list(set(asset_list)) - return asset_list - else: - return [] - - -def asset_perm_api(asset): - if asset: - perm_list = [] - asset_group_all = asset.bis_group.all() - for asset_group in asset_group_all: - perm_list.extend(asset_group.perm_set.all()) - - user_group_list = [] - for perm in perm_list: - user_group_list.append(perm.user_group) - - user_permed_list = [] - for user_group in user_group_list: - user_permed_list.extend(user_group.user_set.all()) - user_permed_list = list(set(user_permed_list)) - return user_permed_list - - -def get_user_host(username): - """Get the hosts of under the user control.""" - hosts_attr = {} - asset_all = user_perm_asset_api(username) - user = User.objects.filter(username=username) - if user: - user = user[0] - for asset in asset_all: - alias = AssetAlias.objects.filter(user=user, host=asset) - if alias and alias[0].alias != '': - hosts_attr[asset.ip] = [asset.id, asset.ip, alias[0].alias] - else: - hosts_attr[asset.ip] = [asset.id, asset.ip, asset.comment] - return hosts_attr - else: - raise ServerError('User %s does not exit!' % username) - - -def get_connect_item(username, ip): - asset = get_object(Asset, ip=ip) - port = int(asset.port) - - if not asset.is_active: - raise ServerError('Host %s is not active.' % ip) - - user = get_object(User, username=username) - - if not user.is_active: - raise ServerError('User %s is not active.' % username) - - login_type_dict = { - 'L': user.ldap_pwd, - } - - if asset.login_type in login_type_dict: - password = CRYPTOR.decrypt(login_type_dict[asset.login_type]) - return username, password, ip, port - - elif asset.login_type == 'M': - username = asset.username - password = CRYPTOR.decrypt(asset.password) - return username, password, ip, port - - else: - raise ServerError('Login type is not in ["L", "M"]') + return HttpResponseRedirect(reverse('login')) def validate(request, user_group=None, user=None, asset_group=None, asset=None, edept=None): + """ + validate the user request + 判定用户请求是否合法 + """ dept = get_session_user_dept(request)[1] if edept: if dept.id != int(edept[0]): return False - + if user_group: dept_user_groups = dept.usergroup_set.all() user_group_ids = [] @@ -480,39 +535,60 @@ def verify(request, user_group=None, user=None, asset_group=None, asset=None, ed def bash(cmd): - """执行bash命令""" + """ + run a bash shell command + 执行bash命令 + """ return subprocess.call(cmd, shell=True) -def is_dir(dir_name, username='root', mode=0755): +def mkdir(dir_name, username='', mode=0755): + """ + insure the dir exist and mode ok + 目录存在,如果不存在就建立,并且权限正确 + """ if not os.path.isdir(dir_name): os.makedirs(dir_name) - bash("chown %s:%s '%s'" % (username, username, dir_name)) - os.chmod(dir_name, mode) + os.chmod(dir_name, mode) + if username: + chown(dir_name, username) -def success(request, msg): +def http_success(request, msg): return render_to_response('success.html', locals()) -def httperror(request, emg): +def http_error(request, emg): message = emg return render_to_response('error.html', locals()) -def node_auth(request): - username = request.POST.get('username', ' ') - seed = request.POST.get('seed', ' ') - filename = request.POST.get('filename', ' ') - user = User.objects.filter(username=username, password=seed) - auth = 1 - if not user: - auth = 0 - if not filename.startswith('/opt/jumpserver/logs/connect/'): - auth = 0 - if auth: - result = {'auth': {'username': username, 'result': 'success'}} - else: - result = {'auth': {'username': username, 'result': 'failed'}} +def my_render(template, data, request): + return render_to_response(template, data, context_instance=RequestContext(request)) - return HttpResponse(json.dumps(result, sort_keys=True, indent=2), content_type='application/json') \ No newline at end of file + +def get_tmp_dir(): + dir_name = os.path.join('/tmp', uuid.uuid4().hex) + mkdir(dir_name, mode=0777) + return dir_name + + +def defend_attack(func): + def _deco(request, *args, **kwargs): + if int(request.session.get('visit', 1)) > 10: + logger.debug('请求次数: %s' % request.session.get('visit', 1)) + return HttpResponse('Forbidden', status=403) + request.session['visit'] = request.session.get('visit', 1) + 1 + request.session.set_expiry(300) + return func(request, *args, **kwargs) + return _deco + + +def get_mac_address(): + node = uuid.getnode() + mac = uuid.UUID(int=node).hex[-12:] + return mac + + +CRYPTOR = PyCrypt(KEY) +logger = set_log(LOG_LEVEL) diff --git a/jumpserver/context_processors.py b/jumpserver/context_processors.py index aac09c7a7..e84cc60ec 100644 --- a/jumpserver/context_processors.py +++ b/jumpserver/context_processors.py @@ -1,26 +1,16 @@ from juser.models import User from jasset.models import Asset from jumpserver.api import * -from jperm.models import Apply def name_proc(request): - user_id = request.session.get('user_id') - role_id = request.session.get('role_id') - if role_id == 2: - user_total_num = User.objects.all().count() - user_active_num = User.objects.filter().count() - host_total_num = Asset.objects.all().count() - host_active_num = Asset.objects.filter(is_active=True).count() - else: - user, dept = get_session_user_dept(request) - user_total_num = dept.user_set.all().count() - user_active_num = dept.user_set.filter(is_active=True).count() - host_total_num = dept.asset_set.all().count() - host_active_num = dept.asset_set.all().filter(is_active=True).count() - - username = User.objects.get(id=user_id).name - apply_info = Apply.objects.filter(admin=username, status=0, read=0) + user_id = request.user.id + role_id = {'SU': 2, 'GA': 1, 'CU': 0}.get(request.user.role, 0) + # role_id = 'SU' + user_total_num = User.objects.all().count() + user_active_num = User.objects.filter().count() + host_total_num = Asset.objects.all().count() + host_active_num = Asset.objects.filter(is_active=True).count() request.session.set_expiry(3600) info_dic = {'session_user_id': user_id, @@ -29,7 +19,7 @@ def name_proc(request): 'user_active_num': user_active_num, 'host_total_num': host_total_num, 'host_active_num': host_active_num, - 'apply_info': apply_info} + } return info_dic diff --git a/jumpserver/settings.py b/jumpserver/settings.py index ce4d7e8b5..fa8431272 100644 --- a/jumpserver/settings.py +++ b/jumpserver/settings.py @@ -11,24 +11,37 @@ https://docs.djangoproject.com/en/1.7/ref/settings/ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os import ConfigParser +import getpass config = ConfigParser.ConfigParser() -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) config.read(os.path.join(BASE_DIR, 'jumpserver.conf')) +KEY_DIR = os.path.join(BASE_DIR, 'keys') + DB_HOST = config.get('db', 'host') DB_PORT = config.getint('db', 'port') DB_USER = config.get('db', 'user') DB_PASSWORD = config.get('db', 'password') DB_DATABASE = config.get('db', 'database') - +AUTH_USER_MODEL = 'juser.User' # mail config +MAIL_ENABLE = config.get('mail', 'mail_enable') EMAIL_HOST = config.get('mail', 'email_host') EMAIL_PORT = config.get('mail', 'email_port') EMAIL_HOST_USER = config.get('mail', 'email_host_user') EMAIL_HOST_PASSWORD = config.get('mail', 'email_host_password') EMAIL_USE_TLS = config.getboolean('mail', 'email_use_tls') +EMAIL_TIMEOUT = 5 + +# ======== Log ========== +LOG_DIR = os.path.join(BASE_DIR, 'logs') +SSH_KEY_DIR = os.path.join(BASE_DIR, 'keys/role_keys') +KEY = config.get('base', 'key') +URL = config.get('base', 'url') +LOG_LEVEL = config.get('base', 'log') +WEB_SOCKET_HOST = config.get('websocket', 'web_socket_host') # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ @@ -43,7 +56,6 @@ TEMPLATE_DEBUG = True ALLOWED_HOSTS = ['0.0.0.0/8'] - # Application definition INSTALLED_APPS = ( @@ -54,6 +66,8 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', + 'django_crontab', + 'bootstrapform', 'jumpserver', 'juser', 'jasset', @@ -64,9 +78,9 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - #'django.middleware.csrf.CsrfViewMiddleware', + # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - #'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) @@ -90,6 +104,12 @@ DATABASES = { } } +# DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.sqlite3', +# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), +# } +# } TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', @@ -98,14 +118,14 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.static', 'django.core.context_processors.tz', 'django.contrib.messages.context_processors.messages', - 'jumpserver.context_processors.name_proc' + 'jumpserver.context_processors.name_proc', ) TEMPLATE_DIRS = ( os.path.join(BASE_DIR, 'templates'), ) -#STATIC_ROOT = os.path.join(BASE_DIR, 'static') +# STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), @@ -129,4 +149,8 @@ USE_TZ = False STATIC_URL = '/static/' +BOOTSTRAP_COLUMN_COUNT = 10 +CRONJOBS = [ + ('0 1 * * *', 'jasset.asset_api.asset_ansible_update_all') +] diff --git a/jumpserver/templatetags/mytags.py b/jumpserver/templatetags/mytags.py index 6f7567b51..ea27b12e1 100644 --- a/jumpserver/templatetags/mytags.py +++ b/jumpserver/templatetags/mytags.py @@ -5,97 +5,67 @@ import ast import time from django import template -from jperm.models import CmdGroup +from jperm.models import PermPush from jumpserver.api import * -from jasset.models import AssetAlias +from jperm.perm_api import get_group_user_perm register = template.Library() -@register.filter(name='stamp2str') -def stamp2str(value): - try: - return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(value)) - except AttributeError: - return '0000/00/00 00:00:00' - - @register.filter(name='int2str') def int2str(value): + """ + int 转换为 str + """ return str(value) @register.filter(name='get_role') def get_role(user_id): - user_role = {'SU': u'超级管理员', 'DA': u'部门管理员', 'CU': u'普通用户'} - user = User.objects.filter(id=user_id) + """ + 根据用户id获取用户权限 + """ + + user_role = {'SU': u'超级管理员', 'GA': u'组管理员', 'CU': u'普通用户'} + user = get_object(User, id=user_id) if user: - user = user[0] return user_role.get(str(user.role), u"普通用户") else: return u"普通用户" -@register.filter(name='groups_str') -def groups_str(user_id): - groups = [] - user = User.objects.get(id=user_id) - for group in user.group.all(): - groups.append(group.name) - if len(groups) < 3: - return ' '.join(groups) - else: - return "%s ..." % ' '.join(groups[0:2]) - - -@register.filter(name='group_str2') -def groups_str2(group_list): +@register.filter(name='groups2str') +def groups2str(group_list): + """ + 将用户组列表转换为str + """ if len(group_list) < 3: return ' '.join([group.name for group in group_list]) else: return '%s ...' % ' '.join([group.name for group in group_list[0:2]]) -@register.filter(name='group_str2_all') -def group_str2_all(group_list): - group_lis = [] - for i in group_list: - if str(i) != 'ALL': - group_lis.append(i) - if len(group_lis) < 3: - return ' '.join([group.name for group in group_lis]) - else: - return '%s ...' % ' '.join([group.name for group in group_lis[0:2]]) +@register.filter(name='user_asset_count') +def user_asset_count(user): + """ + 返回用户权限主机的数量 + """ + assets = user.asset.all() + asset_groups = user.asset_group.all() + + for asset_group in asset_groups: + if asset_group: + assets.extend(asset_group.asset_set.all()) + + return len(assets) -@register.filter(name='group_dept_all') -def group_dept_all(group_list): - group_lis = [] - for i in group_list: - if str(i) != 'ALL': - group_lis.append(i) - return ' '.join([group.name for group in group_lis]) - - -@register.filter(name='group_manage_str') -def group_manage_str(username): - user = User.objects.get(username=username) - group = user.user_group.filter(type='M') - if group: - return group[0].name - else: - return '' - - -@register.filter(name='get_item') -def get_item(dictionary, key): - return dictionary.get(key) - - -@register.filter(name='get_login_type') -def get_login_type(login): - login_types = {'L': 'LDAP', 'M': 'MAP'} - return login_types[login] +@register.filter(name='user_asset_group_count') +def user_asset_group_count(user): + """ + 返回用户权限主机组的数量 + """ + return len(user.asset_group.all()) @register.filter(name='bool2str') @@ -106,166 +76,19 @@ def bool2str(value): return u'否' -# @register.filter(name='user_readonly') -# def user_readonly(user_id): -# user = User.objects.filter(id=user_id) -# if user: -# user = user[0] -# if user.role == 'CU': -# return False -# return True -# - -@register.filter(name='member_count') -def member_count(group_id): - group = UserGroup.objects.get(id=group_id) - return group.user_set.count() - - -@register.filter(name='group_user_count') -def group_user_count(group_id): - group = UserGroup.objects.get(id=group_id) - return group.user_set.count() - - -@register.filter(name='dept_user_num') -def dept_user_num(dept_id): - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - return dept.user_set.count() +@register.filter(name='members_count') +def members_count(group_id): + """统计用户组下成员数量""" + group = get_object(UserGroup, id=group_id) + if group: + return group.user_set.count() else: return 0 -@register.filter(name='dept_group_num') -def dept_group_num(dept_id): - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - return dept.usergroup_set.all().count() - else: - return 0 - - -@register.filter(name='perm_count') -def perm_count(group_id): - group = UserGroup.objects.get(id=group_id) - return group.perm_set.count() - - -@register.filter(name='dept_asset_num') -def dept_asset_num(dept_id): - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - return dept.asset_set.all().count() - return 0 - - -@register.filter(name='ugrp_perm_agrp_count') -def ugrp_perm_agrp_count(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - if user_group: - user_group = user_group[0] - return user_group.perm_set.all().count() - return 0 - - -@register.filter(name='ugrp_sudo_agrp_count') -def ugrp_sudo_agrp_count(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - asset_groups = [] - if user_group: - user_group = user_group[0] - for perm in user_group.sudoperm_set.all(): - asset_groups.extend(perm.asset_group.all()) - return len(set(asset_groups)) - return 0 - - -@register.filter(name='ugrp_perm_asset_count') -def ugrp_perm_asset_count(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - assets = [] - if user_group: - user_group = user_group[0] - asset_groups = [perm.asset_group for perm in user_group.perm_set.all()] - for asset_group in asset_groups: - assets.extend(asset_group.asset_set.all()) - return len(set(assets)) - - -@register.filter(name='ugrp_sudo_asset_count') -def ugrp_sudo_asset_count(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - asset_groups = [] - assets = [] - if user_group: - user_group = user_group[0] - for perm in user_group.sudoperm_set.all(): - asset_groups.extend(perm.asset_group.all()) - - for asset_group in asset_groups: - assets.extend(asset_group.asset_set.all()) - return len(set(assets)) - - -@register.filter(name='get_user_alias') -def get_user_alias(post, user_id): - user = User.objects.get(id=user_id) - host = Asset.objects.get(id=post.id) - alias = AssetAlias.objects.filter(user=user, host=host) - if alias: - return alias[0].alias - else: - return '' - - -@register.filter(name='group_type_to_str') -def group_type_to_str(type_name): - group_types = { - 'P': '用户', - 'M': '部门', - 'A': '用户组', - } - return group_types.get(type_name) - - -@register.filter(name='ast_to_list') -def ast_to_list(lis): - ast_lis = ast.literal_eval(lis) - if len(ast_lis) <= 2: - return ','.join([i for i in ast_lis]) - else: - restr = ','.join([i for i in ast_lis[0:2]]) + '...' - return restr - - -@register.filter(name='get_group_count') -def get_group_count(post, dept): - count = post.asset_set.filter(dept=dept).count() - return count - - -@register.filter(name='get_idc_count') -def get_idc_count(post, dept): - count = post.asset_set.filter(dept=dept).count() - return count - - -@register.filter(name='ast_to_list_1') -def ast_to_list_1(lis): - return ast.literal_eval(lis) - - -@register.filter(name='string_length') -def string_length(string, length): - return '%s ...' % string[0:length] - - @register.filter(name='to_name') def to_name(user_id): + """user id 转位用户名称""" try: user = User.objects.filter(id=int(user_id)) if user: @@ -275,89 +98,182 @@ def to_name(user_id): return '非法用户' -@register.filter(name='to_dept_name') -def to_dept_name(user_id): - try: - user = User.objects.filter(id=int(user_id)) - if user: - user = user[0] - return user.dept.name - except: - return '非法部门' - - @register.filter(name='to_role_name') def to_role_name(role_id): - role_dict = {'0': '普通用户', '1': '部门管理员', '2': '超级管理员'} + """role_id 转变为角色名称""" + role_dict = {'0': '普通用户', '1': '组管理员', '2': '超级管理员'} return role_dict.get(str(role_id), '未知') @register.filter(name='to_avatar') def to_avatar(role_id='0'): + """不同角色不同头像""" role_dict = {'0': 'user', '1': 'admin', '2': 'root'} return role_dict.get(str(role_id), 'user') -@register.filter(name='get_user_asset_group') -def get_user_asset_group(user): - return user_perm_group_api(user) +@register.filter(name='result2bool') +def result2bool(result=''): + """将结果定向为结果""" + result = eval(result) + unreachable = result.get('unreachable', []) + failures = result.get('failures', []) - -@register.filter(name='group_asset_list') -def group_asset_list(group): - return group.asset_set.all() - - -@register.filter(name='group_asset_list_count') -def group_asset_list_count(group): - return group.asset_set.all().count() - - -@register.filter(name='time_delta') -def time_delta(time_before): - delta = datetime.datetime.now() - time_before - days = delta.days - if days: - return "%s 天前" % days + if unreachable or failures: + return '失败' else: - hours = delta.seconds/3600 - if hours: - return "%s 小时前" % hours - else: - mins = delta.seconds/60 - if mins: - return '%s 分钟前' % mins - else: - return '%s 秒前' % delta.seconds + return '成功' -@register.filter(name='sudo_cmd_list') -def sudo_cmd_list(cmd_group_id): - cmd_group = CmdGroup.objects.filter(id=cmd_group_id) - if cmd_group: - cmd_group = cmd_group[0] - return cmd_group.cmd.split(',') +@register.filter(name='rule_member_count') +def rule_member_count(instance, member): + """ + instance is a rule object, + use to get the number of the members + :param instance: + :param member: + :return: + """ + member = getattr(instance, member) + counts = member.all().count() + return str(counts) -@register.filter(name='sudo_cmd_count') -def sudo_cmd_count(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - cmds = [] - if user_group: - user_group = user_group[0] - cmd_groups = [] +@register.filter(name='rule_member_name') +def rule_member_name(instance, member): + """ + instance is a rule object, + use to get the name of the members + :param instance: + :param member: + :return: + """ + member = getattr(instance, member) + names = member.all() - for perm in user_group.sudoperm_set.all(): - cmd_groups.extend(perm.cmd_group.all()) + return names - for cmd_group in cmd_groups: - cmds.extend(cmd_group.cmd.split(',')) - return len(set(cmds)) +@register.filter(name='user_which_groups') +def user_which_group(user, member): + """ + instance is a user object, + use to get the group of the user + :param instance: + :param member: + :return: + """ + member = getattr(user, member) + names = [members.name for members in member.all()] + + return ','.join(names) + + +@register.filter(name='asset_which_groups') +def asset_which_group(asset, member): + """ + instance is a user object, + use to get the group of the user + :param instance: + :param member: + :return: + """ + member = getattr(asset, member) + names = [members.name for members in member.all()] + + return ','.join(names) + + +@register.filter(name='group_str2') +def groups_str2(group_list): + """ + 将用户组列表转换为str + """ + if len(group_list) < 3: + return ' '.join([group.name for group in group_list]) else: - return 0 + return '%s ...' % ' '.join([group.name for group in group_list[0:2]]) +@register.filter(name='str_to_list') +def str_to_list(info): + """ + str to list + """ + print ast.literal_eval(info), type(ast.literal_eval(info)) + return ast.literal_eval(info) + + +@register.filter(name='str_to_dic') +def str_to_dic(info): + """ + str to list + """ + if '{' in info: + info_dic = ast.literal_eval(info).iteritems() + else: + info_dic = {} + return info_dic + + +@register.filter(name='str_to_code') +def str_to_code(char_str): + if char_str: + return char_str + else: + return u'空' + + +@register.filter(name='ip_str_to_list') +def ip_str_to_list(ip_str): + """ + ip str to list + """ + return ip_str.split(',') + + +@register.filter(name='key_exist') +def key_exist(username): + """ + ssh key is exist or not + """ + if os.path.isfile(os.path.join(KEY_DIR, 'user', username+'.pem')): + return True + else: + return False + + +@register.filter(name='check_role') +def check_role(asset_id, user): + """ + ssh key is exist or not + """ + return user + + +@register.filter(name='role_contain_which_sudos') +def role_contain_which_sudos(role): + """ + get role sudo commands + """ + sudo_names = [sudo.name for sudo in role.sudo.all()] + return ','.join(sudo_names) + + +@register.filter(name='get_push_info') +def get_push_info(push_id, arg): + push = get_object(PermPush, id=push_id) + if push and arg: + if arg == 'asset': + return [asset.hostname for asset in push.asset.all()] + if arg == 'asset_group': + return [asset_group.name for asset_group in push.asset_group.all()] + if arg == 'role': + return [role.name for role in push.role.all()] + else: + return [] + +<<<<<<< HEAD @register.filter(name='sudo_cmd_count') def sudo_cmd_count(cmd_group_id): cmd_group = CmdGroup.objects.filter(id=cmd_group_id) @@ -367,22 +283,36 @@ def sudo_cmd_count(cmd_group_id): return len(set(cmd_group.cmd.split(','))) else: return 0 +======= + +@register.filter(name='get_cpu_core') +def get_cpu_core(cpu_info): + cpu_core = cpu_info.split('* ')[1] if cpu_info and '*' in cpu_info else cpu_info + return cpu_core +>>>>>>> dev -@register.filter(name='sudo_cmd_ids') -def sudo_cmd_ids(user_group_id): - user_group = UserGroup.objects.filter(id=user_group_id) - if user_group: - user_group = user_group[0] - cmd_groups = [] - for perm in user_group.sudoperm_set.all(): - cmd_groups.extend(perm.cmd_group.all()) - cmd_ids = [str(cmd_group.id) for cmd_group in cmd_groups] - return ','.join(cmd_ids) +@register.filter(name='get_disk_info') +def get_disk_info(disk_info): + try: + disk_size = 0 + if disk_info: + disk_dic = ast.literal_eval(disk_info) + for disk, size in disk_dic.items(): + disk_size += size + disk_size = int(disk_size) + else: + disk_size = '' + except Exception: + disk_size = '' + return disk_size + + +@register.filter(name='user_perm_asset_num') +def user_perm_asset_num(user_id): + user = get_object(User, id=user_id) + if user: + user_perm_info = get_group_user_perm(user) + return len(user_perm_info.get('asset').keys()) else: - return '0' - - -@register.filter(name='cmd_group_split') -def cmd_group_split(cmd_group): - return cmd_group.cmd.split(',') + return 0 diff --git a/jumpserver/urls.py b/jumpserver/urls.py index bd60d04ba..4bce88592 100644 --- a/jumpserver/urls.py +++ b/jumpserver/urls.py @@ -1,22 +1,20 @@ from django.conf.urls import patterns, include, url -urlpatterns = patterns('', +urlpatterns = patterns('jumpserver.views', # Examples: - (r'^$', 'jumpserver.views.index'), - (r'^api/user/$', 'jumpserver.api.api_user'), - (r'^skin_config/$', 'jumpserver.views.skin_config'), - (r'^install/$', 'jumpserver.views.install'), - (r'^base/$', 'jumpserver.views.base'), - (r'^login/$', 'jumpserver.views.login'), - (r'^logout/$', 'jumpserver.views.logout'), - (r'^file/upload/$', 'jumpserver.views.upload'), - (r'^file/download/$', 'jumpserver.views.download'), - (r'^error/$', 'jumpserver.views.httperror'), - (r'^juser/', include('juser.urls')), - (r'^jasset/', include('jasset.urls')), - (r'^jlog/', include('jlog.urls')), - (r'^jperm/', include('jperm.urls')), - (r'^node_auth/', 'jumpserver.views.node_auth'), - + url(r'^$', 'index', name='index'), + # url(r'^api/user/$', 'api_user'), + url(r'^skin_config/$', 'skin_config', name='skin_config'), + url(r'^login/$', 'Login', name='login'), + url(r'^logout/$', 'Logout', name='logout'), + url(r'^exec_cmd/$', 'exec_cmd', name='exec_cmd'), + url(r'^file/upload/$', 'upload', name='file_upload'), + url(r'^file/download/$', 'download', name='file_download'), + url(r'^setting', 'setting', name='setting'), + url(r'^terminal/$', 'web_terminal', name='terminal'), + url(r'^juser/', include('juser.urls')), + url(r'^jasset/', include('jasset.urls')), + url(r'^jlog/', include('jlog.urls')), + url(r'^jperm/', include('jperm.urls')), ) diff --git a/jumpserver/views.py b/jumpserver/views.py index 31f5b9cfa..bd00aed99 100644 --- a/jumpserver/views.py +++ b/jumpserver/views.py @@ -1,83 +1,98 @@ # coding: utf-8 from __future__ import division +import uuid +import urllib + from django.db.models import Count from django.shortcuts import render_to_response from django.template import RequestContext from django.http import HttpResponseNotFound -from jperm.models import Apply +from django.http import HttpResponse +# from jperm.models import Apply import paramiko from jumpserver.api import * -import uuid -import urllib +from jumpserver.models import Setting +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required +from jlog.models import Log, FileLog +from jperm.perm_api import get_group_user_perm, gen_resource +from jasset.models import Asset, IDC +from jperm.ansible_api import MyRunner def getDaysByNum(num): + """ + 输出格式:([datetime.date(2015, 11, 6), datetime.date(2015, 11, 8)], ['11-06', '11-08']) + """ + today = datetime.date.today() oneday = datetime.timedelta(days=1) - li_date, li_str = [], [] + date_li, date_str = [], [] for i in range(0, num): today = today-oneday - li_date.append(today) - li_str.append(str(today)[5:10]) - li_date.reverse() - li_str.reverse() - t = (li_date, li_str) - return t + date_li.append(today) + date_str.append(str(today)[5:10]) + date_li.reverse() + date_str.reverse() + return date_li, date_str -def get_data(data, items, option): - dic = {} - li_date, li_str = getDaysByNum(7) - for item in items: - li = [] - name = item[option] - if option == 'user': - option_data = data.filter(user=name) - elif option == 'host': - option_data = data.filter(host=name) - for t in li_date: - year, month, day = t.year, t.month, t.day - times = option_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).count() - li.append(times) - dic[name] = li - return dic +def get_data(x, y, z): + pass -@require_login -def index_cu(request): - user_id = request.session.get('user_id') - user = User.objects.filter(id=user_id) - if user: - user = user[0] - login_types = {'L': 'LDAP', 'M': 'MAP'} - user_id = request.session.get('user_id') - username = User.objects.get(id=user_id).username - posts = user_perm_asset_api(username) - host_count = len(posts) - new_posts = [] - post_five = [] - for post in posts: - if len(post_five) < 5: - post_five.append(post) +def get_data_by_day(date_li, item): + data_li = [] + for d in date_li: + logs = Log.objects.filter(start_time__year=d.year, + start_time__month=d.month, + start_time__day=d.day) + if item == 'user': + data_li.append(set([log.user for log in logs])) + elif item == 'asset': + data_li.append(set([log.host for log in logs])) + elif item == 'login': + data_li.append(logs) else: - new_posts.append(post_five) - post_five = [] - new_posts.append(post_five) - - return render_to_response('index_cu.html', locals(), context_instance=RequestContext(request)) + pass + return data_li -@require_login +def get_count_by_day(date_li, item): + data_li = get_data_by_day(date_li, item) + data_count_li = [] + for data in data_li: + data_count_li.append(len(data)) + return data_count_li + + +def get_count_by_date(date_li, item): + data_li = get_data_by_day(date_li, item) + data_count_tmp = [] + for data in data_li: + data_count_tmp.extend(list(data)) + + return len(set(data_count_tmp)) + + +@require_role(role='user') +def index_cu(request): + username = request.user.username + return HttpResponseRedirect(reverse('user_detail')) + + +@require_role(role='user') def index(request): li_date, li_str = getDaysByNum(7) today = datetime.datetime.now().day from_week = datetime.datetime.now() - datetime.timedelta(days=7) - if is_common_user(request): + if is_role_request(request, 'user'): return index_cu(request) - elif is_super_user(request): + elif is_role_request(request, 'super'): + # dashboard 显示汇总 users = User.objects.all() hosts = Asset.objects.all() online = Log.objects.filter(is_finished=0) @@ -85,68 +100,52 @@ def index(request): online_user = online.values('user').distinct() active_users = User.objects.filter(is_active=1) active_hosts = Asset.objects.filter(is_active=1) + + # 一个月历史汇总 + date_li, date_str = getDaysByNum(30) + date_month = repr(date_str) + active_user_per_month = str(get_count_by_day(date_li, 'user')) + active_asset_per_month = str(get_count_by_day(date_li, 'asset')) + active_login_per_month = str(get_count_by_day(date_li, 'login')) + + # 活跃用户资产图 + active_user_month = get_count_by_date(date_li, 'user') + disabled_user_count = len(users.filter(is_active=False)) + inactive_user_month = len(users) - active_user_month + active_asset_month = get_count_by_date(date_li, 'asset') + disabled_asset_count = len(hosts.filter(is_active=False)) if hosts.filter(is_active=False) else 0 + inactive_asset_month = len(hosts) - active_asset_month if len(hosts) > active_asset_month else 0 + + # 一周top10用户和主机 week_data = Log.objects.filter(start_time__range=[from_week, datetime.datetime.now()]) + user_top_ten = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:10] + host_top_ten = week_data.values('host').annotate(times=Count('host')).order_by('-times')[:10] - elif is_group_admin(request): - user = get_session_user_info(request)[2] - dept_name, dept = get_session_user_info(request)[4:] - users = User.objects.filter(dept=dept) - hosts = Asset.objects.filter(dept=dept) - online = Log.objects.filter(dept_name=dept_name, is_finished=0) - online_host = online.values('host').distinct() - online_user = online.values('user').distinct() - active_users = users.filter(is_active=1) - active_hosts = hosts.filter(is_active=1) - week_data = Log.objects.filter(dept_name=dept_name, start_time__range=[from_week, datetime.datetime.now()]) + for user_info in user_top_ten: + username = user_info.get('user') + last = Log.objects.filter(user=username).latest('start_time') + user_info['last'] = last - # percent of dashboard - if users.count() == 0: - percent_user, percent_online_user = '0%', '0%' - else: - percent_user = format(active_users.count() / users.count(), '.0%') - percent_online_user = format(online_user.count() / users.count(), '.0%') - if hosts.count() == 0: - percent_host, percent_online_host = '0%', '0%' - else: - percent_host = format(active_hosts.count() / hosts.count(), '.0%') - percent_online_host = format(online_host.count() / hosts.count(), '.0%') + for host_info in host_top_ten: + host = host_info.get('host') + last = Log.objects.filter(host=host).latest('start_time') + host_info['last'] = last - user_top_ten = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:10] - host_top_ten = week_data.values('host').annotate(times=Count('host')).order_by('-times')[:10] - user_dic, host_dic = get_data(week_data, user_top_ten, 'user'), get_data(week_data, host_top_ten, 'host') + # 一周top5 + week_users = week_data.values('user').distinct().count() + week_hosts = week_data.count() - # a week data - week_users = week_data.values('user').distinct().count() - week_hosts = week_data.count() + user_top_five = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:5] + color = ['label-success', 'label-info', 'label-primary', 'label-default', 'label-warnning'] - user_top_five = week_data.values('user').annotate(times=Count('user')).order_by('-times')[:5] - color = ['label-success', 'label-info', 'label-primary', 'label-default', 'label-warnning'] + # 最后10次权限申请 + # perm apply latest 10 + # perm_apply_10 = Apply.objects.order_by('-date_add')[:10] - # perm apply latest 10 - perm_apply_10 = Apply.objects.order_by('-date_add')[:10] + # 最后10次登陆 + login_10 = Log.objects.order_by('-start_time')[:10] + login_more_10 = Log.objects.order_by('-start_time')[10:21] - # latest 10 login - login_10 = Log.objects.order_by('-start_time')[:10] - login_more_10 = Log.objects.order_by('-start_time')[10:21] - - # a week top 10 - for user_info in user_top_ten: - username = user_info.get('user') - last = Log.objects.filter(user=username).latest('start_time') - user_info['last'] = last - - top = {'user': '活跃用户数', 'host': '活跃主机数', 'times': '登录次数'} - top_dic = {} - for key, value in top.items(): - li = [] - for t in li_date: - year, month, day = t.year, t.month, t.day - if key != 'times': - times = week_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).values(key).distinct().count() - else: - times = week_data.filter(start_time__year=year, start_time__month=month, start_time__day=day).count() - li.append(times) - top_dic[value] = li return render_to_response('index.html', locals(), context_instance=RequestContext(request)) @@ -154,34 +153,6 @@ def skin_config(request): return render_to_response('skin_config.html') -def pages(posts, r): - """分页公用函数""" - contact_list = posts - p = paginator = Paginator(contact_list, 10) - try: - current_page = int(r.GET.get('page', '1')) - except ValueError: - current_page = 1 - - page_range = page_list_return(len(p.page_range), current_page) - - try: - contacts = paginator.page(current_page) - except (EmptyPage, InvalidPage): - contacts = paginator.page(paginator.num_pages) - - if current_page >= 5: - show_first = 1 - else: - show_first = 0 - if current_page <= (len(p.page_range) - 3): - show_end = 1 - else: - show_end = 0 - - return contact_list, p, contacts, page_range, current_page, show_first, show_end - - def is_latest(): node = uuid.getnode() jsn = uuid.UUID(int=node).hex[-12:] @@ -193,137 +164,195 @@ def is_latest(): pass -def login(request): +@defend_attack +def Login(request): """登录界面""" - if request.session.get('username'): - return HttpResponseRedirect('/') + error = '' + if request.user.is_authenticated(): + return HttpResponseRedirect(reverse('index')) if request.method == 'GET': return render_to_response('login.html') else: username = request.POST.get('username') password = request.POST.get('password') - user_filter = User.objects.filter(username=username) - if user_filter: - user = user_filter[0] - if md5_crypt(password) == user.password: - request.session['user_id'] = user.id - user_filter.update(last_login=datetime.datetime.now()) - if user.role == 'SU': - request.session['role_id'] = 2 - elif user.role == 'DA': - request.session['role_id'] = 1 + if username and password: + user = authenticate(username=username, password=password) + if user is not None: + if user.is_active: + login(request, user) + # c = {} + # c.update(csrf(request)) + # request.session['csrf_token'] = str(c.get('csrf_token')) + # user_filter = User.objects.filter(username=username) + # if user_filter: + # user = user_filter[0] + # if PyCrypt.md5_crypt(password) == user.password: + # request.session['user_id'] = user.id + # user_filter.update(last_login=datetime.datetime.now()) + if user.role == 'SU': + request.session['role_id'] = 2 + elif user.role == 'GA': + request.session['role_id'] = 1 + else: + request.session['role_id'] = 0 + return HttpResponseRedirect(request.session.get('pre_url', '/')) + # response.set_cookie('username', username, expires=604800) + # response.set_cookie('seed', PyCrypt.md5_crypt(password), expires=604800) + # return response else: - request.session['role_id'] = 0 - response = HttpResponseRedirect('/', ) - response.set_cookie('username', username, expires=604800) - response.set_cookie('seed', md5_crypt(password), expires=604800) - return response + error = '用户未激活' else: - error = '密码错误,请重新输入。' + error = '用户名或密码错误' else: - error = '用户不存在。' + error = '用户名或密码错误' return render_to_response('login.html', {'error': error}) -def logout(request): - request.session.delete() - return HttpResponseRedirect('/login/') +@require_role('user') +def Logout(request): + logout(request) + return HttpResponseRedirect(reverse('index')) -def filter_ajax_api(request): - attr = request.GET.get('attr', 'user') - value = request.GET.get('value', '') - if attr == 'user': - contact_list = User.objects.filter(name__icontains=value) - elif attr == "user_group": - contact_list = UserGroup.objects.filter(name__icontains=value) - elif attr == "asset": - contact_list = Asset.objects.filter(ip__icontains=value) - elif attr == "asset": - contact_list = BisGroup.objects.filter(name__icontains=value) +@require_role('admin') +def setting(request): + header_title, path1 = '项目设置', '设置' + setting_default = get_object(Setting, name='default') - return render_to_response('filter_ajax_api.html', locals()) + if request.method == "POST": + setting_raw = request.POST.get('setting', '') + if setting_raw == 'default': + username = request.POST.get('username', '') + port = request.POST.get('port', '') + password = request.POST.get('password', '') + private_key = request.POST.get('key', '') + + if '' in [username, port]: + return HttpResponse('所填内容不能为空, 且密码和私钥填一个') + else: + private_key_dir = os.path.join(BASE_DIR, 'keys', 'default') + private_key_path = os.path.join(private_key_dir, 'admin_user.pem') + mkdir(private_key_dir) + + if private_key: + with open(private_key_path, 'w') as f: + f.write(private_key) + os.chmod(private_key_path, 0600) + + if setting_default: + if password: + password_encode = CRYPTOR.encrypt(password) + else: + password_encode = password + Setting.objects.filter(name='default').update(field1=username, field2=port, + field3=password_encode, + field4=private_key_path) + + else: + password_encode = CRYPTOR.encrypt(password) + setting_r = Setting(name='default', field1=username, field2=port, + field3=password_encode, + field4=private_key_path).save() + + msg = "设置成功" + return my_render('setting.html', locals(), request) -def install(request): - from juser.models import DEPT, User - if User.objects.filter(id=5000): - return httperror(request, 'Jumpserver已初始化,不能重复安装!') +@login_required(login_url='/login') +def upload(request): + user = request.user + assets = get_group_user_perm(user).get('asset').keys() + asset_select = [] + if request.method == 'POST': + remote_ip = request.META.get('REMOTE_ADDR') + asset_ids = request.POST.getlist('asset_ids', '') + upload_files = request.FILES.getlist('file[]', None) + date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + upload_dir = get_tmp_dir() + # file_dict = {} + for asset_id in asset_ids: + asset_select.append(get_object(Asset, id=asset_id)) - dept = DEPT(id=1, name="超管部", comment="超级管理部门") - dept.save() - dept2 = DEPT(id=2, name="默认", comment="默认部门") - dept2.save() - IDC(id=1, name="默认", comment="默认IDC").save() - BisGroup(id=1, name="ALL", dept=dept, comment="所有主机组").save() + if not set(asset_select).issubset(set(assets)): + illegal_asset = set(asset_select).issubset(set(assets)) + return HttpResponse('没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset])) - User(id=5000, username="admin", password=md5_crypt('admin'), - name='admin', email='admin@jumpserver.org', role='SU', is_active=True, dept=dept).save() - return success(request, u'Jumpserver初始化成功') + for upload_file in upload_files: + file_path = '%s/%s' % (upload_dir, upload_file.name) + with open(file_path, 'w') as f: + for chunk in upload_file.chunks(): + f.write(chunk) + + res = gen_resource({'user': user, 'asset': asset_select}) + runner = MyRunner(res) + runner.run('copy', module_args='src=%s dest=%s directory_mode' + % (upload_dir, upload_dir), pattern='*') + ret = runner.results + logger.debug(ret) + FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]), + filename=' '.join([f.name for f in upload_files]), type='upload', remote_ip=remote_ip, + result=ret).save() + if ret.get('failed'): + error = u'上传目录: %s
上传失败: [ %s ]
上传成功 [ %s ]' % (upload_dir, + ', '.join(ret.get('failed').keys()), + ', '.join(ret.get('ok').keys())) + return HttpResponse(error, status=500) + msg = u'上传目录: %s
传送成功 [ %s ]' % (upload_dir, ', '.join(ret.get('ok').keys())) + return HttpResponse(msg) + return my_render('upload.html', locals(), request) +@login_required(login_url='/login') def download(request): + user = request.user + assets = get_group_user_perm(user).get('asset').keys() + asset_select = [] + if request.method == 'POST': + remote_ip = request.META.get('REMOTE_ADDR') + asset_ids = request.POST.getlist('asset_ids', '') + file_path = request.POST.get('file_path') + date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + upload_dir = get_tmp_dir() + for asset_id in asset_ids: + asset_select.append(get_object(Asset, id=asset_id)) + + if not set(asset_select).issubset(set(assets)): + illegal_asset = set(asset_select).issubset(set(assets)) + return HttpResponse(u'没有权限的服务器 %s' % ','.join([asset.hostname for asset in illegal_asset])) + + res = gen_resource({'user': user, 'asset': asset_select}) + runner = MyRunner(res) + runner.run('fetch', module_args='src=%s dest=%s' % (file_path, upload_dir), pattern='*') + FileLog(user=request.user.username, host=' '.join([asset.hostname for asset in asset_select]), + filename=file_path, type='download', remote_ip=remote_ip, result=runner.results).save() + logger.debug(runner.results) + os.chdir('/tmp') + tmp_dir_name = os.path.basename(upload_dir) + tar_file = '%s.tar.gz' % upload_dir + bash('tar czf %s %s' % (tar_file, tmp_dir_name)) + f = open(tar_file) + data = f.read() + f.close() + response = HttpResponse(data, content_type='application/octet-stream') + response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(tar_file) + return response + return render_to_response('download.html', locals(), context_instance=RequestContext(request)) -def transfer(sftp, filenames): - # pool = Pool(processes=5) - for filename, file_path in filenames.items(): - print filename, file_path - sftp.put(file_path, '/tmp/%s' % filename) - # pool.apply_async(transfer, (sftp, file_path, '/tmp/%s' % filename)) - sftp.close() - # pool.close() - # pool.join() +@login_required(login_url='/login') +def exec_cmd(request): + role = request.GET.get('role') + check_assets = request.GET.get('check_assets', '') + web_terminal_uri = 'ws://%s/exec?role=%s' % (WEB_SOCKET_HOST, role) + return my_render('exec_cmd.html', locals(), request) -def upload(request): - user, dept = get_session_user_dept(request) - if request.method == 'POST': - hosts = request.POST.get('hosts') - upload_files = request.FILES.getlist('file[]', None) - upload_dir = "/tmp/%s" % user.username - is_dir(upload_dir) - date_now = datetime.datetime.now().strftime("%Y%m%d%H%M%S") - hosts_list = hosts.split(',') - user_hosts = get_user_host(user.username).keys() - unperm_hosts = [] - filenames = {} - for ip in hosts_list: - if ip not in user_hosts: - unperm_hosts.append(ip) +@require_role('user') +def web_terminal(request): + asset_id = request.GET.get('id') + role_name = request.GET.get('role') + web_terminal_uri = 'ws://%s/terminal?id=%s&role=%s' % (WEB_SOCKET_HOST, asset_id, role_name) + return render_to_response('jlog/web_terminal.html', locals()) - if not hosts: - return HttpResponseNotFound(u'地址不能为空') - if unperm_hosts: - print hosts_list - return HttpResponseNotFound(u'%s 没有权限.' % ', '.join(unperm_hosts)) - - for upload_file in upload_files: - file_path = '%s/%s.%s' % (upload_dir, upload_file.name, date_now) - filenames[upload_file.name] = file_path - f = open(file_path, 'w') - for chunk in upload_file.chunks(): - f.write(chunk) - f.close() - - sftps = [] - for host in hosts_list: - username, password, host, port = get_connect_item(user.username, host) - try: - t = paramiko.Transport((host, port)) - t.connect(username=username, password=password) - sftp = paramiko.SFTPClient.from_transport(t) - sftps.append(sftp) - except paramiko.AuthenticationException: - return HttpResponseNotFound(u'%s 连接失败.' % host) - - # pool = Pool(processes=5) - for sftp in sftps: - transfer(sftp, filenames) - # pool.close() - # pool.join() - return HttpResponse('传送成功') - - return render_to_response('upload.html', locals(), context_instance=RequestContext(request)) diff --git a/juser/models.py b/juser/models.py index d7efd7a28..54d1b94a0 100644 --- a/juser/models.py +++ b/juser/models.py @@ -1,41 +1,54 @@ +# coding: utf-8 + from django.db import models - - -class DEPT(models.Model): - name = models.CharField(max_length=80, unique=True) - comment = models.CharField(max_length=160, blank=True, null=True) - - def __unicode__(self): - return self.name +from django.contrib.auth.models import AbstractUser +import time +# from jasset.models import Asset, AssetGroup class UserGroup(models.Model): name = models.CharField(max_length=80, unique=True) - dept = models.ForeignKey(DEPT) comment = models.CharField(max_length=160, blank=True, null=True) def __unicode__(self): return self.name -class User(models.Model): +class User(AbstractUser): USER_ROLE_CHOICES = ( ('SU', 'SuperUser'), - ('DA', 'DeptAdmin'), + ('GA', 'GroupAdmin'), ('CU', 'CommonUser'), ) - username = models.CharField(max_length=80, unique=True) - password = models.CharField(max_length=100) name = models.CharField(max_length=80) - email = models.EmailField(max_length=75) + uuid = models.CharField(max_length=100) role = models.CharField(max_length=2, choices=USER_ROLE_CHOICES, default='CU') - dept = models.ForeignKey(DEPT) group = models.ManyToManyField(UserGroup) - ldap_pwd = models.CharField(max_length=100) - ssh_key_pwd = models.CharField(max_length=100) - is_active = models.BooleanField(default=True) - last_login = models.DateTimeField(null=True) - date_joined = models.DateTimeField(null=True) + ssh_key_pwd = models.CharField(max_length=200) + # is_active = models.BooleanField(default=True) + # last_login = models.DateTimeField(null=True) + # date_joined = models.DateTimeField(null=True) def __unicode__(self): return self.username + + +class AdminGroup(models.Model): + """ + under the user control group + 用户可以管理的用户组,或组的管理员是该用户 + """ + + user = models.ForeignKey(User) + group = models.ForeignKey(UserGroup) + + def __unicode__(self): + return '%s: %s' % (self.user.username, self.group.name) + + +class Document(models.Model): + def upload_to(self, filename): + return 'upload/'+str(self.user.id)+time.strftime('/%Y/%m/%d/', time.localtime())+filename + + docfile = models.FileField(upload_to=upload_to) + user = models.ForeignKey(User) diff --git a/juser/tests.py b/juser/tests.py deleted file mode 100644 index 7ce503c2d..000000000 --- a/juser/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/juser/urls.py b/juser/urls.py index cbaba7bb0..47952dd11 100644 --- a/juser/urls.py +++ b/juser/urls.py @@ -3,31 +3,23 @@ from jumpserver.api import view_splitter from juser.views import * urlpatterns = patterns('juser.views', - # Examples: - # url(r'^$', 'jumpserver.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - - (r'^dept_list/$', view_splitter, {'su': dept_list, 'adm': dept_list_adm}), - (r'^dept_add/$', 'dept_add'), - (r'^dept_del/$', 'dept_del'), - (r'^dept_detail/$', 'dept_detail'), - (r'^dept_del_ajax/$', 'dept_del_ajax'), - (r'^dept_edit/$', 'dept_edit'), - (r'^dept_user_ajax/$', 'dept_user_ajax'), - (r'^group_add/$', view_splitter, {'su': group_add, 'adm': group_add_adm}), - (r'^group_list/$', view_splitter, {'su': group_list, 'adm': group_list_adm}), - (r'^group_detail/$', 'group_detail'), - (r'^group_del/$', view_splitter, {'su': group_del, 'adm': group_del_adm}), - (r'^group_del_ajax/$', 'group_del_ajax'), - (r'^group_edit/$', view_splitter, {'su': group_edit, 'adm': group_edit_adm}), - (r'^user_add/$', view_splitter, {'su': user_add, 'adm': user_add_adm}), - (r'^user_list/$', view_splitter, {'su': user_list, 'adm': user_list_adm}), - (r'^user_detail/$', 'user_detail'), - (r'^user_del/$', 'user_del'), - (r'^user_del_ajax/$', 'user_del_ajax'), - (r'^user_edit/$', view_splitter, {'su': user_edit, 'adm': user_edit_adm}), - (r'^profile/$', 'profile'), - (r'^chg_info/$', 'chg_info'), - (r'^chg_role/$', 'chg_role'), - (r'^down_key/$', 'down_key'), -) + # Examples: + # url(r'^$', 'jumpserver.views.home', name='home'), + # url(r'^blog/', include('blog.urls')), + url(r'^group/add/$', 'group_add', name='user_group_add'), + url(r'^group/list/$', 'group_list', name='user_group_list'), + url(r'^group/del/$', 'group_del', name='user_group_del'), + url(r'^group/edit/$', 'group_edit', name='user_group_edit'), + url(r'^user/add/$', 'user_add', name='user_add'), + url(r'^user/del/$', 'user_del', name='user_del'), + url(r'^user/list/$', 'user_list', name='user_list'), + url(r'^user/edit/$', 'user_edit', name='user_edit'), + url(r'^user/detail/$', 'user_detail', name='user_detail'), + url(r'^user/profile/$', 'profile', name='user_profile'), + url(r'^user/update/$', 'change_info', name='user_update'), + url(r'^mail/retry/$', 'send_mail_retry', name='mail_retry'), + url(r'^password/reset/$', 'reset_password', name='password_reset'), + url(r'^password/forget/$', 'forget_password', name='password_forget'), + url(r'^key/gen/$', 'regen_ssh_key', name='key_gen'), + url(r'^key/down/$', 'down_key', name='key_down'), + ) diff --git a/juser/views.py b/juser/views.py index 22a08499b..d3059a460 100644 --- a/juser/views.py +++ b/juser/views.py @@ -2,17 +2,19 @@ # Author: Guanghongwei # Email: ibuler@qq.com -import random -from Crypto.PublicKey import RSA -import crypt +# import random +# from Crypto.PublicKey import RSA +import uuid +from django.contrib.auth.decorators import login_required -from django.shortcuts import render_to_response from django.db.models import Q -from django.template import RequestContext +from juser.user_api import * +from jperm.perm_api import get_group_user_perm -from jumpserver.api import * +MAIL_FROM = EMAIL_HOST_USER +<<<<<<< HEAD def gen_rand_pwd(num): """生成随机密码""" seed = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -333,718 +335,455 @@ def dept_user_ajax(request): @require_super_user +======= +@require_role(role='super') +>>>>>>> dev def group_add(request): + """ + group add view for route + 添加用户组的视图 + """ error = '' msg = '' - header_title, path1, path2 = '添加小组', '用户管理', '添加小组' + header_title, path1, path2 = '添加用户组', '用户管理', '添加用户组' user_all = User.objects.all() - dept_all = DEPT.objects.all() if request.method == 'POST': group_name = request.POST.get('group_name', '') - dept_id = request.POST.get('dept_id', '') users_selected = request.POST.getlist('users_selected', '') comment = request.POST.get('comment', '') try: - if '' in [group_name, dept_id]: - error = u'组名 或 部门 不能为空' - raise AddError(error) + if not group_name: + error = u'组名 不能为空' + raise ServerError(error) if UserGroup.objects.filter(name=group_name): error = u'组名已存在' - raise AddError(error) - - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - else: - error = u'部门不存在' - raise AddError(error) - - db_add_group(name=group_name, users=users_selected, dept=dept, comment=comment) - except AddError: + raise ServerError(error) + db_add_group(name=group_name, users_id=users_selected, comment=comment) + except ServerError: pass except TypeError: - error = u'保存小组失败' + error = u'添加小组失败' else: msg = u'添加组 %s 成功' % group_name - return render_to_response('juser/group_add.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/group_add.html', locals(), request) -@require_admin -def group_add_adm(request): - error = '' - msg = '' - header_title, path1, path2 = '添加小组', '用户管理', '添加小组' - user, dept = get_session_user_dept(request) - user_all = dept.user_set.all() - - if request.method == 'POST': - group_name = request.POST.get('group_name', '') - users_selected = request.POST.getlist('users_selected', '') - comment = request.POST.get('comment', '') - - try: - if not validate(request, user=users_selected): - raise AddError('没有某用户权限') - if '' in [group_name]: - error = u'组名不能为空' - raise AddError(error) - - db_add_group(name=group_name, users=users_selected, dept=dept, comment=comment) - except AddError: - pass - except TypeError: - error = u'保存小组失败' - else: - msg = u'添加组 %s 成功' % group_name - - return render_to_response('juser/group_add.html', locals(), context_instance=RequestContext(request)) - - -@require_super_user +@require_role(role='super') def group_list(request): - header_title, path1, path2 = '查看小组', '用户管理', '查看小组' + """ + list user group + 用户组列表 + """ + header_title, path1, path2 = '查看用户组', '用户管理', '查看用户组' keyword = request.GET.get('search', '') - did = request.GET.get('did', '') - contact_list = UserGroup.objects.all().order_by('name') - - if did: - dept = DEPT.objects.filter(id=did) - if dept: - dept = dept[0] - contact_list = dept.usergroup_set.all() + user_group_list = UserGroup.objects.all().order_by('name') + group_id = request.GET.get('id', '') if keyword: - contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) + user_group_list = user_group_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('juser/group_list.html', locals(), context_instance=RequestContext(request)) + if group_id: + user_group_list = user_group_list.filter(id=int(group_id)) + + user_group_list, p, user_groups, page_range, current_page, show_first, show_end = pages(user_group_list, request) + return my_render('juser/group_list.html', locals(), request) -@require_admin -def group_list_adm(request): - header_title, path1, path2 = '查看部门小组', '用户管理', '查看小组' - keyword = request.GET.get('search', '') - did = request.GET.get('did', '') - user, dept = get_session_user_dept(request) - contact_list = dept.usergroup_set.all().order_by('name') - - if keyword: - contact_list = contact_list.filter(Q(name__icontains=keyword) | Q(comment__icontains=keyword)) - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - return render_to_response('juser/group_list.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def group_detail(request): - group_id = request.GET.get('id', None) - if not group_id: - return HttpResponseRedirect('/') - group = UserGroup.objects.get(id=group_id) - users = group.user_set.all() - return render_to_response('juser/group_detail.html', locals(), context_instance=RequestContext(request)) - - -@require_super_user +@require_role(role='super') def group_del(request): - group_id = request.GET.get('id', '') - if not group_id: - return HttpResponseRedirect('/') - UserGroup.objects.filter(id=group_id).delete() - return HttpResponseRedirect('/juser/group_list/') - - -@require_admin -def group_del_adm(request): - group_id = request.GET.get('id', '') - if not validate(request, user_group=[group_id]): - return HttpResponseRedirect('/juser/group_list/') - if not group_id: - return HttpResponseRedirect('/') - UserGroup.objects.filter(id=group_id).delete() - return HttpResponseRedirect('/juser/group_list/') - - -@require_admin -def group_del_ajax(request): - group_ids = request.POST.get('group_ids') - group_ids = group_ids.split(',') - if request.session.get('role_id') == 1: - if not validate(request, user_group=group_ids): - return "error" - for group_id in group_ids: + """ + del a group + 删除用户组 + """ + group_ids = request.GET.get('id', '') + group_id_list = group_ids.split(',') + for group_id in group_id_list: UserGroup.objects.filter(id=group_id).delete() + return HttpResponse('删除成功') -def group_update_member(group_id, users_id_list): - group = UserGroup.objects.filter(id=group_id) - if group: - group = group[0] - group.user_set.clear() - for user_id in users_id_list: - user = User.objects.get(id=user_id) - group.user_set.add(user) - - -@require_super_user +@require_role(role='super') def group_edit(request): error = '' msg = '' - header_title, path1, path2 = '修改小组信息', '用户管理', '编辑小组' + header_title, path1, path2 = '编辑用户组', '用户管理', '编辑用户组' + if request.method == 'GET': group_id = request.GET.get('id', '') - group = UserGroup.objects.filter(id=group_id) - if group: - group = group[0] - dept_all = DEPT.objects.all() - users_all = User.objects.all() - users_selected = group.user_set.all() - users = [user for user in users_all if user not in users_selected] + user_group = get_object(UserGroup, id=group_id) + # user_group = UserGroup.objects.get(id=group_id) + users_selected = User.objects.filter(group=user_group) + users_remain = User.objects.filter(~Q(group=user_group)) + users_all = User.objects.all() - return render_to_response('juser/group_edit.html', locals(), context_instance=RequestContext(request)) - else: + elif request.method == 'POST': group_id = request.POST.get('group_id', '') group_name = request.POST.get('group_name', '') - dept_id = request.POST.get('dept_id', '') comment = request.POST.get('comment', '') users_selected = request.POST.getlist('users_selected') - users = [] try: if '' in [group_id, group_name]: - raise AddError('组名不能为空') - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - else: - raise AddError('部门不存在') - for user_id in users_selected: - users.extend(User.objects.filter(id=user_id)) + raise ServerError('组名不能为空') - user_group = UserGroup.objects.filter(id=group_id) - if user_group: - user_group.update(name=group_name, comment=comment, dept=dept) - user_group = user_group[0] - user_group.user_set.clear() - user_group.user_set = users - - except AddError, e: + if len(UserGroup.objects.filter(name=group_name)) > 1: + raise ServerError(u'%s 用户组已存在' % group_name) + # add user group + for user in User.objects.filter(id__in=users_selected): + user.group.add(UserGroup.objects.get(id=group_id)) + # delete user group + user_group = UserGroup.objects.get(id=group_id) + for user in [user for user in User.objects.filter(group=user_group) if user not in User.objects.filter(id__in=users_selected)]: + user_group_all = user.group.all() + user.group.clear() + for g in user_group_all: + if g == user_group: + continue + user.group.add(g) + user_group.name = group_name + user_group.comment = comment + user_group.save() + except ServerError, e: error = e + if not error: + return HttpResponseRedirect(reverse('user_group_list')) + else: + users_all = User.objects.all() + users_selected = User.objects.filter(group=user_group) + users_remain = User.objects.filter(~Q(group=user_group)) - return HttpResponseRedirect('/juser/group_list/') + return my_render('juser/group_edit.html', locals(), request) -@require_admin -def group_edit_adm(request): - error = '' - msg = '' - header_title, path1, path2 = '修改小组信息', '用户管理', '编辑小组' - user, dept = get_session_user_dept(request) - if request.method == 'GET': - group_id = request.GET.get('id', '') - if not validate(request, user_group=[group_id]): - return HttpResponseRedirect('/juser/group_list/') - group = UserGroup.objects.filter(id=group_id) - if group: - group = group[0] - users_all = dept.user_set.all() - users_selected = group.user_set.all() - users = [user for user in users_all if user not in users_selected] - - return render_to_response('juser/group_edit.html', locals(), context_instance=RequestContext(request)) - else: - group_id = request.POST.get('group_id', '') - group_name = request.POST.get('group_name', '') - comment = request.POST.get('comment', '') - users_selected = request.POST.getlist('users_selected') - - users = [] - try: - if not validate(request, user=users_selected): - raise AddError(u'右侧非部门用户') - - if not validate(request, user_group=[group_id]): - raise AddError(u'没有权限修改本组') - - for user_id in users_selected: - users.extend(User.objects.filter(id=user_id)) - - user_group = UserGroup.objects.filter(id=group_id) - if user_group: - user_group.update(name=group_name, comment=comment, dept=dept) - user_group = user_group[0] - user_group.user_set.clear() - user_group.user_set = users - - except AddError, e: - error = e - - return HttpResponseRedirect('/juser/group_list/') - - -@require_super_user +@require_role(role='super') def user_add(request): error = '' msg = '' header_title, path1, path2 = '添加用户', '用户管理', '添加用户' - user_role = {'SU': u'超级管理员', 'DA': u'部门管理员', 'CU': u'普通用户'} - dept_all = DEPT.objects.all() + user_role = {'SU': u'超级管理员', 'CU': u'普通用户'} group_all = UserGroup.objects.all() if request.method == 'POST': username = request.POST.get('username', '') - password = gen_rand_pwd(16) + password = PyCrypt.gen_rand_pass(16) name = request.POST.get('name', '') email = request.POST.get('email', '') - dept_id = request.POST.get('dept_id') groups = request.POST.getlist('groups', []) - role_post = request.POST.get('role', 'CU') - ssh_key_pwd = gen_rand_pwd(16) - is_active = True if request.POST.get('is_active', '1') == '1' else False - ldap_pwd = gen_rand_pwd(16) + admin_groups = request.POST.getlist('admin_groups', []) + role = request.POST.get('role', 'CU') + uuid_r = uuid.uuid4().get_hex() + ssh_key_pwd = PyCrypt.gen_rand_pass(16) + extra = request.POST.getlist('extra', []) + is_active = False if '0' in extra else True + ssh_key_login_need = True + send_mail_need = True if '2' in extra else False try: - if '' in [username, password, ssh_key_pwd, name, groups, role_post, is_active]: + if '' in [username, password, ssh_key_pwd, name, role]: error = u'带*内容不能为空' - raise AddError - user = User.objects.filter(username=username) - if user: + raise ServerError + check_user_is_exist = User.objects.filter(username=username) + if check_user_is_exist: error = u'用户 %s 已存在' % username - raise AddError + raise ServerError - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - else: - error = u'部门不存在' - raise AddError(error) - - except AddError: + except ServerError: pass else: try: - user = db_add_user(username=username, - password=md5_crypt(password), - name=name, email=email, dept=dept, - groups=groups, role=role_post, - ssh_key_pwd=md5_crypt(ssh_key_pwd), - ldap_pwd=CRYPTOR.encrypt(ldap_pwd), + user = db_add_user(username=username, name=name, + password=password, + email=email, role=role, uuid=uuid_r, + groups=groups, admin_groups=admin_groups, + ssh_key_pwd=ssh_key_pwd, is_active=is_active, date_joined=datetime.datetime.now()) + server_add_user(username, password, ssh_key_pwd, ssh_key_login_need) + user = get_object(User, username=username) + if groups: + user_groups = [] + for user_group_id in groups: + user_groups.extend(UserGroup.objects.filter(id=user_group_id)) - server_add_user(username, password, ssh_key_pwd) - if LDAP_ENABLE: - ldap_add_user(username, ldap_pwd) - mail_title = u'恭喜你的跳板机用户添加成功 Jumpserver' - mail_msg = """ - Hi, %s - 您的用户名: %s - 您的部门: %s - 您的角色: %s - 您的web登录密码: %s - 您的ssh密钥文件密码: %s - 密钥下载地址: http://%s:%s/juser/down_key/?id=%s - 说明: 请登陆后再下载密钥! - """ % (name, username, dept.name, user_role.get(role_post, ''), - password, ssh_key_pwd, SEND_IP, SEND_PORT, user.id) - - except Exception, e: + except IndexError, e: error = u'添加用户 %s 失败 %s ' % (username, e) try: db_del_user(username) server_del_user(username) - if LDAP_ENABLE: - ldap_del_user(username) except Exception: pass else: - send_mail(mail_title, mail_msg, MAIL_FROM, [email], fail_silently=False) - msg = u'添加用户 %s 成功! 用户密码已发送到 %s 邮箱!' % (username, email) - return render_to_response('juser/user_add.html', locals(), context_instance=RequestContext(request)) + if MAIL_ENABLE and send_mail_need: + user_add_mail(user, kwargs=locals()) + msg = get_display_msg(user, password, ssh_key_pwd, ssh_key_login_need, send_mail_need) + return my_render('juser/user_add.html', locals(), request) -@require_admin -def user_add_adm(request): - error = '' - msg = '' - header_title, path1, path2 = '添加用户', '用户管理', '添加用户' - user, dept = get_session_user_dept(request) - group_all = dept.usergroup_set.all() - - if request.method == 'POST': - username = request.POST.get('username', '') - password = gen_rand_pwd(16) - name = request.POST.get('name', '') - email = request.POST.get('email', '') - groups = request.POST.getlist('groups', []) - ssh_key_pwd = gen_rand_pwd(16) - is_active = True if request.POST.get('is_active', '1') == '1' else False - ldap_pwd = gen_rand_pwd(16) - - try: - if '' in [username, password, ssh_key_pwd, name, groups, is_active]: - error = u'带*内容不能为空' - raise AddError - user = User.objects.filter(username=username) - if user: - error = u'用户 %s 已存在' % username - raise AddError - - except AddError: - pass - else: - try: - user = db_add_user(username=username, - password=md5_crypt(password), - name=name, email=email, dept=dept, - groups=groups, role='CU', - ssh_key_pwd=md5_crypt(ssh_key_pwd), - ldap_pwd=CRYPTOR.encrypt(ldap_pwd), - is_active=is_active, - date_joined=datetime.datetime.now()) - - server_add_user(username, password, ssh_key_pwd) - if LDAP_ENABLE: - ldap_add_user(username, ldap_pwd) - - except Exception, e: - error = u'添加用户 %s 失败 %s ' % (username, e) - try: - db_del_user(username) - server_del_user(username) - if LDAP_ENABLE: - ldap_del_user(username) - except Exception: - pass - else: - mail_title = u'恭喜你的跳板机用户添加成功 Jumpserver' - mail_msg = """ - Hi, %s - 您的用户名: %s - 您的部门: %s - 您的角色: %s - 您的web登录密码: %s - 您的ssh密钥文件密码: %s - 密钥下载地址: http://%s:%s/juser/down_key/?id=%s - 说明: 请登陆后再下载密钥! - """ % (name, username, dept.name, '普通用户', - password, ssh_key_pwd, SEND_IP, SEND_PORT, user.id) - send_mail(mail_title, mail_msg, MAIL_FROM, [email], fail_silently=False) - msg = u'添加用户 %s 成功! 用户密码已发送到 %s 邮箱!' % (username, email) - - return render_to_response('juser/user_add.html', locals(), context_instance=RequestContext(request)) - - -@require_super_user +@require_role(role='super') def user_list(request): user_role = {'SU': u'超级管理员', 'GA': u'组管理员', 'CU': u'普通用户'} header_title, path1, path2 = '查看用户', '用户管理', '用户列表' keyword = request.GET.get('keyword', '') gid = request.GET.get('gid', '') - did = request.GET.get('did', '') - contact_list = User.objects.all().order_by('name') + users_list = User.objects.all().order_by('username') if gid: user_group = UserGroup.objects.filter(id=gid) if user_group: user_group = user_group[0] - contact_list = user_group.user_set.all() - - if did: - dept = DEPT.objects.filter(id=did) - if dept: - dept = dept[0] - contact_list = dept.user_set.all().order_by('name') + users_list = user_group.user_set.all() if keyword: - contact_list = contact_list.filter(Q(username__icontains=keyword) | Q(name__icontains=keyword)).order_by('name') + users_list = users_list.filter(Q(username__icontains=keyword) | Q(name__icontains=keyword)).order_by('username') - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) + users_list, p, users, page_range, current_page, show_first, show_end = pages(users_list, request) - return render_to_response('juser/user_list.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/user_list.html', locals(), request) -@require_admin -def user_list_adm(request): - user_role = {'SU': u'超级管理员', 'GA': u'组管理员', 'CU': u'普通用户'} - header_title, path1, path2 = '查看用户', '用户管理', '用户列表' - keyword = request.GET.get('keyword', '') - user, dept = get_session_user_dept(request) - gid = request.GET.get('gid', '') - contact_list = dept.user_set.all().order_by('name') - - if gid: - if not validate(request, user_group=[gid]): - return HttpResponseRedirect('/juser/user_list/') - user_group = UserGroup.objects.filter(id=gid) - if user_group: - user_group = user_group[0] - contact_list = user_group.user_set.all() - - if keyword: - contact_list = contact_list.filter(Q(username__icontains=keyword) | Q(name__icontains=keyword)).order_by('name') - - contact_list, p, contacts, page_range, current_page, show_first, show_end = pages(contact_list, request) - - return render_to_response('juser/user_list.html', locals(), context_instance=RequestContext(request)) - - -@require_login +@require_role(role='user') def user_detail(request): - header_title, path1, path2 = '查看用户', '用户管理', '用户详情' + header_title, path1, path2 = '用户详情', '用户管理', '用户详情' if request.session.get('role_id') == 0: - user_id = request.session.get('user_id') + user_id = request.user.id else: user_id = request.GET.get('id', '') - if request.session.get('role_id') == 1: - user, dept = get_session_user_dept(request) - if not validate(request, user=[user_id]): - return HttpResponseRedirect('/') - if not user_id: - return HttpResponseRedirect('/juser/user_list/') - user = User.objects.filter(id=user_id) - if user: - user = user[0] - asset_group_permed = user_perm_group_api(user) - logs_last = Log.objects.filter(user=user.name).order_by('-start_time')[0:10] - logs_all = Log.objects.filter(user=user.name).order_by('-start_time') - logs_num = len(logs_all) + user = get_object(User, id=user_id) + if not user: + return HttpResponseRedirect(reverse('user_list')) - return render_to_response('juser/user_detail.html', locals(), context_instance=RequestContext(request)) + user_perm_info = get_group_user_perm(user) + role_assets = user_perm_info.get('role') + user_log_ten = Log.objects.filter(user=user.username).order_by('id')[0:10] + user_log_last = Log.objects.filter(user=user.username).order_by('id')[0:50] + user_log_last_num = len(user_log_last) + + return my_render('juser/user_detail.html', locals(), request) -@require_admin +@require_role(role='admin') def user_del(request): - user_id = request.GET.get('id', '') - if not user_id: - return HttpResponseRedirect('/juser/user_list/') + if request.method == "GET": + user_ids = request.GET.get('id', '') + user_id_list = user_ids.split(',') + elif request.method == "POST": + user_ids = request.POST.get('id', '') + user_id_list = user_ids.split(',') + else: + return HttpResponse('错误请求') - if request.session.get('role_id', '') == '1': - if not validate(request, user=[user_id]): - return HttpResponseRedirect('/juser/user_list/') - - user = User.objects.filter(id=user_id) - if user and user[0].username != 'admin': - user = user[0] - user.delete() - server_del_user(user.username) - if LDAP_ENABLE: - ldap_del_user(user.username) - return HttpResponseRedirect('/juser/user_list/') - - -@require_admin -def user_del_ajax(request): - user_ids = request.POST.get('ids') - user_ids = user_ids.split(',') - if request.session.get('role_id', '') == 1: - if not validate(request, user=user_ids): - return "error" - for user_id in user_ids: - user = User.objects.filter(id=user_id) - if user and user[0].username != 'admin': - user = user[0] + for user_id in user_id_list: + user = get_object(User, id=user_id) + if user and user.username != 'admin': + logger.debug(u"删除用户 %s " % user.username) + bash('userdel -r %s' % user.username) user.delete() - server_del_user(user.username) - if LDAP_ENABLE: - ldap_del_user(user.username) - return HttpResponse('删除成功') -@require_super_user +@require_role('admin') +def send_mail_retry(request): + uuid_r = request.GET.get('uuid', '1') + user = get_object(User, uuid=uuid_r) + msg = u""" + 跳板机地址: %s + 用户名:%s + 重设密码:%s/juser/password/forget/ + 请登录web点击个人信息页面重新生成ssh密钥 + """ % (URL, user.username, URL) + + try: + send_mail(u'邮件重发', msg, MAIL_FROM, [user.email], fail_silently=False) + except IndexError: + return Http404 + return HttpResponse('发送成功') + + +@defend_attack +def forget_password(request): + if request.method == 'POST': + defend_attack(request) + email = request.POST.get('email', '') + username = request.POST.get('username', '') + name = request.POST.get('name', '') + user = get_object(User, username=username, email=email, name=name) + if user: + timestamp = int(time.time()) + hash_encode = PyCrypt.md5_crypt(str(user.uuid) + str(timestamp) + KEY) + msg = u""" + Hi %s, 请点击下面链接重设密码! + %s/juser/password/reset/?uuid=%s×tamp=%s&hash=%s + """ % (user.name, URL, user.uuid, timestamp, hash_encode) + send_mail('忘记跳板机密码', msg, MAIL_FROM, [email], fail_silently=False) + msg = u'请登陆邮箱,点击邮件重设密码' + return http_success(request, msg) + else: + error = u'用户不存在或邮件地址错误' + + return render_to_response('juser/forget_password.html', locals()) + + +@defend_attack +def reset_password(request): + uuid_r = request.GET.get('uuid', '') + timestamp = request.GET.get('timestamp', '') + hash_encode = request.GET.get('hash', '') + action = '/juser/password/reset/?uuid=%s×tamp=%s&hash=%s' % (uuid_r, timestamp, hash_encode) + + if request.method == 'POST': + password = request.POST.get('password') + password_confirm = request.POST.get('password_confirm') + print password, password_confirm + if password != password_confirm: + return HttpResponse('密码不匹配') + else: + user = get_object(User, uuid=uuid_r) + if user: + user.password = PyCrypt.md5_crypt(password) + user.save() + return http_success(request, u'密码重设成功') + else: + return HttpResponse('用户不存在') + + if hash_encode == PyCrypt.md5_crypt(uuid_r + timestamp + KEY): + if int(time.time()) - int(timestamp) > 600: + return http_error(request, u'链接已超时') + else: + return render_to_response('juser/reset_password.html', locals()) + + return http_error(request, u'错误请求') + + +@require_role(role='super') def user_edit(request): - header_title, path1, path2 = '编辑用户', '用户管理', '用户编辑' + header_title, path1, path2 = '编辑用户', '用户管理', '编辑用户' if request.method == 'GET': user_id = request.GET.get('id', '') if not user_id: - return HttpResponseRedirect('/') + return HttpResponseRedirect(reverse('index')) - user_role = {'SU': u'超级管理员', 'DA': u'部门管理员', 'CU': u'普通用户'} - user = User.objects.filter(id=user_id) - dept_all = DEPT.objects.all() + user_role = {'SU': u'超级管理员', 'CU': u'普通用户'} + user = get_object(User, id=user_id) group_all = UserGroup.objects.all() if user: - user = user[0] groups_str = ' '.join([str(group.id) for group in user.group.all()]) + admin_groups_str = ' '.join([str(admin_group.group.id) for admin_group in user.admingroup_set.all()]) else: - user_id = request.POST.get('user_id', '') + user_id = request.GET.get('id', '') password = request.POST.get('password', '') name = request.POST.get('name', '') email = request.POST.get('email', '') - dept_id = request.POST.get('dept_id') groups = request.POST.getlist('groups', []) role_post = request.POST.get('role', 'CU') - ssh_key_pwd = request.POST.get('ssh_key_pwd', '') - is_active = True if request.POST.get('is_active', '1') == '1' else False - - user_role = {'SU': u'超级管理员', 'DA': u'部门管理员', 'CU': u'普通用户'} - dept = DEPT.objects.filter(id=dept_id) - if dept: - dept = dept[0] - else: - dept = DEPT.objects.get(id='2') + admin_groups = request.POST.getlist('admin_groups', []) + extra = request.POST.getlist('extra', []) + is_active = True if '0' in extra else False + email_need = True if '2' in extra else False + user_role = {'SU': u'超级管理员', 'GA': u'部门管理员', 'CU': u'普通用户'} if user_id: - user = User.objects.filter(id=user_id) - if user: - user = user[0] + user = get_object(User, id=user_id) else: - return HttpResponseRedirect('/juser/user_list/') + return HttpResponseRedirect(reverse('user_list')) - if password != user.password: - password = md5_crypt(password) - - if ssh_key_pwd != user.ssh_key_pwd: - gen_ssh_key(user.username, ssh_key_pwd) - ssh_key_pwd = CRYPTOR.encrypt(ssh_key_pwd) + if password != '': + password_decode = password + else: + password_decode = None db_update_user(user_id=user_id, password=password, name=name, email=email, groups=groups, - dept=dept, + admin_groups=admin_groups, role=role_post, - is_active=is_active, - ssh_key_pwd=ssh_key_pwd) + is_active=is_active) - return HttpResponseRedirect('/juser/user_list/') + if email_need: + msg = u""" + Hi %s: + 您的信息已修改,请登录跳板机查看详细信息 + 地址:%s + 用户名: %s + 密码:%s (如果密码为None代表密码为原密码) + 权限::%s - return render_to_response('juser/user_edit.html', locals(), context_instance=RequestContext(request)) - - -@require_admin -def user_edit_adm(request): - header_title, path1, path2 = '编辑用户', '用户管理', '用户编辑' - user, dept = get_session_user_dept(request) - if request.method == 'GET': - user_id = request.GET.get('id', '') - if not user_id: - return HttpResponseRedirect('/juser/user_list/') - - if not validate(request, user=[user_id]): - return HttpResponseRedirect('/juser/user_list/') - - user = User.objects.filter(id=user_id) - dept_all = DEPT.objects.all() - group_all = dept.usergroup_set.all() - if user: - user = user[0] - groups_str = ' '.join([str(group.id) for group in user.group.all()]) - - else: - user_id = request.POST.get('user_id', '') - password = request.POST.get('password', '') - name = request.POST.get('name', '') - email = request.POST.get('email', '') - groups = request.POST.getlist('groups', []) - ssh_key_pwd = request.POST.get('ssh_key_pwd', '') - is_active = True if request.POST.get('is_active', '1') == '1' else False - - if not validate(request, user=[user_id], user_group=groups): - return HttpResponseRedirect('/juser/user_edit/') - if user_id: - user = User.objects.filter(id=user_id) - if user: - user = user[0] - else: - return HttpResponseRedirect('/juser/user_list/') - - if password != user.password: - password = md5_crypt(password) - - if ssh_key_pwd != user.ssh_key_pwd: - ssh_key_pwd = CRYPTOR.encrypt(ssh_key_pwd) - - db_update_user(user_id=user_id, - password=password, - name=name, - email=email, - groups=groups, - is_active=is_active, - ssh_key_pwd=ssh_key_pwd) - - return HttpResponseRedirect('/juser/user_list/') - - return render_to_response('juser/user_edit.html', locals(), context_instance=RequestContext(request)) + """ % (user.name, URL, user.username, password_decode, user_role.get(role_post, u'')) + send_mail('您的信息已修改', msg, MAIL_FROM, [email], fail_silently=False) + + return HttpResponseRedirect(reverse('user_list')) + return my_render('juser/user_edit.html', locals(), request) +@require_role('user') def profile(request): - user_id = request.session.get('user_id') + user_id = request.user.id if not user_id: - return HttpResponseRedirect('/') + return HttpResponseRedirect(reverse('index')) user = User.objects.get(id=user_id) - return render_to_response('juser/profile.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/profile.html', locals(), request) -def chg_info(request): +def change_info(request): header_title, path1, path2 = '修改信息', '用户管理', '修改个人信息' - user_id = request.session.get('user_id') - user_set = User.objects.filter(id=user_id) + user_id = request.user.id + user = User.objects.get(id=user_id) error = '' - if user_set: - user = user_set[0] - else: - return HttpResponseRedirect('/') + if not user: + return HttpResponseRedirect(reverse('index')) if request.method == 'POST': name = request.POST.get('name', '') password = request.POST.get('password', '') - ssh_key_pwd = request.POST.get('ssh_key_pwd', '') email = request.POST.get('email', '') - if '' in [name, password, ssh_key_pwd, email]: + if '' in [name, email]: error = '不能为空' - if len(password) < 6 or len(ssh_key_pwd) < 6: - error = '密码须大于6位' - if not error: - if password != user.password: - password = md5_crypt(password) - - if ssh_key_pwd != user.ssh_key_pwd: - gen_ssh_key(user.username, ssh_key_pwd) - ssh_key_pwd = md5_crypt(ssh_key_pwd) - - user_set.update(name=name, password=password, ssh_key_pwd=ssh_key_pwd, email=email) + User.objects.filter(id=user_id).update(name=name, email=email) + if len(password) > 0: + user.set_password(password) + user.save() msg = '修改成功' - return render_to_response('juser/chg_info.html', locals(), context_instance=RequestContext(request)) + return my_render('juser/change_info.html', locals(), request) +@require_role(role='user') +def regen_ssh_key(request): + uuid_r = request.GET.get('uuid', '') + user = get_object(User, uuid=uuid_r) + if not user: + return HttpResponse('没有该用户') + + username = user.username + ssh_key_pass = PyCrypt.gen_rand_pass(16) + gen_ssh_key(username, ssh_key_pass) + return HttpResponse('ssh密钥已生成,密码为 %s, 请到下载页面下载' % ssh_key_pass) - -@require_login +@require_role(role='user') def down_key(request): - user_id = '' - if is_super_user(request): - user_id = request.GET.get('id') + if is_role_request(request, 'super'): + uuid_r = request.GET.get('uuid', '') + else: + uuid_r = request.user.uuid - if is_group_admin(request): - user_id = request.GET.get('id') - if not validate(request, user=[user_id]): - user_id = request.session.get('user_id') - - if is_common_user(request): - user_id = request.session.get('user_id') - - if user_id: - user = User.objects.filter(id=user_id) + if uuid_r: + user = get_object(User, uuid=uuid_r) if user: - user = user[0] username = user.username - private_key_dir = os.path.join(BASE_DIR, 'keys/jumpserver/') - private_key_file = os.path.join(private_key_dir, username+".pem") + private_key_file = os.path.join(KEY_DIR, 'user', username+'.pem') + print private_key_file if os.path.isfile(private_key_file): f = open(private_key_file) data = f.read() @@ -1052,5 +791,5 @@ def down_key(request): response = HttpResponse(data, content_type='application/octet-stream') response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(private_key_file) return response + return HttpResponse('No Key File. Contact Admin.') - return HttpResponse('No Key File. Contact Admin.') \ No newline at end of file diff --git a/manage.py b/manage.py old mode 100644 new mode 100755 diff --git a/service.sh b/service.sh old mode 100644 new mode 100755 index 33b1a95e1..64791a59a --- a/service.sh +++ b/service.sh @@ -7,7 +7,7 @@ # Date: 2015-04-12 # Version: 2.0.0 # Site: http://www.jumpserver.org -# Author: jumpserver group +# Author: Jumpserver Team . /etc/init.d/functions export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/node/bin @@ -26,17 +26,16 @@ start() { success "$jump_start" else daemon python $base_dir/manage.py runserver 0.0.0.0:80 &>> /tmp/jumpserver.log 2>&1 & - daemon python $base_dir/log_handler.py &> /dev/null 2>&1 & - cd $base_dir/websocket/;daemon node index.js &> /dev/null 2>&1 & + daemon python $base_dir/run_websocket.py &> /dev/null 2>&1 & sleep 2 echo -n "$jump_start" nums=0 - for i in manage.py log_handler.py index.js;do - ps aux | grep "$i" | grep -v 'grep' &> /dev/null && let nums+=1 + for i in manage.py run_websocket.py;do + ps aux | grep "$i" | grep -v 'grep' &> /dev/null && let nums+=1 || echo "$i not running" done - if [ "x$nums" == "x3" ];then + if [ "x$nums" == "x2" ];then success "$jump_start" touch "$lockfile" echo @@ -44,7 +43,6 @@ start() { failure "$jump_start" echo fi - fi @@ -56,7 +54,7 @@ stop() { echo -n $"Stopping ${PROC_NAME} service:" if [ -e $lockfile ];then - ps aux | grep -E 'manage.py|log_handler.py|index.js' | grep -v grep | awk '{print $2}' | xargs kill -9 &> /dev/null + ps aux | grep -E 'manage.py|run_websocket.py' | grep -v grep | awk '{print $2}' | xargs kill -9 &> /dev/null ret=$? if [ $ret -eq 0 ]; then @@ -104,9 +102,3 @@ esac - - - - - - diff --git a/static/.DS_Store b/static/.DS_Store deleted file mode 100644 index 1f949c218..000000000 Binary files a/static/.DS_Store and /dev/null differ diff --git a/static/css/style.css b/static/css/style.css index 5b0e5745d..2dc7a2591 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2822,7 +2822,9 @@ body.body-small .footer.fixed { .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { - border-top: 1px solid #e7eaec; + /*border-top: 1px solid #e7eaec;*/ + border-bottom: 1px solid #e7eaec; + border-top: none; line-height: 1.42857; padding: 8px; vertical-align: top; @@ -3516,8 +3518,8 @@ body.modal-open { z-index: 100; } .lockscreen.middle-box { - width: 200px; - margin-left: -100px; + width: 400px; + margin-left: -200px; margin-top: -190px; } .loginscreen.middle-box { @@ -4562,3 +4564,10 @@ body.skin-3 { .red-fonts { color: #ed5565; } + +.form-group.required .control-label:after { + content: " *"; + color: red; +} + +.n-invalid {border: 1px solid #f00;} diff --git a/static/img/root.png b/static/img/root.png index aaa37e666..0d07dbf97 100644 Binary files a/static/img/root.png and b/static/img/root.png differ diff --git a/static/js/base.js b/static/js/base.js index 9324ae0a6..b872558f3 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -16,6 +16,22 @@ function check_all(form) { } } +function checkAll(id, name){ + var checklist = document.getElementsByName(name); + if(document.getElementById(id).checked) + { + for(var i=0;i" + this.options.dictDefaultMessage + "")); + this.element.appendChild(Dropzone.createElement("
" + this.options.dictDefaultMessage + "
")); } if (this.clickableElements.length) { setupHiddenFileInput = (function(_this) { diff --git a/static/js/highcharts/highcharts.src.js b/static/js/highcharts/highcharts.src.js index 9e063bd7d..381650b0d 100644 --- a/static/js/highcharts/highcharts.src.js +++ b/static/js/highcharts/highcharts.src.js @@ -180,7 +180,7 @@ function merge() { } /** - * Take an array and turn into a hash with even number arguments as keys and odd numbers as + * Take an array and turn into a hash with even number arguments as role_keys and odd numbers as * values. Allows creating constants for commonly used style properties, attributes etc. * Avoid it in performance critical situations like looping */ @@ -448,7 +448,7 @@ dateFormat = function (format, timestamp, capitalize) { lang = defaultOptions.lang, langWeekdays = lang.weekdays, - // List all format keys. Custom formats can be added from the outside. + // List all format role_keys. Custom formats can be added from the outside. replacements = extend({ // Day @@ -14895,7 +14895,7 @@ var AreaSeries = extendClass(Series, { pointMap[points[i].x] = points[i]; } - // Sort the keys (#1651) + // Sort the role_keys (#1651) for (x in stack) { if (stack[x].total !== null) { // nulled after switching between grouping and not (#1651, #2336) keys.push(+x); diff --git a/static/js/jquery-ui-1.10.4.min.js b/static/js/jquery-ui-1.10.4.min.js index d2da7b53a..ef3be2396 100644 --- a/static/js/jquery-ui-1.10.4.min.js +++ b/static/js/jquery-ui-1.10.4.min.js @@ -3,5 +3,5 @@ * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.accordion.js, jquery.ui.autocomplete.js, jquery.ui.button.js, jquery.ui.datepicker.js, jquery.ui.dialog.js, jquery.ui.draggable.js, jquery.ui.droppable.js, jquery.ui.effect.js, jquery.ui.effect-blind.js, jquery.ui.effect-bounce.js, jquery.ui.effect-clip.js, jquery.ui.effect-drop.js, jquery.ui.effect-explode.js, jquery.ui.effect-fade.js, jquery.ui.effect-fold.js, jquery.ui.effect-highlight.js, jquery.ui.effect-pulsate.js, jquery.ui.effect-scale.js, jquery.ui.effect-shake.js, jquery.ui.effect-slide.js, jquery.ui.effect-transfer.js, jquery.ui.menu.js, jquery.ui.progressbar.js, jquery.ui.resizable.js, jquery.ui.selectable.js, jquery.ui.slider.js, jquery.ui.sortable.js, jquery.ui.spinner.js, jquery.ui.tabs.js, jquery.ui.tooltip.js * Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */ -(function(e,t){function i(t,i){var s,a,o,r=t.nodeName.toLowerCase();return"area"===r?(s=t.parentNode,a=s.name,t.href&&a&&"map"===s.nodeName.toLowerCase()?(o=e("img[usemap=#"+a+"]")[0],!!o&&n(o)):!1):(/input|select|textarea|button|object/.test(r)?!t.disabled:"a"===r?t.href||i:i)&&n(t)}function n(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var s=0,a=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.4",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,n){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),n&&n.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var n,s,a=e(this[0]);a.length&&a[0]!==document;){if(n=a.css("position"),("absolute"===n||"relative"===n||"fixed"===n)&&(s=parseInt(a.css("zIndex"),10),!isNaN(s)&&0!==s))return s;a=a.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++s)})},removeUniqueId:function(){return this.each(function(){a.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,n){return!!e.data(t,n[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),s=isNaN(n);return(s||n>=0)&&i(t,!s)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,n){function s(t,i,n,s){return e.each(a,function(){i-=parseFloat(e.css(t,"padding"+this))||0,n&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var a="Width"===n?["Left","Right"]:["Top","Bottom"],o=n.toLowerCase(),r={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+n]=function(i){return i===t?r["inner"+n].call(this):this.each(function(){e(this).css(o,s(this,i)+"px")})},e.fn["outer"+n]=function(t,i){return"number"!=typeof t?r["outer"+n].call(this,t):this.each(function(){e(this).css(o,s(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,n){var s,a=e.ui[t].prototype;for(s in n)a.plugins[s]=a.plugins[s]||[],a.plugins[s].push([i,n[s]])},call:function(e,t,i){var n,s=e.plugins[t];if(s&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(n=0;s.length>n;n++)e.options[s[n][0]]&&s[n][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var n=i&&"left"===i?"scrollLeft":"scrollTop",s=!1;return t[n]>0?!0:(t[n]=1,s=t[n]>0,t[n]=0,s)}})})(jQuery);(function(t,e){var i=0,s=Array.prototype.slice,n=t.cleanData;t.cleanData=function(e){for(var i,s=0;null!=(i=e[s]);s++)try{t(i).triggerHandler("remove")}catch(o){}n(e)},t.widget=function(i,s,n){var o,a,r,h,l={},c=i.split(".")[0];i=i.split(".")[1],o=c+"-"+i,n||(n=s,s=t.Widget),t.expr[":"][o.toLowerCase()]=function(e){return!!t.data(e,o)},t[c]=t[c]||{},a=t[c][i],r=t[c][i]=function(t,i){return this._createWidget?(arguments.length&&this._createWidget(t,i),e):new r(t,i)},t.extend(r,a,{version:n.version,_proto:t.extend({},n),_childConstructors:[]}),h=new s,h.options=t.widget.extend({},h.options),t.each(n,function(i,n){return t.isFunction(n)?(l[i]=function(){var t=function(){return s.prototype[i].apply(this,arguments)},e=function(t){return s.prototype[i].apply(this,t)};return function(){var i,s=this._super,o=this._superApply;return this._super=t,this._superApply=e,i=n.apply(this,arguments),this._super=s,this._superApply=o,i}}(),e):(l[i]=n,e)}),r.prototype=t.widget.extend(h,{widgetEventPrefix:a?h.widgetEventPrefix||i:i},l,{constructor:r,namespace:c,widgetName:i,widgetFullName:o}),a?(t.each(a._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,r,i._proto)}),delete a._childConstructors):s._childConstructors.push(r),t.widget.bridge(i,r)},t.widget.extend=function(i){for(var n,o,a=s.call(arguments,1),r=0,h=a.length;h>r;r++)for(n in a[r])o=a[r][n],a[r].hasOwnProperty(n)&&o!==e&&(i[n]=t.isPlainObject(o)?t.isPlainObject(i[n])?t.widget.extend({},i[n],o):t.widget.extend({},o):o);return i},t.widget.bridge=function(i,n){var o=n.prototype.widgetFullName||i;t.fn[i]=function(a){var r="string"==typeof a,h=s.call(arguments,1),l=this;return a=!r&&h.length?t.widget.extend.apply(null,[a].concat(h)):a,r?this.each(function(){var s,n=t.data(this,o);return n?t.isFunction(n[a])&&"_"!==a.charAt(0)?(s=n[a].apply(n,h),s!==n&&s!==e?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):e):t.error("no such method '"+a+"' for "+i+" widget instance"):t.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+a+"'")}):this.each(function(){var e=t.data(this,o);e?e.option(a||{})._init():t.data(this,o,new n(a,this))}),l}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(e,s){s=t(s||this.defaultElement||this)[0],this.element=t(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this.bindings=t(),this.hoverable=t(),this.focusable=t(),s!==this&&(t.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===s&&this.destroy()}}),this.document=t(s.style?s.ownerDocument:s.document||s),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:t.noop,_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(t.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:t.noop,widget:function(){return this.element},option:function(i,s){var n,o,a,r=i;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof i)if(r={},n=i.split("."),i=n.shift(),n.length){for(o=r[i]=t.widget.extend({},this.options[i]),a=0;n.length-1>a;a++)o[n[a]]=o[n[a]]||{},o=o[n[a]];if(i=n.pop(),1===arguments.length)return o[i]===e?null:o[i];o[i]=s}else{if(1===arguments.length)return this.options[i]===e?null:this.options[i];r[i]=s}return this._setOptions(r),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return this.options[t]=e,"disabled"===t&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!e).attr("aria-disabled",e),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var o,a=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=o=t(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,o=this.widget()),t.each(n,function(n,r){function h(){return i||a.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof r?a[r]:r).apply(a,arguments):e}"string"!=typeof r&&(h.guid=r.guid=r.guid||h.guid||t.guid++);var l=n.match(/^(\w+)\s*(.*)$/),c=l[1]+a.eventNamespace,u=l[2];u?o.delegate(u,c,h):s.bind(c,h)})},_off:function(t,e){e=(e||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,t.unbind(e).undelegate(e)},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){t(e.currentTarget).addClass("ui-state-hover")},mouseleave:function(e){t(e.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){t(e.currentTarget).addClass("ui-state-focus")},focusout:function(e){t(e.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}})})(jQuery);(function(t){var e=!1;t(document).mouseup(function(){e=!1}),t.widget("ui.mouse",{version:"1.10.4",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.bind("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).bind("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!e){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?t(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===t.data(i.target,this.widgetName+".preventClickEvent")&&t.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return s._mouseMove(t)},this._mouseUpDelegate=function(t){return s._mouseUp(t)},t(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),e=!0,!0)):!0}},_mouseMove:function(e){return t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button?this._mouseUp(e):this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){return t(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),!1},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,l=Math.round,h=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(l.horizontal="center"),d>g&&g>r(n+a)&&(l.vertical="middle"),l.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,l)}),c.offset(t.extend(M,{using:h}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-o-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-o-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-o-a,t.top+p+f+g>c&&(0>s||r(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,t.top+p+f+g>u&&(i>0||u>r(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(e){var t=0,i={},a={};i.height=i.paddingTop=i.paddingBottom=i.borderTopWidth=i.borderBottomWidth="hide",a.height=a.paddingTop=a.paddingBottom=a.borderTopWidth=a.borderBottomWidth="show",e.widget("ui.accordion",{version:"1.10.4",options:{active:0,animate:{},collapsible:!1,event:"click",header:"> li > :first-child,> :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},_create:function(){var t=this.options;this.prevShow=this.prevHide=e(),this.element.addClass("ui-accordion ui-widget ui-helper-reset").attr("role","tablist"),t.collapsible||t.active!==!1&&null!=t.active||(t.active=0),this._processPanels(),0>t.active&&(t.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():e(),content:this.active.length?this.active.next():e()}},_createIcons:function(){var t=this.options.icons;t&&(e("").addClass("ui-accordion-header-icon ui-icon "+t.header).prependTo(this.headers),this.active.children(".ui-accordion-header-icon").removeClass(t.header).addClass(t.activeHeader),this.headers.addClass("ui-accordion-icons"))},_destroyIcons:function(){this.headers.removeClass("ui-accordion-icons").children(".ui-accordion-header-icon").remove()},_destroy:function(){var e;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role"),this.headers.removeClass("ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("aria-selected").removeAttr("aria-controls").removeAttr("tabIndex").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),this._destroyIcons(),e=this.headers.next().css("display","").removeAttr("role").removeAttr("aria-hidden").removeAttr("aria-labelledby").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled").each(function(){/^ui-accordion/.test(this.id)&&this.removeAttribute("id")}),"content"!==this.options.heightStyle&&e.css("height","")},_setOption:function(e,t){return"active"===e?(this._activate(t),undefined):("event"===e&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(t)),this._super(e,t),"collapsible"!==e||t||this.options.active!==!1||this._activate(0),"icons"===e&&(this._destroyIcons(),t&&this._createIcons()),"disabled"===e&&this.headers.add(this.headers.next()).toggleClass("ui-state-disabled",!!t),undefined)},_keydown:function(t){if(!t.altKey&&!t.ctrlKey){var i=e.ui.keyCode,a=this.headers.length,s=this.headers.index(t.target),n=!1;switch(t.keyCode){case i.RIGHT:case i.DOWN:n=this.headers[(s+1)%a];break;case i.LEFT:case i.UP:n=this.headers[(s-1+a)%a];break;case i.SPACE:case i.ENTER:this._eventHandler(t);break;case i.HOME:n=this.headers[0];break;case i.END:n=this.headers[a-1]}n&&(e(t.target).attr("tabIndex",-1),e(n).attr("tabIndex",0),n.focus(),t.preventDefault())}},_panelKeyDown:function(t){t.keyCode===e.ui.keyCode.UP&&t.ctrlKey&&e(t.currentTarget).prev().focus()},refresh:function(){var t=this.options;this._processPanels(),t.active===!1&&t.collapsible===!0||!this.headers.length?(t.active=!1,this.active=e()):t.active===!1?this._activate(0):this.active.length&&!e.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(t.active=!1,this.active=e()):this._activate(Math.max(0,t.active-1)):t.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){this.headers=this.element.find(this.options.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all"),this.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom").filter(":not(.ui-accordion-content-active)").hide()},_refresh:function(){var i,a=this.options,s=a.heightStyle,n=this.element.parent(),r=this.accordionId="ui-accordion-"+(this.element.attr("id")||++t);this.active=this._findActive(a.active).addClass("ui-accordion-header-active ui-state-active ui-corner-top").removeClass("ui-corner-all"),this.active.next().addClass("ui-accordion-content-active").show(),this.headers.attr("role","tab").each(function(t){var i=e(this),a=i.attr("id"),s=i.next(),n=s.attr("id");a||(a=r+"-header-"+t,i.attr("id",a)),n||(n=r+"-panel-"+t,s.attr("id",n)),i.attr("aria-controls",n),s.attr("aria-labelledby",a)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(a.event),"fill"===s?(i=n.height(),this.element.siblings(":visible").each(function(){var t=e(this),a=t.css("position");"absolute"!==a&&"fixed"!==a&&(i-=t.outerHeight(!0))}),this.headers.each(function(){i-=e(this).outerHeight(!0)}),this.headers.next().each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===s&&(i=0,this.headers.next().each(function(){i=Math.max(i,e(this).css("height","").height())}).height(i))},_activate:function(t){var i=this._findActive(t)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return"number"==typeof t?this.headers.eq(t):e()},_setupEvents:function(t){var i={keydown:"_keydown"};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(t){var i=this.options,a=this.active,s=e(t.currentTarget),n=s[0]===a[0],r=n&&i.collapsible,o=r?e():s.next(),h=a.next(),d={oldHeader:a,oldPanel:h,newHeader:r?e():s,newPanel:o};t.preventDefault(),n&&!i.collapsible||this._trigger("beforeActivate",t,d)===!1||(i.active=r?!1:this.headers.index(s),this.active=n?e():s,this._toggle(d),a.removeClass("ui-accordion-header-active ui-state-active"),i.icons&&a.children(".ui-accordion-header-icon").removeClass(i.icons.activeHeader).addClass(i.icons.header),n||(s.removeClass("ui-corner-all").addClass("ui-accordion-header-active ui-state-active ui-corner-top"),i.icons&&s.children(".ui-accordion-header-icon").removeClass(i.icons.header).addClass(i.icons.activeHeader),s.next().addClass("ui-accordion-content-active")))},_toggle:function(t){var i=t.newPanel,a=this.prevShow.length?this.prevShow:t.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=a,this.options.animate?this._animate(i,a,t):(a.hide(),i.show(),this._toggleComplete(t)),a.attr({"aria-hidden":"true"}),a.prev().attr("aria-selected","false"),i.length&&a.length?a.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true",tabIndex:0,"aria-expanded":"true"})},_animate:function(e,t,s){var n,r,o,h=this,d=0,c=e.length&&(!t.length||e.index()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("