Commit f37b3316 authored by ibuler's avatar ibuler

Merge with dev

parents 5bbad019 9dbf4983
...@@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互 ...@@ -26,36 +26,7 @@ Jumpserver是一款使用Python, Django开发的开源跳板机系统, 助力互
### Install 安装 ### Install 安装
1. 安装 Python3    [详细安装](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
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来配合, 详见详细安装文档
### Usage 使用 ### Usage 使用
......
# ~*~ coding: utf-8 ~*~ # ~*~ 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"); # Licensed under the GNU General Public License v2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
...@@ -87,7 +87,7 @@ class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet): ...@@ -87,7 +87,7 @@ class AssetGroupViewSet(CustomFilterMixin, BulkModelViewSet):
""" """
Asset group api set, for add,delete,update,list,retrieve resource 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 serializer_class = serializers.AssetGroupSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
...@@ -298,9 +298,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): ...@@ -298,9 +298,7 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView):
class LabelViewSet(BulkModelViewSet): class LabelViewSet(BulkModelViewSet):
queryset = Label.objects.annotate(asset_count=Count("assets"))\ queryset = Label.objects.annotate(asset_count=Count("assets"))
.annotate(admin_user_count=Count("adminuser")) \
.annotate(system_user_count=Count("systemuser"))
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser,)
serializer_class = serializers.LabelSerializer serializer_class = serializers.LabelSerializer
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app. 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. :license: GPL v2, see LICENSE for more details.
""" """
......
...@@ -16,7 +16,7 @@ class Label(models.Model): ...@@ -16,7 +16,7 @@ class Label(models.Model):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name")) name = models.CharField(max_length=128, verbose_name=_("Name"))
value = models.CharField(max_length=128, verbose_name=_("Value")) 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")) is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
date_created = models.DateTimeField( date_created = models.DateTimeField(
......
...@@ -30,7 +30,6 @@ class AssetUser(models.Model): ...@@ -30,7 +30,6 @@ class AssetUser(models.Model):
_password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) _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, ]) _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')) _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')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
date_created = models.DateTimeField(auto_now_add=True) date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True) date_updated = models.DateTimeField(auto_now=True)
......
...@@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer): ...@@ -22,7 +22,7 @@ class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
@staticmethod @staticmethod
def get_assets_amount(obj): def get_assets_amount(obj):
return obj.assets.count() return obj.asset_count
class AssetUpdateSystemUserSerializer(serializers.ModelSerializer): class AssetUpdateSystemUserSerializer(serializers.ModelSerializer):
...@@ -288,8 +288,6 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer): ...@@ -288,8 +288,6 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer):
class LabelSerializer(serializers.ModelSerializer): class LabelSerializer(serializers.ModelSerializer):
asset_count = serializers.SerializerMethodField() asset_count = serializers.SerializerMethodField()
admin_user_count = serializers.SerializerMethodField()
system_user_count = serializers.SerializerMethodField()
class Meta: class Meta:
model = Label model = Label
...@@ -300,14 +298,6 @@ class LabelSerializer(serializers.ModelSerializer): ...@@ -300,14 +298,6 @@ class LabelSerializer(serializers.ModelSerializer):
def get_asset_count(obj): def get_asset_count(obj):
return obj.asset_count 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): def get_field_names(self, declared_fields, info):
fields = super().get_field_names(declared_fields, info) fields = super().get_field_names(declared_fields, info)
fields.extend(['get_category_display']) fields.extend(['get_category_display'])
......
...@@ -121,7 +121,7 @@ function initTable() { ...@@ -121,7 +121,7 @@ function initTable() {
{data: "type" }, {data: "is_connective" }], {data: "type" }, {data: "is_connective" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
$(document).ready(function () { $(document).ready(function () {
......
...@@ -184,7 +184,7 @@ function initTable() { ...@@ -184,7 +184,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
......
...@@ -34,10 +34,6 @@ $(document).ready(function(){ ...@@ -34,10 +34,6 @@ $(document).ready(function(){
var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>'; var detail_btn = '<a href="{% url "assets:asset-group-detail" pk=DEFAULT_PK %}">' + cellData + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(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) { {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 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); 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() { ...@@ -176,7 +176,7 @@ function initTable() {
{data: "get_type_display" }, {data: "is_connective" }, {data: "id"}], {data: "get_type_display" }, {data: "is_connective" }, {data: "id"}],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
......
...@@ -14,8 +14,6 @@ ...@@ -14,8 +14,6 @@
<th class="text-center">{% trans 'Name' %}</th> <th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Value' %}</th> <th class="text-center">{% trans 'Value' %}</th>
<th class="text-center">{% trans 'Asset' %}</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> <th class="text-center">{% trans 'Action' %}</th>
</tr> </tr>
</thead> </thead>
...@@ -36,7 +34,7 @@ function initTable() { ...@@ -36,7 +34,7 @@ function initTable() {
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', rowData.id)); $(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 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); 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) $(td).html(update_btn + del_btn)
...@@ -44,8 +42,7 @@ function initTable() { ...@@ -44,8 +42,7 @@ function initTable() {
ajax_url: '{% url "api-assets:label-list" %}?sort=name', ajax_url: '{% url "api-assets:label-list" %}?sort=name',
columns: [ columns: [
{data: "id"}, {data: "name" }, {data: "value" }, {data: "id"}, {data: "name" }, {data: "value" },
{data: "asset_count" }, {data: "admin_user_count" }, {data: "asset_count" }, {data: "id"}
{data: "system_user_count" }, {data: "id"}
], ],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
......
...@@ -121,7 +121,7 @@ function initAssetsTable() { ...@@ -121,7 +121,7 @@ function initAssetsTable() {
columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }], columns: [{data: "hostname" }, {data: "ip" }, {data: "port" }, {data: "hostname" }],
op_html: $('#actions').html() op_html: $('#actions').html()
}; };
jumpserver.initDataTable(options); jumpserver.initServerSideDataTable(options);
} }
$(document).ready(function () { $(document).ready(function () {
......
...@@ -60,11 +60,6 @@ class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView) ...@@ -60,11 +60,6 @@ class ClusterUpdateView(AdminUserRequiredMixin, SuccessMessageMixin, UpdateView)
success_url = reverse_lazy('assets:cluster-list') success_url = reverse_lazy('assets:cluster-list')
success_message = update_success_msg 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): def get_context_data(self, **kwargs):
context = { context = {
'app': _('assets'), 'app': _('assets'),
......
...@@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView): ...@@ -63,8 +63,6 @@ class LDAPTestingAPI(APIView):
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"] search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"] attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
print(serializer.validated_data)
try: try:
attr_map = json.loads(attr_map) attr_map = json.loads(attr_map)
except json.JSONDecodeError: except json.JSONDecodeError:
...@@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView): ...@@ -77,9 +75,6 @@ class LDAPTestingAPI(APIView):
except Exception as e: except Exception as e:
return Response({"error": str(e)}, status=401) 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": "*"}), ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values())) attributes=list(attr_map.values()))
if not ok: if not ok:
......
...@@ -5,6 +5,7 @@ import json ...@@ -5,6 +5,7 @@ import json
from django import forms from django import forms
from django.utils import six from django.utils import six
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
class DictField(forms.Field): class DictField(forms.Field):
...@@ -18,16 +19,16 @@ class DictField(forms.Field): ...@@ -18,16 +19,16 @@ class DictField(forms.Field):
# we don't need to handle that explicitly. # we don't need to handle that explicitly.
if isinstance(value, six.string_types): if isinstance(value, six.string_types):
try: try:
print(value)
value = json.loads(value) value = json.loads(value)
return value return value
except json.JSONDecodeError: except json.JSONDecodeError:
pass return ValidationError(_("Not a valid json"))
value = {} else:
return value return ValidationError(_("Not a string type"))
def validate(self, value): def validate(self, value):
print(value) if isinstance(value, ValidationError):
raise value
if not value and self.required: if not value and self.required:
raise ValidationError(self.error_messages['required'], code='required') raise ValidationError(self.error_messages['required'], code='required')
......
...@@ -4,7 +4,9 @@ import json ...@@ -4,7 +4,9 @@ import json
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.html import escape
from django.db import transaction from django.db import transaction
from django.conf import settings
from .models import Setting from .models import Setting
from .fields import DictField from .fields import DictField
...@@ -24,34 +26,38 @@ def to_form_value(value): ...@@ -24,34 +26,38 @@ def to_form_value(value):
data = value data = value
return data return data
except json.JSONDecodeError: except json.JSONDecodeError:
return '' return ""
class BaseForm(forms.Form): class BaseForm(forms.Form):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
settings = Setting.objects.all() db_settings = Setting.objects.all()
for name, field in self.fields.items(): for name, field in self.fields.items():
db_value = getattr(settings, name).value db_value = getattr(db_settings, name).value
if db_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) 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: if not self.is_bound:
raise ValueError("Form is not bound") raise ValueError("Form is not bound")
settings = Setting.objects.all() db_settings = Setting.objects.all()
if self.is_valid(): if self.is_valid():
with transaction.atomic(): with transaction.atomic():
for name, value in self.cleaned_data.items(): for name, value in self.cleaned_data.items():
field = self.fields[name] field = self.fields[name]
if isinstance(field.widget, forms.PasswordInput) and not value: if isinstance(field.widget, forms.PasswordInput) and not value:
continue continue
if value == to_form_value(getattr(settings, name).value): if value == to_form_value(getattr(db_settings, name).value):
continue continue
defaults = { defaults = {
'name': name, 'name': name,
'category': category,
'value': to_model_value(value) 'value': to_model_value(value)
} }
Setting.objects.update_or_create(defaults=defaults, name=name) Setting.objects.update_or_create(defaults=defaults, name=name)
...@@ -72,9 +78,6 @@ class BasicSettingForm(BaseForm): ...@@ -72,9 +78,6 @@ class BasicSettingForm(BaseForm):
max_length=1024, label=_("Email Subject Prefix"), max_length=1024, label=_("Email Subject Prefix"),
initial="[Jumpserver] " initial="[Jumpserver] "
) )
AUTH_LDAP = forms.BooleanField(
label=_("Enable LDAP Auth"), initial=False, required=False
)
class EmailSettingForm(BaseForm): class EmailSettingForm(BaseForm):
...@@ -129,3 +132,28 @@ class LDAPSettingForm(BaseForm): ...@@ -129,3 +132,28 @@ class LDAPSettingForm(BaseForm):
AUTH_LDAP_START_TLS = forms.BooleanField( AUTH_LDAP_START_TLS = forms.BooleanField(
label=_("Use SSL"), initial=False, required=False 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 ...@@ -2,6 +2,7 @@ import json
import ldap import ldap
from django.db import models from django.db import models
from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from django_auth_ldap.config import LDAPSearch from django_auth_ldap.config import LDAPSearch
...@@ -24,6 +25,7 @@ class SettingManager(models.Manager): ...@@ -24,6 +25,7 @@ class SettingManager(models.Manager):
class Setting(models.Model): class Setting(models.Model):
name = models.CharField(max_length=128, unique=True, verbose_name=_("Name")) name = models.CharField(max_length=128, unique=True, verbose_name=_("Name"))
value = models.TextField(verbose_name=_("Value")) value = models.TextField(verbose_name=_("Value"))
category = models.CharField(max_length=128, default="default")
enabled = models.BooleanField(verbose_name=_("Enabled"), default=True) enabled = models.BooleanField(verbose_name=_("Enabled"), default=True)
comment = models.TextField(verbose_name=_("Comment")) comment = models.TextField(verbose_name=_("Comment"))
...@@ -33,17 +35,28 @@ class Setting(models.Model): ...@@ -33,17 +35,28 @@ class Setting(models.Model):
return self.name return self.name
@property @property
def value_(self): def cleaned_value(self):
try: try:
return json.loads(self.value) return json.loads(self.value)
except json.JSONDecodeError: except json.JSONDecodeError:
return None 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 @classmethod
def refresh_all_settings(cls): def refresh_all_settings(cls):
try:
settings_list = cls.objects.all() settings_list = cls.objects.all()
for setting in settings_list: for setting in settings_list:
setting.refresh_setting() setting.refresh_setting()
except (ProgrammingError, OperationalError):
pass
def refresh_setting(self): def refresh_setting(self):
try: try:
...@@ -53,9 +66,9 @@ class Setting(models.Model): ...@@ -53,9 +66,9 @@ class Setting(models.Model):
setattr(settings, self.name, value) setattr(settings, self.name, value)
if self.name == "AUTH_LDAP": 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) 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) settings.AUTHENTICATION_BACKENDS.remove(settings.AUTH_LDAP_BACKEND)
if self.name == "AUTH_LDAP_SEARCH_FILTER": if self.name == "AUTH_LDAP_SEARCH_FILTER":
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
<li> <li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a> <a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
<li> <li>
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a> <a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <div class="tab-content">
......
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
<li class="active"> <li class="active">
<a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a> <a href="{% url 'settings:ldap-setting' %}" class="text-center"><i class="fa fa-archive"></i> {% trans 'LDAP setting' %} </a>
</li> </li>
<li>
<a href="{% url 'settings:terminal-setting' %}" class="text-center"><i class="fa fa-hdd-o"></i> {% trans 'Terminal setting' %} </a>
</li>
</ul> </ul>
</div> </div>
<div class="tab-content"> <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 = [ ...@@ -10,4 +10,5 @@ urlpatterns = [
url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'), url(r'^$', views.BasicSettingView.as_view(), name='basic-setting'),
url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'), url(r'^email/$', views.EmailSettingView.as_view(), name='email-setting'),
url(r'^ldap/$', views.LDAPSettingView.as_view(), name='ldap-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.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ 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 .mixins import AdminUserRequiredMixin
from .signals import ldap_auth_enable from .signals import ldap_auth_enable
...@@ -25,8 +28,6 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -25,8 +28,6 @@ class BasicSettingView(AdminUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() 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") msg = _("Update setting successfully, please restart program")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:basic-setting') return redirect('settings:basic-setting')
...@@ -79,6 +80,8 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -79,6 +80,8 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
form = self.form_class(request.POST) form = self.form_class(request.POST)
if form.is_valid(): if form.is_valid():
form.save() 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") msg = _("Update setting successfully, please restart program")
messages.success(request, msg) messages.success(request, msg)
return redirect('settings:ldap-setting') return redirect('settings:ldap-setting')
...@@ -86,3 +89,31 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView): ...@@ -86,3 +89,31 @@ class LDAPSettingView(AdminUserRequiredMixin, TemplateView):
context = self.get_context_data() context = self.get_context_data()
context.update({"form": form}) context.update({"form": form})
return render(request, self.template_name, context) 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 ...@@ -274,7 +274,7 @@ EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD EMAIL_HOST_PASSWORD = CONFIG.EMAIL_HOST_PASSWORD
EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL EMAIL_USE_SSL = CONFIG.EMAIL_USE_SSL
EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS EMAIL_USE_TLS = CONFIG.EMAIL_USE_TLS
EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX EMAIL_SUBJECT_PREFIX = CONFIG.EMAIL_SUBJECT_PREFIX or ''
REST_FRAMEWORK = { REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions, # Use Django's standard `django.contrib.auth` permissions,
...@@ -298,7 +298,7 @@ REST_FRAMEWORK = { ...@@ -298,7 +298,7 @@ REST_FRAMEWORK = {
'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z', 'DATETIME_FORMAT': '%Y-%m-%d %H:%M:%S %z',
'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'], 'DATETIME_INPUT_FORMATS': ['%Y-%m-%d %H:%M:%S %z'],
# 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 15 # 'PAGE_SIZE': 15
} }
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
...@@ -373,7 +373,20 @@ CAPTCHA_FOREGROUND_COLOR = '#001100' ...@@ -373,7 +373,20 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',) CAPTCHA_NOISE_FUNCTIONS = ('captcha.helpers.noise_dots',)
CAPTCHA_TEST_MODE = CONFIG.CAPTCHA_TEST_MODE 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 # Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
BOOTSTRAP3 = { BOOTSTRAP3 = {
......
This diff is collapsed.
...@@ -165,7 +165,7 @@ class AdHoc(models.Model): ...@@ -165,7 +165,7 @@ class AdHoc(models.Model):
if item and isinstance(item, list): if item and isinstance(item, list):
self._tasks = json.dumps(item) self._tasks = json.dumps(item)
else: else:
raise SyntaxError('Tasks should be a list') raise SyntaxError('Tasks should be a list: {}'.format(item))
@property @property
def hosts(self): def hosts(self):
......
...@@ -16,6 +16,8 @@ def update_or_create_ansible_task( ...@@ -16,6 +16,8 @@ def update_or_create_ansible_task(
run_as_admin=False, run_as="", become_info=None, run_as_admin=False, run_as="", become_info=None,
created_by=None, created_by=None,
): ):
if not hosts or not tasks or not task_name:
return
defaults = { defaults = {
'name': task_name, 'name': task_name,
...@@ -41,7 +43,7 @@ def update_or_create_ansible_task( ...@@ -41,7 +43,7 @@ def update_or_create_ansible_task(
new_adhoc.become = become_info new_adhoc.become = become_info
if not adhoc or adhoc != new_adhoc: 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() new_adhoc.save()
task.latest_adhoc = new_adhoc task.latest_adhoc = new_adhoc
created = True created = True
......
...@@ -303,7 +303,7 @@ div.dataTables_wrapper div.dataTables_filter { ...@@ -303,7 +303,7 @@ div.dataTables_wrapper div.dataTables_filter {
.profile-element div:first-child { .profile-element div:first-child {
line-height: 60px; line-height: 60px;
width: 70px; /*width: 70px;*/
float: left; float: left;
text-align: center; 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 @@ ...@@ -4,6 +4,6 @@
<img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg"> <img style="display: none" src="http://www.jumpserver.org/img/evaluate_avatar1.jpg">
</div> </div>
<div> <div>
<strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2017 <strong>Copyright</strong> 北京堆栈科技有限公司 &copy; 2014-2018
</div> </div>
</div> </div>
\ No newline at end of file
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
<!--</form>--> <!--</form>-->
</div> </div>
<ul class="nav navbar-top-links navbar-right"> <ul class="nav navbar-top-links navbar-right">
<li> {# <li>#}
<span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span> {# <span class="m-r-sm text-muted welcome-message">{% trans 'Welcome to use Jumpserver system' %}</span>#}
</li> {# </li>#}
<li class="dropdown"> <li class="dropdown">
<a class="dropdown-toggle count-info" data-toggle="dropdown" href="#"> <a class="dropdown-toggle count-info" data-toggle="dropdown" href="#">
<span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span> <span class="m-r-sm text-muted welcome-message">{% trans 'Help' %}</span>
...@@ -22,11 +22,10 @@ ...@@ -22,11 +22,10 @@
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<a data-toggle="dropdown" class="dropdown-toggle" href="#"> <a data-toggle="dropdown" class="dropdown-toggle" href="#">
<span class="m-r-sm text-muted welcome-message"> <span class="m-r-sm text-muted welcome-message">
<img alt="image" class="img-circle" width="40" height="40" src="{{ request.user.avatar_url }}"/> <img alt="image" class="img-circle" width="30" height="30" src="{{ request.user.avatar_url }}"/>
<strong class="font-bold"> {{ request.user.name }} <span style="font-size: 13px;font-weight: 400"> {{ request.user.name }}
<span style="color: #8095a8"></span>
<b class="caret"></b> <b class="caret"></b>
</strong> </span>
</span> </span>
</a> </a>
<ul class="dropdown-menu animated fadeInRight m-t-xs"> <ul class="dropdown-menu animated fadeInRight m-t-xs">
......
...@@ -2,11 +2,8 @@ ...@@ -2,11 +2,8 @@
{% load i18n %} {% load i18n %}
<li class="nav-header"> <li class="nav-header">
<div class="dropdown profile-element"> <div class="dropdown profile-element">
<div> <div href="http://www.jumpserver.org" target="_blank">
<img alt="image" height="40" src="/static/img/logo.png"/> <img alt="image" height="55" src="/static/img/logo-text.png" style="margin-left: 10px"/>
</div>
<div>
<a href="http://www.jumpserver.org" target="_blank">Jumpserver</a>
</div> </div>
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
Copyright Jumpserver.org Copyright Jumpserver.org
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<small>2014-2017</small> <small>2014-2018</small>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
{% for login in last_login_ten %} {% for login in last_login_ten %}
<div class="feed-element"> <div class="feed-element">
<a href="#" class="pull-left"> <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> </a>
<div class="media-body "> <div class="media-body ">
{% ifequal login.is_finished 0 %} {% ifequal login.is_finished 0 %}
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from collections import OrderedDict from collections import OrderedDict
import copy
import logging import logging
import os import os
import uuid import uuid
...@@ -21,7 +20,8 @@ from .serializers import TerminalSerializer, StatusSerializer, \ ...@@ -21,7 +20,8 @@ from .serializers import TerminalSerializer, StatusSerializer, \
SessionSerializer, TaskSerializer, ReplaySerializer SessionSerializer, TaskSerializer, ReplaySerializer
from .hands import IsSuperUserOrAppUser, IsAppUser, \ from .hands import IsSuperUserOrAppUser, IsAppUser, \
IsSuperUserOrAppUserOrUserReadonly IsSuperUserOrAppUserOrUserReadonly
from .backends import get_command_store, SessionCommandSerializer from .backends import get_command_store, get_multi_command_store, \
SessionCommandSerializer
logger = logging.getLogger(__file__) logger = logging.getLogger(__file__)
...@@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet): ...@@ -141,7 +141,9 @@ class StatusViewSet(viewsets.ModelViewSet):
session = serializer.save() session = serializer.save()
return session return session
else: 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) logger.error(msg)
return None return None
...@@ -195,6 +197,7 @@ class CommandViewSet(viewsets.ViewSet): ...@@ -195,6 +197,7 @@ class CommandViewSet(viewsets.ViewSet):
""" """
command_store = get_command_store() command_store = get_command_store()
multi_command_storage = get_multi_command_store()
serializer_class = SessionCommandSerializer serializer_class = SessionCommandSerializer
permission_classes = (IsSuperUserOrAppUser,) permission_classes = (IsSuperUserOrAppUser,)
...@@ -215,7 +218,7 @@ class CommandViewSet(viewsets.ViewSet): ...@@ -215,7 +218,7 @@ class CommandViewSet(viewsets.ViewSet):
return Response({"msg": msg}, status=401) return Response({"msg": msg}, status=401)
def list(self, request, *args, **kwargs): 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) serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
...@@ -258,3 +261,13 @@ class SessionReplayViewSet(viewsets.ViewSet): ...@@ -258,3 +261,13 @@ class SessionReplayViewSet(viewsets.ViewSet):
return redirect(url) return redirect(url)
else: else:
return HttpResponseNotFound() 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 ...@@ -2,9 +2,39 @@ from importlib import import_module
from django.conf import settings from django.conf import settings
from .command.serializers import SessionCommandSerializer from .command.serializers import SessionCommandSerializer
TYPE_ENGINE_MAPPING = {
'elasticsearch': 'terminal.backends.command.es',
}
def get_command_store(): def get_command_store():
command_engine = import_module(settings.COMMAND_STORAGE_BACKEND) params = settings.COMMAND_STORAGE
command_store = command_engine.CommandStore() engine_class = import_module(params['ENGINE'])
return command_store 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): ...@@ -19,3 +19,9 @@ class CommandBase(object):
input=None, session=None): input=None, session=None):
pass 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 ...@@ -8,7 +8,7 @@ from .base import CommandBase
class CommandStore(CommandBase): class CommandStore(CommandBase):
def __init__(self): def __init__(self, params):
from terminal.models import Command from terminal.models import Command
self.model = Command self.model = Command
...@@ -37,7 +37,9 @@ class CommandStore(CommandBase): ...@@ -37,7 +37,9 @@ class CommandStore(CommandBase):
)) ))
return self.model.objects.bulk_create(_commands) 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, user=None, asset=None, system_user=None,
input=None, session=None): input=None, session=None):
filter_kwargs = {} filter_kwargs = {}
...@@ -60,10 +62,28 @@ class CommandStore(CommandBase): ...@@ -60,10 +62,28 @@ class CommandStore(CommandBase):
if session: if session:
filter_kwargs['session'] = 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) 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): ...@@ -18,5 +18,26 @@ class AbstractSessionCommand(models.Model):
class Meta: class Meta:
abstract = True 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): def __str__(self):
return self.input 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 @@ ...@@ -2,15 +2,27 @@
# #
from django import forms from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import Terminal 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): class TerminalForm(forms.ModelForm):
command_storage = forms.ChoiceField(choices=get_all_command_storage(),
label=_("Command storage"))
class Meta: class Meta:
model = Terminal model = Terminal
fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment'] fields = ['name', 'remote_addr', 'ssh_port', 'http_port', 'comment', 'command_storage']
help_texts = { help_texts = {
'ssh_port': _("Coco ssh listen port"), 'ssh_port': _("Coco ssh listen port"),
'http_port': _("Coco http/ws listen port"), 'http_port': _("Coco http/ws listen port"),
......
...@@ -4,6 +4,7 @@ import uuid ...@@ -4,6 +4,7 @@ import uuid
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from users.models import User from users.models import User
from .backends.command.models import AbstractSessionCommand from .backends.command.models import AbstractSessionCommand
...@@ -15,6 +16,8 @@ class Terminal(models.Model): ...@@ -15,6 +16,8 @@ class Terminal(models.Model):
remote_addr = models.CharField(max_length=128, verbose_name=_('Remote Address')) remote_addr = models.CharField(max_length=128, verbose_name=_('Remote Address'))
ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222) ssh_port = models.IntegerField(verbose_name=_('SSH Port'), default=2222)
http_port = models.IntegerField(verbose_name=_('HTTP Port'), default=5000) 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) 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_accepted = models.BooleanField(default=False, verbose_name='Is Accepted')
is_deleted = models.BooleanField(default=False) is_deleted = models.BooleanField(default=False)
...@@ -33,6 +36,26 @@ class Terminal(models.Model): ...@@ -33,6 +36,26 @@ class Terminal(models.Model):
self.user.is_active = active self.user.is_active = active
self.user.save() 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): def create_app_user(self):
random = uuid.uuid4().hex[:6] random = uuid.uuid4().hex[:6]
user, access_key = User.create_app_user(name="{}-{}".format(self.name, random), comment=self.comment) user, access_key = User.create_app_user(name="{}-{}".format(self.name, random), comment=self.comment)
......
...@@ -5,7 +5,7 @@ from django.utils import timezone ...@@ -5,7 +5,7 @@ from django.utils import timezone
from rest_framework import serializers from rest_framework import serializers
from .models import Terminal, Status, Session, Task from .models import Terminal, Status, Session, Task
from .backends import get_command_store from .backends import get_multi_command_store
class TerminalSerializer(serializers.ModelSerializer): class TerminalSerializer(serializers.ModelSerializer):
...@@ -43,14 +43,14 @@ class TerminalSerializer(serializers.ModelSerializer): ...@@ -43,14 +43,14 @@ class TerminalSerializer(serializers.ModelSerializer):
class SessionSerializer(serializers.ModelSerializer): class SessionSerializer(serializers.ModelSerializer):
command_amount = serializers.SerializerMethodField() command_amount = serializers.SerializerMethodField()
command_store = get_command_store() command_store = get_multi_command_store()
class Meta: class Meta:
model = Session model = Session
fields = '__all__' fields = '__all__'
def get_command_amount(self, obj): 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): class StatusSerializer(serializers.ModelSerializer):
......
...@@ -13,10 +13,6 @@ RUNNING = False ...@@ -13,10 +13,6 @@ RUNNING = False
logger = get_logger(__file__) 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(): def set_session_info_cache():
logger.debug("") logger.debug("")
from .utils import get_session_asset_list, get_session_user_list, \ from .utils import get_session_asset_list, get_session_user_list, \
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
{% bootstrap_field form.remote_addr layout="horizontal" %} {% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.ssh_port layout="horizontal" %} {% bootstrap_field form.ssh_port layout="horizontal" %}
{% bootstrap_field form.http_port layout="horizontal" %} {% bootstrap_field form.http_port layout="horizontal" %}
{% bootstrap_field form.command_storage layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %} {% bootstrap_field form.comment layout="horizontal" %}
</form> </form>
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
{% bootstrap_field form.remote_addr layout="horizontal" %} {% bootstrap_field form.remote_addr layout="horizontal" %}
{% bootstrap_field form.ssh_port layout="horizontal" %} {% bootstrap_field form.ssh_port layout="horizontal" %}
{% bootstrap_field form.http_port layout="horizontal" %} {% bootstrap_field form.http_port layout="horizontal" %}
{% bootstrap_field form.command_storage layout="horizontal" %}
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3> <h3>{% trans 'Other' %}</h3>
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from django import template from django import template
from ..backends import get_command_store from ..backends import get_multi_command_store
register = template.Library() register = template.Library()
command_store = get_command_store() command_store = get_multi_command_store()
@register.filter @register.filter
def get_session_command_amount(session_id): 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 = [ ...@@ -20,7 +20,8 @@ urlpatterns = [
url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$', url(r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-]{36})/replay/$',
api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}), api.SessionReplayViewSet.as_view({'get': 'retrieve', 'post': 'create'}),
name='session-replay'), 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 urlpatterns += router.urls
...@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _ ...@@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from ..models import Command from ..models import Command
from .. import utils from .. import utils
from ..backends import get_command_store from ..backends import get_multi_command_store
__all__ = ['CommandListView'] __all__ = ['CommandListView']
command_store = get_command_store() common_storage = get_multi_command_store()
class CommandListView(DatetimeSearchMixin, ListView): class CommandListView(DatetimeSearchMixin, ListView):
...@@ -39,7 +39,7 @@ class CommandListView(DatetimeSearchMixin, ListView): ...@@ -39,7 +39,7 @@ class CommandListView(DatetimeSearchMixin, ListView):
filter_kwargs['system_user'] = self.system_user filter_kwargs['system_user'] = self.system_user
if self.command: if self.command:
filter_kwargs['input'] = self.command filter_kwargs['input'] = self.command
queryset = command_store.filter(**filter_kwargs) queryset = common_storage.filter(**filter_kwargs)
return queryset return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
......
...@@ -10,7 +10,7 @@ from django.conf import settings ...@@ -10,7 +10,7 @@ from django.conf import settings
from users.utils import AdminUserRequiredMixin from users.utils import AdminUserRequiredMixin
from common.mixins import DatetimeSearchMixin from common.mixins import DatetimeSearchMixin
from ..models import Session, Command, Terminal from ..models import Session, Command, Terminal
from ..backends import get_command_store from ..backends import get_multi_command_store
from .. import utils from .. import utils
...@@ -19,7 +19,7 @@ __all__ = [ ...@@ -19,7 +19,7 @@ __all__ = [
'SessionDetailView', 'SessionDetailView',
] ]
command_store = get_command_store() command_store = get_multi_command_store()
class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView):
......
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
from rest_framework import generics 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.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import BulkModelViewSet
from .serializers import UserSerializer, UserGroupSerializer, \ from .serializers import UserSerializer, UserGroupSerializer, \
UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \ UserGroupUpdateMemeberSerializer, UserPKUpdateSerializer, \
UserUpdateGroupSerializer UserUpdateGroupSerializer, ChangeUserPasswordSerializer
from .tasks import write_login_log_async from .tasks import write_login_log_async
from .models import User, UserGroup from .models import User, UserGroup
from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly from .permissions import IsSuperUser, IsValidUser, IsCurrentUserOrReadOnly
...@@ -24,10 +24,21 @@ class UserViewSet(CustomFilterMixin, BulkModelViewSet): ...@@ -24,10 +24,21 @@ class UserViewSet(CustomFilterMixin, BulkModelViewSet):
queryset = User.objects.exclude(role="App") queryset = User.objects.exclude(role="App")
# queryset = User.objects.all().exclude(role="App").order_by("date_joined") # queryset = User.objects.all().exclude(role="App").order_by("date_joined")
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsSuperUser,) permission_classes = (IsSuperUser, IsAuthenticated)
filter_fields = ('username', 'email', 'name', 'id') 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): class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserUpdateGroupSerializer serializer_class = UserUpdateGroupSerializer
...@@ -37,6 +48,7 @@ class UserUpdateGroupApi(generics.RetrieveUpdateAPIView): ...@@ -37,6 +48,7 @@ class UserUpdateGroupApi(generics.RetrieveUpdateAPIView):
class UserResetPasswordApi(generics.UpdateAPIView): class UserResetPasswordApi(generics.UpdateAPIView):
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = (IsAuthenticated,)
def perform_update(self, serializer): def perform_update(self, serializer):
# Note: we are not updating the user object here. # Note: we are not updating the user object here.
...@@ -128,7 +140,11 @@ class UserAuthApi(APIView): ...@@ -128,7 +140,11 @@ class UserAuthApi(APIView):
user_agent = request.data.get('HTTP_USER_AGENT', '') user_agent = request.data.get('HTTP_USER_AGENT', '')
if not login_ip: 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( user, msg = check_user_valid(
username=username, password=password, username=username, password=password,
......
...@@ -172,7 +172,6 @@ class UserBulkUpdateForm(forms.ModelForm): ...@@ -172,7 +172,6 @@ class UserBulkUpdateForm(forms.ModelForm):
if self.data.get(field) is not None: if self.data.get(field) is not None:
changed_fields.append(field) changed_fields.append(field)
print(changed_fields)
cleaned_data = {k: v for k, v in self.cleaned_data.items() cleaned_data = {k: v for k, v in self.cleaned_data.items()
if k in changed_fields} if k in changed_fields}
users = cleaned_data.pop('users', '') users = cleaned_data.pop('users', '')
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
Other module of this app shouldn't connect with other app. 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. :license: GPL v2, see LICENSE for more details.
""" """
......
...@@ -71,3 +71,9 @@ class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer): ...@@ -71,3 +71,9 @@ class UserGroupUpdateMemeberSerializer(serializers.ModelSerializer):
model = UserGroup model = UserGroup
fields = ['id', 'users'] fields = ['id', 'users']
class ChangeUserPasswordSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['password']
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
<form method="post" class="form-horizontal" action="" enctype="multipart/form-data"> <form method="post" class="form-horizontal" action="" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<h3>{% trans 'Account' %}</h3> <h3>{% trans 'Account' %}</h3>
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.name layout="horizontal" %} {% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.email layout="horizontal" %} {% bootstrap_field form.email layout="horizontal" %}
{% bootstrap_field form.groups layout="horizontal" %} {% bootstrap_field form.groups layout="horizontal" %}
...@@ -32,12 +32,6 @@ ...@@ -32,12 +32,6 @@
<span class="help-block ">{{ form.date_expired.errors }}</span> <span class="help-block ">{{ form.date_expired.errors }}</span>
</div> </div>
</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> <div class="hr-line-dashed"></div>
<h3>{% trans 'Profile' %}</h3> <h3>{% trans 'Profile' %}</h3>
{% bootstrap_field form.phone layout="horizontal" %} {% bootstrap_field form.phone layout="horizontal" %}
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
Copyright Jumpserver.org Copyright Jumpserver.org
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<small>© 2014-2017</small> <small>© 2014-2018</small>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -78,7 +78,7 @@ ...@@ -78,7 +78,7 @@
Copyright Jumpserver.org Copyright Jumpserver.org
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<small>© 2014-2017</small> <small>© 2014-2018</small>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
Copyright Jumpserver.org Copyright Jumpserver.org
</div> </div>
<div class="col-md-6 text-right"> <div class="col-md-6 text-right">
<small>© 2014-2017</small> <small>© 2014-2018</small>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -24,8 +24,9 @@ ...@@ -24,8 +24,9 @@
<li class="pull-right"> <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> <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>
<li class="pull-right"> <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' %} <i class="fa fa-trash-o"></i>{% trans 'Delete' %}
</a> </a>
</li> </li>
...@@ -128,7 +129,7 @@ ...@@ -128,7 +129,7 @@
<td><span class="pull-right"> <td><span class="pull-right">
<div class="switch"> <div class="switch">
<div class="onoffswitch"> <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"> <label class="onoffswitch-label" for="is_active">
<span class="onoffswitch-inner"></span> <span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span> <span class="onoffswitch-switch"></span>
...@@ -156,7 +157,7 @@ ...@@ -156,7 +157,7 @@
<td>{% trans 'Send reset password mail' %}:</td> <td>{% trans 'Send reset password mail' %}:</td>
<td> <td>
<span class="pull-right"> <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> </span>
</td> </td>
</tr> </tr>
...@@ -164,7 +165,7 @@ ...@@ -164,7 +165,7 @@
<td>{% trans 'Send reset ssh key mail' %}:</td> <td>{% trans 'Send reset ssh key mail' %}:</td>
<td> <td>
<span class="pull-right"> <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> </span>
</td> </td>
</tr> </tr>
...@@ -331,7 +332,7 @@ $(document).ready(function() { ...@@ -331,7 +332,7 @@ $(document).ready(function() {
} }
swal({ swal({
title: "{% trans 'Are you sure?' %}", 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", type: "warning",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
...@@ -356,7 +357,7 @@ $(document).ready(function() { ...@@ -356,7 +357,7 @@ $(document).ready(function() {
} }
swal({ swal({
title: "{% trans 'Are you sure?' %}", 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", type: "warning",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#DD6B55", confirmButtonColor: "#DD6B55",
......
...@@ -76,7 +76,7 @@ function initTable() { ...@@ -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 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 = ""; 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>' del_btn = '<a class="btn btn-xs btn-danger m-l-xs" disabled>{% trans "Delete" %}</a>'
.replace('{{ DEFAULT_PK }}', cellData) .replace('{{ DEFAULT_PK }}', cellData)
.replace('99991938', rowData.name); .replace('99991938', rowData.name);
......
...@@ -19,6 +19,8 @@ urlpatterns = [ ...@@ -19,6 +19,8 @@ urlpatterns = [
url(r'^v1/token/$', api.UserToken.as_view(), name='user-token'), 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/profile/$', api.UserProfile.as_view(), name='user-profile'),
url(r'^v1/auth/$', api.UserAuthApi.as_view(), name='user-auth'), 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/$', url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/password/reset/$',
api.UserResetPasswordApi.as_view(), name='user-reset-password'), api.UserResetPasswordApi.as_view(), name='user-reset-password'),
url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/reset/$', url(r'^v1/users/(?P<pk>[0-9a-zA-Z\-]{36})/pubkey/reset/$',
......
...@@ -180,7 +180,9 @@ def validate_ip(ip): ...@@ -180,7 +180,9 @@ def validate_ip(ip):
def write_login_log(username, type='', ip='', user_agent=''): def write_login_log(username, type='', ip='', user_agent=''):
if not (ip and validate_ip(ip)): if not (ip and validate_ip(ip)):
ip = '0.0.0.0' ip = ip[:15]
city = "Unknown"
else:
city = get_ip_city(ip) city = get_ip_city(ip)
LoginLog.objects.create( LoginLog.objects.create(
username=username, type=type, username=username, type=type,
......
...@@ -53,8 +53,11 @@ class UserLoginView(FormView): ...@@ -53,8 +53,11 @@ class UserLoginView(FormView):
if not self.request.session.test_cookie_worked(): if not self.request.session.test_cookie_worked():
return HttpResponse(_("Please enable cookies and try again.")) return HttpResponse(_("Please enable cookies and try again."))
auth_login(self.request, form.get_user()) auth_login(self.request, form.get_user())
login_ip = self.request.META.get('HTTP_X_FORWARDED_FOR') or \ x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR', '').split(',')
self.request.META.get('REMOTE_ADDR', '') 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', '') user_agent = self.request.META.get('HTTP_USER_AGENT', '')
write_login_log_async.delay( write_login_log_async.delay(
self.request.user.username, type='W', self.request.user.username, type='W',
......
## Jumpserver v0.4.0 版本安装详细过程 More see [安装文档](https://github.com/jumpserver/jumpserver/wiki/v0.5.0-%E5%9F%BA%E4%BA%8E-CentOS7)
### 环境
- 系统: 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
...@@ -56,6 +56,8 @@ uritemplate==3.0.0 ...@@ -56,6 +56,8 @@ uritemplate==3.0.0
urllib3==1.22 urllib3==1.22
vine==1.1.4 vine==1.1.4
gunicorn==19.7.1 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 ephem==3.7.6.0
python-gssapi==0.6.4 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