Commit f37b3316 authored by ibuler's avatar ibuler

Merge with dev

parents 5bbad019 9dbf4983
......@@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
### Install 安装
1. 安装 Python3
2. 安装依赖
```
$ cd requirements && yum -y install $(cat rpm_requirements.txt) && pip install -r requirements.txt
```
3. 修改配置文件
```
$ cp config_example.py config.py
```
4. 修改表结构
```
$ cd apps && python manage.py makemigrations && python manage.py migrate
```
5. 运行
```
$ python run_server.py
```
6. 其它
整合luna,coco需要nginx来配合, 详见详细安装文档
   [详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
### Usage 使用
......
# ~*~ coding: utf-8 ~*~
# Copyright (C) 2014-2017 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
# Copyright (C) 2014-2018 Beijing DuiZhan Technology Co.,Ltd. All Rights Reserved.
#
# Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License.
......@@ -87,7 +87,7 @@ class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet):
"""
Asset group api set, for add,delete,update,list,retrieve resource
"""
queryset = AssetGroup.objects.all()
queryset = AssetGroup.objects.all().annotate(asset_count=Count("assets"))
serializer_class = serializers.AssetGroupSerializer
permission_classes = (IsSuperUser,)
......@@ -298,9 +298,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
class LabelViewSet(BulkModelViewSet):
queryset = Label.objects.annotate(asset_count=Count("assets"))\
.annotate(admin_user_count=Count("adminuser")) \
.annotate(system_user_count=Count("systemuser"))
queryset = Label.objects.annotate(asset_count=Count("assets"))
permission_classes = (IsSuperUser,)
serializer_class = serializers.LabelSerializer
......
......@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2017 by Jumpserver Team.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
......
......@@ -16,7 +16,7 @@ class Label(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, verbose_name=_("Category"))
category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
date_created = models.DateTimeField(
......
......@@ -30,7 +30,6 @@ class AssetUser(models.Model):
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
_private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ])
_public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key'))
labels = models.ManyToManyField('assets.Label', blank=True, verbose_name=_("Labels"))
comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
......
......@@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod
def get_assets_amount(obj):
return obj.assets.count()
return obj.asset_count
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
......@@ -288,8 +288,6 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer):
class LabelSerializer(serializers.ModelSerializer):
asset_count = serializers.SerializerMethodField()
admin_user_count = serializers.SerializerMethodField()
system_user_count = serializers.SerializerMethodField()
class Meta:
model = Label
......@@ -300,14 +298,6 @@ class LabelSerializer(serializers.ModelSerializer):
def get_asset_count(obj):
return obj.asset_count
@staticmethod
def get_admin_user_count(obj):
return obj.admin_user_count
@staticmethod
def get_system_user_count(obj):
return obj.system_user_count
def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info)
fields.extend(['get_category_display'])
......
......@@ -121,7 +121,7 @@ function initTable() {
{data: "type" }, {data: "is_connective" }],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function () {
......
......@@ -184,7 +184,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
jumpserver.initServerSideDataTable(options);
}
......
......@@ -34,10 +34,6 @@ $(document).ready(function(){
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 3, createdCell: function (td, cellData) {
var innerHtml = cellData.length > 30 ? cellData.substring(0, 30) + '...': cellData;
$(td).html('<a href="javascript:void(0);" data-toggle="tooltip" title="' + cellData + '">' + innerHtml + '</a>');
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:asset-group-update" pk=DEFAULT_PK %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_asset_group_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
......
......@@ -176,7 +176,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
jumpserver.initServerSideDataTable(options);
}
......
......@@ -14,8 +14,6 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Value' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'Admin user' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
</thead>
......@@ -36,7 +34,7 @@ function initTable() {
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id));
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{targets: 4, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "assets:cluster-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
var del_btn = '<a class="btn btn-xs btn-danger m-l-xs btn_cluster_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
......@@ -44,8 +42,7 @@ function initTable() {
ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [
{data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count" }, {data: "admin_user_count" },
{data: "system_user_count" }, {data: "id"}
{data: "asset_count" }, {data: "id"}
],
op_html: $('#actions').html()
};
......
......@@ -121,7 +121,7 @@ function initAssetsTable() {
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
op_html: $('#actions').html()
};
jumpserver.initDataTable(options);
jumpserver.initServerSideDataTable(options);
}
$(document).ready(function () {
......
......@@ -60,11 +60,6 @@ class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView)
success_url = reverse_lazy('assets:cluster-list')
success_message = update_success_msg
def form_valid(self, form):
cluster = form.save(commit=False)
cluster.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = {
'app': _('assets'),
......
......@@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView):
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
print(serializer.validated_data)
try:
attr_map = json.loads(attr_map)
except json.JSONDecodeError:
......@@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView):
except Exception as e:
return Response({"error": str(e)}, status=401)
print(search_ou)
print(search_filter % ({"user": "*"}))
print(attr_map.values())
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values()))
if not ok:
......
......@@ -5,6 +5,7 @@ import json
from django import forms
from django.utils import six
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
class DictField(forms.Field):
......@@ -18,16 +19,16 @@ class DictField(forms.Field):
# we don't need to handle that explicitly.
if isinstance(value, six.string_types):
try:
print(value)
value = json.loads(value)
return value
except json.JSONDecodeError:
pass
value = {}
return value
return ValidationError(_("Not a valid json"))
else:
return ValidationError(_("Not a string type"))
def validate(self, value):
print(value)
if isinstance(value, ValidationError):
raise value
if not value and self.required:
raise ValidationError(self.error_messages['required'], code='required')
......
......@@ -4,7 +4,9 @@ import json
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.html import escape
from django.db import transaction
from django.conf import settings
from .models import Setting
from .fields import DictField
......@@ -24,34 +26,38 @@ def to_form_value(value):
data = value
return data
except json.JSONDecodeError:
return ''
return ""
class BaseForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
settings = Setting.objects.all()
db_settings = Setting.objects.all()
for name, field in self.fields.items():
db_value = getattr(settings, name).value
if db_value:
db_value = getattr(db_settings, name).value
django_value = getattr(settings, name) if hasattr(settings, name) else None
if db_value is False or db_value:
field.initial = to_form_value(db_value)
elif django_value is False or django_value:
field.initial = to_form_value(to_model_value(django_value))
def save(self):
def save(self, category="default"):
if not self.is_bound:
raise ValueError("Form is not bound")
settings = Setting.objects.all()
db_settings = Setting.objects.all()
if self.is_valid():
with transaction.atomic():
for name, value in self.cleaned_data.items():
field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value:
continue
if value == to_form_value(getattr(settings, name).value):
if value == to_form_value(getattr(db_settings, name).value):
continue
defaults = {
'name': name,
'category': category,
'value': to_model_value(value)
}
Setting.objects.update_or_create(defaults=defaults, name=name)
......@@ -72,9 +78,6 @@ class BasicSettingForm(BaseForm):
max_length=1024, label=_("Email Subject Prefix"),
initial="[Jumpserver] "
)
AUTH_LDAP = forms.BooleanField(
label=_("Enable LDAP Auth"), initial=False, required=False
)
class EmailSettingForm(BaseForm):
......@@ -129,3 +132,28 @@ class LDAPSettingForm(BaseForm):
AUTH_LDAP_START_TLS = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False
)
class TerminalSettingForm(BaseForm):
SORT_BY_CHOICES = (
('hostname', _('Hostname')),
('ip', _('IP')),
)
TERMINAL_ASSET_LIST_SORT_BY = forms.ChoiceField(
choices=SORT_BY_CHOICES, initial='hostname', label=_("List sort by")
)
TERMINAL_HEARTBEAT_INTERVAL = forms.IntegerField(
initial=5, label=_("Heartbeat interval"), help_text=_("Units: seconds")
)
TERMINAL_PASSWORD_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Password auth")
)
TERMINAL_PUBLIC_KEY_AUTH = forms.BooleanField(
initial=True, required=False, label=_("Public key auth")
)
TERMINAL_COMMAND_STORAGE = DictField(
label=_("Command storage"), help_text=_(
"Set terminal storage setting, `default` is the using as default,"
"You can set other storage and some terminal using"
)
)
......@@ -2,6 +2,7 @@ import json
import ldap
from django.db import models
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django_auth_ldap.config import LDAPSearch
......@@ -24,6 +25,7 @@ class SettingManager(models.Manager):
class Setting(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
value = models.TextField(verbose_name=_("Value"))
category = models.CharField(max_length=128, default="default")
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
comment = models.TextField(verbose_name=_("Comment"))
......@@ -33,17 +35,28 @@ class Setting(models.Model):
return self.name
@property
def value_(self):
def cleaned_value(self):
try:
return json.loads(self.value)
except json.JSONDecodeError:
return None
@cleaned_value.setter
def cleaned_value(self, item):
try:
v = json.dumps(item)
self.value = v
except json.JSONDecodeError as e:
raise ValueError("Json dump error: {}".format(str(e)))
@classmethod
def refresh_all_settings(cls):
try:
settings_list = cls.objects.all()
for setting in settings_list:
setting.refresh_setting()
except (ProgrammingError, OperationalError):
pass
def refresh_setting(self):
try:
......@@ -53,9 +66,9 @@ class Setting(models.Model):
setattr(settings, self.name, value)
if self.name == "AUTH_LDAP":
if self.value_ and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
if self.cleaned_value and settings.AUTH_LDAP_BACKEND not in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.insert(0, settings.AUTH_LDAP_BACKEND)
elif not self.value_ and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
elif not self.cleaned_value and settings.AUTH_LDAP_BACKEND in settings.AUTHENTICATION_BACKENDS:
settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
if self.name == "AUTH_LDAP_SEARCH_FILTER":
......
......@@ -20,6 +20,9 @@
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
......
......@@ -20,6 +20,9 @@
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
......
......@@ -20,6 +20,9 @@
<li class="active">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
......
{% extends 'base.html' %}
{% load static %}
{% load bootstrap3 %}
{% load i18n %}
{% load common_tags %}
{% block content %}
<div class="wrapper wrapper-content animated fadeInRight">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="panel-options">
<ul class="nav nav-tabs">
<li>
<a href="{% url 'settings:basic-setting' %}" class="text-center"><i class="fa fa-cubes"></i> {% trans 'Basic setting' %}</a>
</li>
<li>
<a href="{% url 'settings:email-setting' %}" class="text-center"><i class="fa fa-envelope"></i> {% trans 'Email setting' %} </a>
</li>
<li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li>
<li class="active">
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul>
</div>
<div class="tab-content">
<div class="col-sm-12" style="padding-left:0">
<div class="ibox-content" style="border-width: 0;padding-top: 40px;">
<form action="" method="post" class="form-horizontal">
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% csrf_token %}
<h3>{% trans "Basic setting" %}</h3>
{% for field in form %}
{% if not field.field|is_bool_field %}
{% bootstrap_field field layout="horizontal" %}
{% else %}
<div class="form-group">
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-8">
<div class="col-sm-1">
{{ field }}
</div>
<div class="col-sm-9">
<span class="help-block" >{{ field.help_text }}</span>
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div class="hr-line-dashed"></div>
<h3>{% trans "Command storage" %}</h3>
<table class="table table-hover " id="task-history-list-table" >
<thead>
<tr>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Type' %}</th>
</tr>
</thead>
<tbody>
{% for name, setting in command_storage.items %}
<tr>
<td>{{ name }}</td>
<td>{{ setting.TYPE }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{# <button class="btn btn-default btn-circle btn-add-command-storage" data-toggle="modal" data-target="#add_command_storage_model" tabindex="0" type="button"><i class="fa fa-plus"></i></button>#}
{# <div class="hr-line-dashed"></div>#}
{# <h3>{% trans "Replay storage" %}</h3>#}
<div class="hr-line-dashed"></div>
<div class="form-group">
<div class="col-sm-4 col-sm-offset-2">
<button class="btn btn-default" type="reset"> {% trans 'Reset' %}</button>
<button id="submit_button" class="btn btn-primary" type="submit">{% trans 'Submit' %}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$(document).ready(function () {
})
.on("click", ".btn-test", function () {
var data = {};
var form = $("form").serializeArray();
$.each(form, function (i, field) {
data[field.name] = field.value;
});
var the_url = "{% url 'api-common:ldap-testing' %}";
function error(message) {
toastr.error(message)
}
function success(message) {
toastr.success(message.msg)
}
APIUpdateAttr({
url: the_url,
body: JSON.stringify(data),
method: "POST",
flash_message: false,
success: success,
error: error
});
})
.on('click', '', function () {
})
</script>
{% endblock %}
......@@ -10,4 +10,5 @@ urlpatterns = [
url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'),
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-setting'),
url(r'^terminal/$', views.TerminalSettingView.as_view(), name='terminal-setting'),
]
from django.views.generic import View, TemplateView
from django.views.generic import TemplateView
from django.shortcuts import render, redirect
from django.contrib import messages
from django.utils.translation import ugettext as _
from django.conf import settings
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm
from .models import Setting
from .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable
......@@ -25,8 +28,6 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
if "AUTH_LDAP" in form.cleaned_data:
ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:basic-setting')
......@@ -79,6 +80,8 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
if "AUTH_LDAP" in form.cleaned_data:
ldap_auth_enable.send(form.cleaned_data["AUTH_LDAP"])
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:ldap-setting')
......@@ -86,3 +89,31 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
context = self.get_context_data()
context.update({"form": form})
return render(request, self.template_name, context)
class TerminalSettingView(AdminUserRequiredMixin, TemplateView):
form_class = TerminalSettingForm
template_name = "common/terminal_setting.html"
def get_context_data(self, **kwargs):
command_storage = settings.TERMINAL_COMMAND_STORAGE
context = {
'app': _('Settings'),
'action': _('Terminal setting'),
'form': self.form_class(),
'command_storage': command_storage,
}
kwargs.update(context)
return super().get_context_data(**kwargs)
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
msg = _("Update setting successfully, please restart program")
messages.success(request, msg)
return redirect('settings:terminal-setting')
else:
context = self.get_context_data()
context.update({"form": form})
return render(request, self.template_name, context)
......@@ -274,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or ''
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
......@@ -298,7 +298,7 @@ REST_FRAMEWORK = {
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 15
# 'PAGE_SIZE': 15
}
AUTHENTICATION_BACKENDS = [
......@@ -373,7 +373,20 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE
COMMAND_STORAGE_BACKEND = 'terminal.backends.command.db'
COMMAND_STORAGE = {
'ENGINE': 'terminal.backends.command.db',
}
TERMINAL_COMMAND_STORAGE = {
"default": {
"TYPE": "server",
},
# 'ali-es': {
# 'TYPE': 'elasticsearch',
# 'HOSTS': ['http://elastic:changeme@localhost:9200'],
# },
}
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = {
......
This diff is collapsed.
......@@ -165,7 +165,7 @@ class AdHoc(models.Model):
if item and isinstance(item, list):
self._tasks = json.dumps(item)
else:
raise SyntaxError('Tasks should be a list')
raise SyntaxError('Tasks should be a list: {}'.format(item))
@property
def hosts(self):
......
......@@ -16,6 +16,8 @@ def update_or_create_ansible_task(
run_as_admin=False, run_as="", become_info=None,
created_by=None,
):
if not hosts or not tasks or not task_name:
return
defaults = {
'name': task_name,
......@@ -41,7 +43,7 @@ def update_or_create_ansible_task(
new_adhoc.become = become_info
if not adhoc or adhoc != new_adhoc:
logger.debug("Task create new adhoc: {}".format(task_name))
print("Task create new adhoc: {}".format(task_name))
new_adhoc.save()
task.latest_adhoc = new_adhoc
created = True
......
......@@ -303,7 +303,7 @@ div.dataTables_wrapper div.dataTables_filter {
.profile-element div:first-child {
line-height: 60px;
width: 70px;
/*width: 70px;*/
float: left;
text-align: center;
}
......
apps/static/img/avatar/admin.png

106 KB | W: | H:

apps/static/img/avatar/admin.png

11.5 KB | W: | H:

apps/static/img/avatar/admin.png
apps/static/img/avatar/admin.png
apps/static/img/avatar/admin.png
apps/static/img/avatar/admin.png
  • 2-up
  • Swipe
  • Onion skin
apps/static/img/avatar/user.png

61.3 KB | W: | H:

apps/static/img/avatar/user.png

11.5 KB | W: | H:

apps/static/img/avatar/user.png
apps/static/img/avatar/user.png
apps/static/img/avatar/user.png
apps/static/img/avatar/user.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -4,6 +4,6 @@
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
</div>
<div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2017
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018
</div>
</div>
\ No newline at end of file
......@@ -10,9 +10,9 @@
<!--</form>-->
</div>
<ul class="nav navbar-top-links navbar-right">
<li>
<span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>
</li>
{# <li>#}
{# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
{# </li>#}
<li class="dropdown">
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
<span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span>
......@@ -22,11 +22,10 @@
{% if request.user.is_authenticated %}
<a data-toggle="dropdown" class="dropdown-toggle" href="#">
<span class="m-r-sm text-muted welcome-message">
<img alt="image" class="img-circle" width="40" height="40" src="{{ request.user.avatar_url }}"/>
<strong class="font-bold"> {{ request.user.name }}
<span style="color: #8095a8"></span>
<img alt="image" class="img-circle" width="30" height="30" src="{{ request.user.avatar_url }}"/>
<span style="font-size: 13px;font-weight: 400"> {{ request.user.name }}
<b class="caret"></b>
</strong>
</span>
</span>
</a>
<ul class="dropdown-menu animated fadeInRight m-t-xs">
......
......@@ -2,11 +2,8 @@
{% load i18n %}
<li class="nav-header">
<div class="dropdown profile-element">
<div>
<img alt="image" height="40" src="/static/img/logo.png"/>
</div>
<div>
<a href="http://www.jumpserver.org" target="_blank">Jumpserver</a>
<div href="http://www.jumpserver.org" target="_blank">
<img alt="image" height="55" src="/static/img/logo-text.png" style="margin-left: 10px"/>
</div>
</div>
<div class="clearfix"></div>
......
......@@ -52,7 +52,7 @@
Copyright Jumpserver.org
</div>
<div class="col-md-6 text-right">
<small>2014-2017</small>
<small>2014-2018</small>
</div>
</div>
</div>
......
......@@ -164,7 +164,7 @@
{% for login in last_login_ten %}
<div class="feed-element">
<a href="#" class="pull-left">
<img alt="image" class="img-circle" src="/static/img/root.png">
<img alt="image" class="img-circle" src="{% static 'img/avatar/user.png' %}">
</a>
<div class="media-body ">
{% ifequal login.is_finished 0 %}
......
# -*- coding: utf-8 -*-
#
from collections import OrderedDict
import copy
import logging
import os
import uuid
......@@ -21,7 +20,8 @@ from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly
from .backends import get_command_store, SessionCommandSerializer
from .backends import get_command_store, get_multi_command_store, \
SessionCommandSerializer
logger = logging.getLogger(__file__)
......@@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet):
session = serializer.save()
return session
else:
msg = "session data is not valid {}".format(serializer.errors)
msg = "session data is not valid {}: {}".format(
serializer.errors, str(serializer.data)
)
logger.error(msg)
return None
......@@ -195,6 +197,7 @@ class CommandViewSet(viewsets.ViewSet):
"""
command_store = get_command_store()
multi_command_storage = get_multi_command_store()
serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,)
......@@ -215,7 +218,7 @@ class CommandViewSet(viewsets.ViewSet):
return Response({"msg": msg}, status=401)
def list(self, request, *args, **kwargs):
queryset = list(self.command_store.all())
queryset = self.multi_command_storage.filter()
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
......@@ -258,3 +261,13 @@ class SessionReplayViewSet(viewsets.ViewSet):
return redirect(url)
else:
return HttpResponseNotFound()
class TerminalConfig(APIView):
permission_classes = (IsAppUser,)
def get(self, request):
user = request.user
terminal = user.terminal
configs = terminal.config
return Response(configs, status=200)
......@@ -2,9 +2,39 @@ from importlib import import_module
from django.conf import settings
from .command.serializers import SessionCommandSerializer
TYPE_ENGINE_MAPPING = {
'elasticsearch': 'terminal.backends.command.es',
}
def get_command_store():
command_engine = import_module(settings.COMMAND_STORAGE_BACKEND)
command_store = command_engine.CommandStore()
return command_store
params = settings.COMMAND_STORAGE
engine_class = import_module(params['ENGINE'])
storage = engine_class.CommandStore(params)
return storage
def get_terminal_command_store():
storage_list = {}
for name, params in settings.TERMINAL_COMMAND_STORAGE.items():
tp = params['TYPE']
if tp == 'server':
storage = get_command_store()
else:
if not TYPE_ENGINE_MAPPING.get(tp):
raise AssertionError("Command storage type should in {}".format(
', '.join(TYPE_ENGINE_MAPPING.keys()))
)
engine_class = import_module(TYPE_ENGINE_MAPPING[tp])
storage = engine_class.CommandStore(params)
storage_list[name] = storage
return storage_list
def get_multi_command_store():
from .command.multi import CommandStore
storage_list = get_terminal_command_store().values()
storage = CommandStore(storage_list)
return storage
......@@ -19,3 +19,9 @@ class CommandBase(object):
input=None, session=None):
pass
@abc.abstractmethod
def count(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
pass
......@@ -8,7 +8,7 @@ from .base import CommandBase
class CommandStore(CommandBase):
def __init__(self):
def __init__(self, params):
from terminal.models import Command
self.model = Command
......@@ -37,7 +37,9 @@ class CommandStore(CommandBase):
))
return self.model.objects.bulk_create(_commands)
def filter(self, date_from=None, date_to=None,
@staticmethod
def make_filter_kwargs(
date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
filter_kwargs = {}
......@@ -60,10 +62,28 @@ class CommandStore(CommandBase):
if session:
filter_kwargs['session'] = session
return filter_kwargs
def filter(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
filter_kwargs = self.make_filter_kwargs(
date_from=date_from, date_to=date_to, user=user,
asset=asset, system_user=system_user, input=input,
session=session,
)
queryset = self.model.objects.filter(**filter_kwargs)
return queryset
return [command.to_dict() for command in queryset]
def count(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
filter_kwargs = self.make_filter_kwargs(
date_from=date_from, date_to=date_to, user=user,
asset=asset, system_user=system_user, input=input,
session=session,
)
count = self.model.objects.filter(**filter_kwargs).count()
return count
def all(self):
"""返回所有数据"""
return self.model.objects.iterator()
# -*- coding: utf-8 -*-
#
from jms_es_sdk import ESStore
from .base import CommandBase
class CommandStore(CommandBase, ESStore):
def __init__(self, params):
hosts = params.get('HOSTS', ['http://localhost'])
ESStore.__init__(self, hosts=hosts)
def save(self, command):
return ESStore.save(self, command)
def bulk_save(self, commands):
return ESStore.bulk_save(self, commands)
def filter(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
data = ESStore.filter(
self, date_from=date_from, date_to=date_to,
user=user, asset=asset, system_user=system_user,
input=input, session=session
)
return [item["_source"] for item in data["hits"] if item]
def count(self, date_from=None, date_to=None,
user=None, asset=None, system_user=None,
input=None, session=None):
amount = ESStore.count(
self, date_from=date_from, date_to=date_to,
user=user, asset=asset, system_user=system_user,
input=input, session=session
)
return amount
......@@ -18,5 +18,26 @@ class AbstractSessionCommand(models.Model):
class Meta:
abstract = True
@classmethod
def from_dict(cls, d):
self = cls()
for k, v in d.items():
setattr(self, k, v)
return self
@classmethod
def from_multi_dict(cls, l):
commands = []
for d in l:
command = cls.from_dict(d)
commands.append(command)
return commands
def to_dict(self):
d = {}
for field in self._meta.fields:
d[field.name] = getattr(self, field.name)
return d
def __str__(self):
return self.input
# -*- coding: utf-8 -*-
#
from .base import CommandBase
class CommandStore(CommandBase):
def __init__(self, storage_list):
self.storage_list = storage_list
def filter(self, **kwargs):
queryset = []
for storage in self.storage_list:
queryset.extend(storage.filter(**kwargs))
return sorted(queryset, key=lambda command: command["timestamp"], reverse=True)
def count(self, **kwargs):
amount = 0
for storage in self.storage_list:
amount += storage.count(**kwargs)
return amount
def save(self, command):
pass
def bulk_save(self, commands):
pass
......@@ -2,15 +2,27 @@
#
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from .models import Terminal
def get_all_command_storage():
# storage_choices = []
from common.models import Setting
Setting.refresh_all_settings()
for k, v in settings.TERMINAL_COMMAND_STORAGE.items():
yield (k, k)
class TerminalForm(forms.ModelForm):
command_storage = forms.ChoiceField(choices=get_all_command_storage(),
label=_("Command storage"))
class Meta:
model = Terminal
fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment']
fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment', 'command_storage']
help_texts = {
'ssh_port': _("Coco ssh listen port"),
'http_port': _("Coco http/ws listen port"),
......
......@@ -4,6 +4,7 @@ import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from users.models import User
from .backends.command.models import AbstractSessionCommand
......@@ -15,6 +16,8 @@ class Terminal(models.Model):
remote_addr = models.CharField(max_length=128, verbose_name=_('Remote Address'))
ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222)
http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000)
command_storage = models.CharField(max_length=128, verbose_name=_("Command storage"), default='default')
replay_storage = models.CharField(max_length=128, verbose_name=_("Replay storage"), default='default')
user = models.OneToOneField(User, related_name='terminal', verbose_name='Application User', null=True, on_delete=models.CASCADE)
is_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
is_deleted = models.BooleanField(default=False)
......@@ -33,6 +36,26 @@ class Terminal(models.Model):
self.user.is_active = active
self.user.save()
def get_common_storage(self):
storage_all = settings.TERMINAL_COMMAND_STORAGE
if self.command_storage in storage_all:
storage = storage_all.get(self.command_storage)
else:
storage = storage_all.get('default')
return {"TERMINAL_COMMAND_STORAGE": storage}
def get_replay_storage(self):
pass
@property
def config(self):
configs = {}
for k in dir(settings):
if k.startswith('TERMINAL'):
configs[k] = getattr(settings, k)
configs.update(self.get_common_storage())
return configs
def create_app_user(self):
random = uuid.uuid4().hex[:6]
user, access_key = User.create_app_user(name="{}-{}".format(self.name, random), comment=self.comment)
......
......@@ -5,7 +5,7 @@ from django.utils import timezone
from rest_framework import serializers
from .models import Terminal, Status, Session, Task
from .backends import get_command_store
from .backends import get_multi_command_store
class TerminalSerializer(serializers.ModelSerializer):
......@@ -43,14 +43,14 @@ class TerminalSerializer(serializers.ModelSerializer):
class SessionSerializer(serializers.ModelSerializer):
command_amount = serializers.SerializerMethodField()
command_store = get_command_store()
command_store = get_multi_command_store()
class Meta:
model = Session
fields = '__all__'
def get_command_amount(self, obj):
return len(self.command_store.filter(session=obj.session))
return self.command_store.count(session=str(obj.id))
class StatusSerializer(serializers.ModelSerializer):
......
......@@ -13,10 +13,6 @@ RUNNING = False
logger = get_logger(__file__)
@shared_task
@register_as_period_task(interval=3600)
@after_app_ready_start
@after_app_shutdown_clean
def set_session_info_cache():
logger.debug("")
from .utils import get_session_asset_list, get_session_user_list, \
......
......@@ -12,6 +12,7 @@
{% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.ssh_port layout="horizontal" %}
{% bootstrap_field form.http_port layout="horizontal" %}
{% bootstrap_field form.command_storage layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
</form>
......
......@@ -35,6 +35,7 @@
{% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.ssh_port layout="horizontal" %}
{% bootstrap_field form.http_port layout="horizontal" %}
{% bootstrap_field form.command_storage layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
......
# ~*~ coding: utf-8 ~*~
from django import template
from ..backends import get_command_store
from ..backends import get_multi_command_store
register = template.Library()
command_store = get_command_store()
command_store = get_multi_command_store()
@register.filter
def get_session_command_amount(session_id):
return len(command_store.filter(session=str(session_id)))
return command_store.count(session=session_id)
......@@ -20,7 +20,8 @@ urlpatterns = [
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'),
url(r'^v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key')
url(r'^v1/terminal/(?P<terminal>[a-zA-Z0-9\-]{36})/access-key', api.TerminalTokenApi.as_view(), name='terminal-access-key'),
url(r'^v1/terminal/config', api.TerminalConfig.as_view(), name='terminal-config'),
]
urlpatterns += router.urls
......@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
from common.mixins import DatetimeSearchMixin
from ..models import Command
from .. import utils
from ..backends import get_command_store
from ..backends import get_multi_command_store
__all__ = ['CommandListView']
command_store = get_command_store()
common_storage = get_multi_command_store()
class CommandListView(DatetimeSearchMixin, ListView):
......@@ -39,7 +39,7 @@ class CommandListView(DatetimeSearchMixin, ListView):
filter_kwargs['system_user'] = self.system_user
if self.command:
filter_kwargs['input'] = self.command
queryset = command_store.filter(**filter_kwargs)
queryset = common_storage.filter(**filter_kwargs)
return queryset
def get_context_data(self, **kwargs):
......
......@@ -10,7 +10,7 @@ from django.conf import settings
from users.utils import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal
from ..backends import get_command_store
from ..backends import get_multi_command_store
from .. import utils
......@@ -19,7 +19,7 @@ __all__ = [
'SessionDetailView',
]
command_store = get_command_store()
command_store = get_multi_command_store()
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
......
# ~*~ coding: utf-8 ~*~
from rest_framework import generics
from rest_framework.permissions import AllowAny
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_bulk import BulkModelViewSet
from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer
UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async
from .models import User, UserGroup
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly
......@@ -24,10 +24,21 @@ class UserViewSet(CustomFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App")
# queryset = User.objects.all().exclude(role="App").order_by("date_joined")
serializer_class = UserSerializer
permission_classes = (IsSuperUser,)
permission_classes = (IsSuperUser, IsAuthenticated)
filter_fields = ('username', 'email', 'name', 'id')
class ChangeUserPasswordApi(generics.RetrieveUpdateAPIView):
permission_classes = (IsSuperUser,)
queryset = User.objects.all()
serializer_class = ChangeUserPasswordSerializer
def perform_update(self, serializer):
user = self.get_object()
user.password_raw = serializer.validated_data["password"]
user.save()
class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer
......@@ -37,6 +48,7 @@ class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer):
# Note: we are not updating the user object here.
......@@ -128,7 +140,11 @@ class UserAuthApi(APIView):
user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip:
login_ip = request.META.get('HTTP_X_FORWARDED_FOR') or request.META.get("REMOTE_ADDR")
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for:
login_ip = x_forwarded_for[0]
else:
login_ip = request.META.get("REMOTE_ADDR")
user, msg = check_user_valid(
username=username, password=password,
......
......@@ -172,7 +172,6 @@ class UserBulkUpdateForm(forms.ModelForm):
if self.data.get(field) is not None:
changed_fields.append(field)
print(changed_fields)
cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields}
users = cleaned_data.pop('users', '')
......
......@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app.
:copyright: (c) 2014-2017 by Jumpserver Team.
:copyright: (c) 2014-2018 by Jumpserver Team.
:license: GPL v2, see LICENSE for more details.
"""
......
......@@ -71,3 +71,9 @@ class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
model = UserGroup
fields = ['id', 'users']
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['password']
......@@ -11,8 +11,8 @@
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
{% csrf_token %}
<h3>{% trans 'Account' %}</h3>
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.email layout="horizontal" %}
{% bootstrap_field form.groups layout="horizontal" %}
......@@ -32,12 +32,6 @@
<span class="help-block ">{{ form.date_expired.errors }}</span>
</div>
</div>
{# <div class="form-group">#}
{# <label for="{{ form.enable_otp.id_for_label }}" class="col-sm-2 control-label">{% trans 'Enable OTP' %}</label>#}
{# <div class="col-sm-8">#}
{# {{ form.enable_otp }}#}
{# </div>#}
{# </div>#}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Profile' %}</h3>
{% bootstrap_field form.phone layout="horizontal" %}
......
......@@ -55,7 +55,7 @@
Copyright Jumpserver.org
</div>
<div class="col-md-6 text-right">
<small>© 2014-2017</small>
<small>© 2014-2018</small>
</div>
</div>
</div>
......
......@@ -78,7 +78,7 @@
Copyright Jumpserver.org
</div>
<div class="col-md-6 text-right">
<small>© 2014-2017</small>
<small>© 2014-2018</small>
</div>
</div>
</div>
......
......@@ -74,7 +74,7 @@
Copyright Jumpserver.org
</div>
<div class="col-md-6 text-right">
<small>© 2014-2017</small>
<small>© 2014-2018</small>
</div>
</div>
</div>
......
......@@ -24,8 +24,9 @@
<li class="pull-right">
<a class="btn btn-outline btn-default" href="{% url 'users:user-update' pk=user_object.id %}"><i class="fa fa-edit"></i>{% trans 'Update' %}</a>
</li>
<li class="pull-right">
<a class="btn btn-outline btn-danger btn-delete-user">
<a class="btn btn-outline {% if request.user != user_object and user_object.username != "admin" %} btn-danger btn-delete-user {% else %} disabled {% endif %}">
<i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a>
</li>
......@@ -128,7 +129,7 @@
<td><span class="pull-right">
<div class="switch">
<div class="onoffswitch">
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} class="onoffswitch-checkbox" id="is_active">
<input type="checkbox" {% if user_object.is_active %} checked {% endif %} {% if request.user == user_object %} disabled {% endif %} class="onoffswitch-checkbox" id="is_active">
<label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
......@@ -156,7 +157,7 @@
<td>{% trans 'Send reset password mail' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-reset-password" style="width: 54px">{% trans 'Send' %}</button>
<button type="button" class="btn btn-primary btn-xs" {% if request.user == user_object %} disabled="disabled" {% endif %} id="btn-reset-password" style="width: 54px">{% trans 'Send' %}</button>
</span>
</td>
</tr>
......@@ -164,7 +165,7 @@
<td>{% trans 'Send reset ssh key mail' %}:</td>
<td>
<span class="pull-right">
<button type="button" class="btn btn-primary btn-xs" id="btn-reset-pk" style="width: 54px;">{% trans 'Send' %}</button>
<button type="button" class="btn btn-primary btn-xs" {% if request.user == user_object %} disabled="disabled" {% endif %} id="btn-reset-pk" style="width: 54px;">{% trans 'Send' %}</button>
</span>
</td>
</tr>
......@@ -331,7 +332,7 @@ $(document).ready(function() {
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans "This will reset the user's password. A password-reset email will be sent to the user\'s mailbox." %}",
text: "{% trans "This will reset the user password and send a reset mail"%}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
......@@ -356,7 +357,7 @@ $(document).ready(function() {
}
swal({
title: "{% trans 'Are you sure?' %}",
text: "{% trans 'This will reset the user\'s public key.' %}",
text: "{% trans 'This will reset the user public key and send a reset mail' %}",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
......
......@@ -76,7 +76,7 @@ function initTable() {
var update_btn = '<a href="{% url "users:user-update" pk=DEFAULT_PK %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'.replace('00000000-0000-0000-0000-000000000000', cellData);
var del_btn = "";
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ user.username }}") {
if (rowData.id === 1 || rowData.username === "admin" || rowData.username === "{{ request.user.username }}") {
del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', rowData.name);
......
......@@ -19,6 +19,8 @@ urlpatterns = [
url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'),
url(r'^v1/profile/$', api.UserProfile.as_view(), name='user-profile'),
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/$',
api.ChangeUserPasswordApi.as_view(), name='change-user-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$',
api.UserResetPasswordApi.as_view(), name='user-reset-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/reset/$',
......
......@@ -180,7 +180,9 @@ def validate_ip(ip):
def write_login_log(username, type='', ip='', user_agent=''):
if not (ip and validate_ip(ip)):
ip = '0.0.0.0'
ip = ip[:15]
city = "Unknown"
else:
city = get_ip_city(ip)
LoginLog.objects.create(
username=username, type=type,
......
......@@ -53,8 +53,11 @@ class UserLoginView(FormView):
if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again."))
auth_login(self.request, form.get_user())
login_ip = self.request.META.get('HTTP_X_FORWARDED_FOR') or \
self.request.META.get('REMOTE_ADDR', '')
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
if x_forwarded_for and x_forwarded_for[0]:
login_ip = x_forwarded_for[0]
else:
login_ip = self.request.META.get('REMOTE_ADDR', '')
user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay(
self.request.user.username, type='W',
......
## Jumpserver v0.4.0 版本安装详细过程
### 环境
- 系统: CentOS 6.5 x86\_64 mini
- Python: 版本 3.6 大部分功能兼容 2.7
- 安装目录
- /opt/jumpserver
- /opt/coco
#### 一. 环境准备
##### 1.1 安装基本工具和库
$ yum -y install sqlite-devel git epel-release
$ yum -y install sshpass python-devel libffi-devel openssl-devel
$ yum -y install gcc gcc-c++
##### 1.2 安装Python 3.6 和 虚拟环境
#### 二. Jumpserver安装
##### 2.1 下载仓库代码
$ cd /opt
$ git clone https://github.com/jumpserver/jumpserver.git
$ cd jumpserver
$ git checkout dev
##### 2.2 安装依赖
$ cd requirements
$ sudo yum -y install `cat rpm_requirements.txt`
$ pip install -r requirements.txt -i https://pypi.doubanio.com/simple
// 解决Mac安装ldap提示 Modules/LDAPObject.c:18:10: fatal error: 'sasl.h' file not found
pip install python-ldap \
--global-option=build_ext \
--global-option="-I$(xcrun --show-sdk-path)/usr/include/sasl"
##### 2.3 准备配置文件
$ cd ..
$ cp config_example.py config.py
$ vim config.py
// 默认使用的是 DevelpmentConfig 所以应该去修改这部分
class DevelopmentConfig(Config):
EMAIL_HOST = 'smtp.exmail.qq.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = 'ask@jumpserver.org'
EMAIL_HOST_PASSWORD = 'xxx'
EMAIL_USE_SSL = True // 端口是 465 设置 True 否则 False
EMAIL_USE_TLS = False // 端口是 587 设置为 True 否则 False
SITE_URL = 'http://localhost:8080' // 发送邮件会使用这个地址
##### 2.4 初始化数据库
$ cd utils
$ sh make_migrations.sh
$ sh init_db.sh
##### 2.5 安装redis server
$ yum -y install redis
$ service redis start
**2.6 启动**
```
$ cd ..
$ python run_server.py
```
访问 http://ip:8080
账号密码: admin admin
**2.7 测试使用**
- 创建用户
会发送邮件,测试是否正常修改密码,登录
- 创建管理用户
创建一个管理用户, 创建资产时需要关联
- 创建资产
创建一个 资产,关联刚创建的管理用户
- 创建系统用户
系统用户是用来登录资产的,授权时需要
- 创建授权规则
关联用户,资产,系统用户 形成授权规则,授权的系统用户会自动推送到资产上
#### 三. 安装 SSH SERVER - COCO
**3.1 下载代码库**
```
$ cd /opt
$ git clone https://github.com/jumpserver/coco.git
```
**3.2 安装依赖**
```
$ cd coco
$ pip install -r requirements.txt # -i https://pypi.doubanio.com/simple
```
**3.3 启动**
```
$ python run_server.py
```
说明: Coco启动后会向jumpserver注册,请去 jumpserver页面 - 应用程序 - terminal - coco - Accept 允许, 这时 coco就 运行在 2222端口,可以ssh来连接
命令行:
``` 
ssh admin@YourServerIP -p2222
```
**3.5 测试**
- 测试登录 ssh server
- 测试跳转
- 测试命令记录回
[1]: https://segmentfault.com/a/1190000000654227
[2]: https://github.com/jumpserver/jumpserver.git
[3]: https://github.com/jumpserver/coco.git
More see [安装文档](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
......@@ -56,6 +56,8 @@ uritemplate==3.0.0
urllib3==1.22
vine==1.1.4
gunicorn==19.7.1
django_celery_beat==1.1.0
https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat
#django_celery_beat==1.1.0
ephem==3.7.6.0
python-gssapi==0.6.4
jms-es-sdk
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment