Dev remoteapp (#3205)

* [Update] 修改RemoteApp关联的系统用户:从RemoteApp中转移到RemoteAppPermission中(未提交迁移文件)

* [Update] 修改RemoteApp关联的系统用户:提交迁移文件

* [Update] 修改RemoteApp关联的系统用户:修改迁移文件

* [Update] 修改迁移文件1

* [Update] 修改迁移文件2

* [Update] 修改迁移文件3

* [Update] 修改RemoteAppPermsUtil获取系统用户的逻辑
pull/3224/head
BaiJiangJie 2019-09-12 18:25:22 +08:00 committed by 老广
parent 3a8fad7c7d
commit bdcf9ba153
22 changed files with 250 additions and 88 deletions

View File

@ -89,23 +89,16 @@ class RemoteAppCreateUpdateForm(RemoteAppTypeForms, OrgModelForm):
super().__init__(*args, **kwargs)
field_asset = self.fields['asset']
field_asset.queryset = field_asset.queryset.has_protocol('rdp')
field_system_user = self.fields['system_user']
field_system_user.queryset = field_system_user.queryset.filter(
protocol=SystemUser.PROTOCOL_RDP
)
class Meta:
model = RemoteApp
fields = [
'name', 'asset', 'system_user', 'type', 'path', 'comment'
'name', 'asset', 'type', 'path', 'comment'
]
widgets = {
'asset': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('Asset')
}),
'system_user': forms.Select(attrs={
'class': 'select2', 'data-placeholder': _('System user')
})
}
def _clean_params(self):

View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.7 on 2019-09-09 09:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('applications', '0001_initial'),
('perms', '0008_remoteapppermission_system_users'),
]
operations = [
migrations.RemoveField(
model_name='remoteapp',
name='system_user',
),
]

View File

@ -22,10 +22,6 @@ class RemoteApp(OrgModelMixin):
asset = models.ForeignKey(
'assets.Asset', on_delete=models.CASCADE, verbose_name=_('Asset')
)
system_user = models.ForeignKey(
'assets.SystemUser', on_delete=models.CASCADE,
verbose_name=_('System user')
)
type = models.CharField(
default=const.REMOTE_APP_TYPE_CHROME,
choices=const.REMOTE_APP_TYPE_CHOICES,
@ -80,10 +76,3 @@ class RemoteApp(OrgModelMixin):
'id': self.asset.id,
'hostname': self.asset.hostname
}
@property
def system_user_info(self):
return {
'id': self.system_user.id,
'name': self.system_user.name
}

View File

@ -73,13 +73,13 @@ class RemoteAppSerializer(BulkOrgResourceModelSerializer):
model = RemoteApp
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'asset', 'system_user', 'type', 'path', 'params',
'id', 'name', 'asset', 'type', 'path', 'params',
'comment', 'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display',
'get_type_display',
]
read_only_fields = [
'created_by', 'date_created', 'asset_info',
'system_user_info', 'get_type_display'
'get_type_display'
]
@ -89,7 +89,7 @@ class RemoteAppConnectionInfoSerializer(serializers.ModelSerializer):
class Meta:
model = RemoteApp
fields = [
'id', 'name', 'asset', 'system_user', 'parameter_remote_app',
'id', 'name', 'asset', 'parameter_remote_app',
]
read_only_fields = ['parameter_remote_app']

View File

@ -13,7 +13,6 @@
{% csrf_token %}
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.asset layout="horizontal" %}
{% bootstrap_field form.system_user layout="horizontal" %}
{% bootstrap_field form.type layout="horizontal" %}
{% bootstrap_field form.path layout="horizontal" %}

View File

@ -57,10 +57,6 @@
<td>{% trans 'Asset' %}:</td>
<td><b><a href="{% url 'assets:asset-detail' pk=remote_app.asset.id %}">{{ remote_app.asset.hostname }}</a></b></td>
</tr>
<tr>
<td>{% trans 'System user' %}:</td>
<td><b><a href="{% url 'assets:system-user-detail' pk=remote_app.system_user.id %}">{{ remote_app.system_user.name }}</a></b></td>
</tr>
<tr>
<td>{% trans 'App type' %}:</td>
<td><b>{{ remote_app.get_type_display }}</b></td>

View File

@ -20,7 +20,6 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@ -47,12 +46,11 @@ function initTable() {
var detail_btn = '<a href="{% url 'assets:asset-detail' pk=DEFAULT_PK %}">' + hostname + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
var detail_btn = '<a href="{% url 'assets:system-user-detail' pk=DEFAULT_PK %}">' + name + '</a>';
$(td).html(detail_btn.replace('{{ DEFAULT_PK }}', cellData.id));
{targets: 3, createdCell: function (td, cellData, rowData) {
var comment = htmlEscape(cellData);
$(td).html(comment)
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{targets: 5, createdCell: function (td, cellData, rowData) {
var update_btn = '<a href="{% url "applications:remote-app-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-delete" data-rid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'.replace('{{ DEFAULT_PK }}', cellData);
$(td).html(update_btn + del_btn)
@ -64,7 +62,6 @@ function initTable() {
{data: "name" },
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment"},
{data: "id", orderable: false}
],

View File

@ -16,7 +16,6 @@
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'App type' %}</th>
<th class="text-center">{% trans 'Asset' %}</th>
<th class="text-center">{% trans 'System user' %}</th>
<th class="text-center">{% trans 'Comment' %}</th>
<th class="text-center">{% trans 'Action' %}</th>
</tr>
@ -49,11 +48,7 @@ function initTable() {
var hostname = htmlEscape(cellData.hostname);
$(td).html(hostname);
}},
{targets: 4, createdCell: function (td, cellData, rowData) {
var name = htmlEscape(cellData.name);
$(td).html(name);
}},
{targets: 6, createdCell: function (td, cellData, rowData) {
{targets: 5, createdCell: function (td, cellData, rowData) {
var conn_btn = '<a href="{% url "luna-view" %}?login_to=' + cellData +'" class="btn btn-xs btn-primary">{% trans "Connect" %}</a>'.replace("{{ DEFAULT_PK }}", cellData);
$(td).html(conn_btn)
}}
@ -64,7 +59,6 @@ function initTable() {
{data: "name"},
{data: "get_type_display", orderable: false},
{data: "asset_info", orderable: false},
{data: "system_user_info", orderable: false},
{data: "comment", orderable: false},
{data: "id", orderable: false}
]

View File

@ -13,9 +13,7 @@ router = BulkRouter()
router.register(r'remote-apps', api.RemoteAppViewSet, 'remote-app')
urlpatterns = [
path('remote-apps/<uuid:pk>/connection-info/',
api.RemoteAppConnectionInfoApi.as_view(),
name='remote-app-connection-info')
path('remote-apps/<uuid:pk>/connection-info/', api.RemoteAppConnectionInfoApi.as_view(), name='remote-app-connection-info'),
]
old_version_urlpatterns = [
re_path('(?P<resource>remote-app)/.*', capi.redirect_plural_name_api)

View File

@ -9,16 +9,49 @@ from rest_framework.views import Response
from django.utils.decorators import method_decorator
from django.views.decorators.http import condition
from rest_framework.generics import get_object_or_404
from django.utils.translation import ugettext as _
from common.utils import get_logger
from assets.utils import LabelFilterMixin
from .. import const
from ..hands import Asset, Node, SystemUser
from common.permissions import IsValidUser, IsOrgAdminOrAppUser, IsOrgAdmin
from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..hands import User, Asset, Node, SystemUser
from .. import serializers
from .. import const
logger = get_logger(__name__)
__all__ = ['UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin']
__all__ = [
'UserPermissionCacheMixin', 'GrantAssetsMixin', 'NodesWithUngroupMixin',
'UserPermissionMixin',
]
class UserPermissionMixin:
permission_classes = (IsOrgAdminOrAppUser,)
obj = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
self.obj = self.get_obj()
def get(self, request, *args, **kwargs):
set_to_root_org()
return super().get(request, *args, **kwargs)
def get_obj(self):
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
return user
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
# def get_etag(request, *args, **kwargs):

View File

@ -8,16 +8,16 @@ from rest_framework.generics import (
ListAPIView, get_object_or_404, RetrieveAPIView
)
from common.permissions import IsValidUser, IsOrgAdminOrAppUser, IsOrgAdmin
from common.permissions import IsOrgAdminOrAppUser, IsOrgAdmin
from common.tree import TreeNodeSerializer
from common.utils import get_logger
from orgs.utils import set_to_root_org
from ..utils import (
ParserNode, AssetPermissionUtilV2
)
from ..hands import User, Asset, Node, SystemUser, NodeSerializer
from .. import serializers
from ..models import Action
from .mixin import UserPermissionMixin
logger = get_logger(__name__)
@ -39,32 +39,6 @@ __all__ = [
]
class UserPermissionMixin:
permission_classes = (IsOrgAdminOrAppUser,)
obj = None
def initial(self, *args, **kwargs):
super().initial(*args, *kwargs)
self.obj = self.get_obj()
def get(self, request, *args, **kwargs):
set_to_root_org()
return super().get(request, *args, **kwargs)
def get_obj(self):
user_id = self.kwargs.get('pk', '')
if user_id:
user = get_object_or_404(User, id=user_id)
else:
user = self.request.user
return user
def get_permissions(self):
if self.kwargs.get('pk') is None:
self.permission_classes = (IsValidUser,)
return super().get_permissions()
class UserAssetPermissionMixin(UserPermissionMixin):
util = None

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import uuid
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView, Response
from rest_framework.generics import (
@ -12,13 +13,16 @@ from ..utils import (
RemoteAppPermissionUtil, construct_remote_apps_tree_root,
parse_remote_app_to_tree_node,
)
from ..hands import User, RemoteAppSerializer, UserGroup
from ..hands import User, RemoteApp, RemoteAppSerializer, UserGroup, SystemUser
from ..mixins import RemoteAppFilterMixin
from .mixin import UserPermissionMixin
from .. import serializers
__all__ = [
'UserGrantedRemoteAppsApi', 'ValidateUserRemoteAppPermissionApi',
'UserGrantedRemoteAppsAsTreeApi', 'UserGroupGrantedRemoteAppsApi',
'UserGrantedRemoteAppSystemUsersApi',
]
@ -65,18 +69,43 @@ class UserGrantedRemoteAppsAsTreeApi(UserGrantedRemoteAppsApi):
return super().get_serializer(data, many=True)
class UserGrantedRemoteAppSystemUsersApi(UserPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.RemoteAppSystemUserSerializer
only_fields = serializers.RemoteAppSystemUserSerializer.Meta.only_fields
def get_queryset(self):
util = RemoteAppPermissionUtil(self.obj)
remote_app_id = self.kwargs.get('remote_app_id')
remote_app = get_object_or_404(RemoteApp, id=remote_app_id)
system_users = util.get_remote_app_system_users(remote_app)
return system_users
class ValidateUserRemoteAppPermissionApi(APIView):
permission_classes = (IsOrgAdminOrAppUser,)
def get(self, request, *args, **kwargs):
user_id = request.query_params.get('user_id', '')
remote_app_id = request.query_params.get('remote_app_id', '')
system_id = request.query_params.get('system_user_id', '')
try:
user_id = uuid.UUID(user_id)
remote_app_id = uuid.UUID(remote_app_id)
system_id = uuid.UUID(system_id)
except ValueError:
return Response({'msg': False}, status=403)
user = get_object_or_404(User, id=user_id)
remote_app = get_object_or_404(RemoteApp, id=remote_app_id)
system_user = get_object_or_404(SystemUser, id=system_id)
util = RemoteAppPermissionUtil(user)
remote_app = util.get_remote_apps().filter(id=remote_app_id).exists()
if remote_app:
system_users = util.get_remote_app_system_users(remote_app)
if system_user in system_users:
return Response({'msg': True}, status=200)
return Response({'msg': False}, status=403)

View File

@ -35,6 +35,9 @@ class RemoteAppPermissionCreateUpdateForm(OrgModelForm):
),
'remote_apps': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('RemoteApp')}
),
'system_users': forms.SelectMultiple(
attrs={'class': 'select2', 'data-placeholder': _('System user')}
)
}

View File

@ -0,0 +1,32 @@
# Generated by Django 2.1.7 on 2019-09-09 09:09
from django.db import migrations, models
from assets.models import SystemUser
def migrate_system_user_from_remote_app_to_remote_app_perms(apps, schema_editor):
remote_app_perms_model = apps.get_model("perms", "RemoteAppPermission")
db_alias = schema_editor.connection.alias
perms = remote_app_perms_model.objects.using(db_alias).all()
for perm in perms:
system_users_ids = perm.remote_apps.values_list('system_user', flat=True)
perm.system_users.set(system_users_ids)
class Migration(migrations.Migration):
dependencies = [
('assets', '0037_auto_20190724_2002'),
('perms', '0007_remove_assetpermission_actions'),
]
operations = [
migrations.AddField(
model_name='remoteapppermission',
name='system_users',
field=models.ManyToManyField(related_name='granted_by_remote_app_permissions', to='assets.SystemUser', verbose_name='System user'),
),
migrations.RunPython(
code=migrate_system_user_from_remote_app_to_remote_app_perms,
),
]

View File

@ -13,6 +13,7 @@ __all__ = [
class RemoteAppPermission(BasePermission):
remote_apps = models.ManyToManyField('applications.RemoteApp', related_name='granted_by_permissions', blank=True, verbose_name=_("RemoteApp"))
system_users = models.ManyToManyField('assets.SystemUser', related_name='granted_by_remote_app_permissions', verbose_name=_("System user"))
class Meta:
unique_together = [('org_id', 'name')]

View File

@ -20,8 +20,8 @@ class RemoteAppPermissionSerializer(BulkOrgResourceModelSerializer):
model = RemoteAppPermission
list_serializer_class = AdaptedBulkListSerializer
fields = [
'id', 'name', 'users', 'user_groups', 'remote_apps', 'comment',
'is_active', 'date_start', 'date_expired', 'is_valid',
'id', 'name', 'users', 'user_groups', 'remote_apps', 'system_users',
'comment', 'is_active', 'date_start', 'date_expired', 'is_valid',
'created_by', 'date_created',
]
read_only_fields = ['created_by', 'date_created']

View File

@ -12,6 +12,7 @@ __all__ = [
'NodeGrantedSerializer',
'AssetGrantedSerializer',
'ActionsSerializer', 'AssetSystemUserSerializer',
'RemoteAppSystemUserSerializer',
]
@ -24,13 +25,22 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority',
'protocol', 'login_mode',
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
)
fields = list(only_fields) + ["actions"]
read_only_fields = fields
class RemoteAppSystemUserSerializer(serializers.ModelSerializer):
class Meta:
model = SystemUser
only_fields = (
'id', 'name', 'username', 'priority', 'protocol', 'login_mode',
)
fields = list(only_fields)
read_only_fields = fields
class AssetGrantedSerializer(serializers.ModelSerializer):
"""
被授权资产的数据结构

View File

@ -47,6 +47,7 @@
<h3>{% trans 'RemoteApp' %}</h3>
{% bootstrap_field form.remote_apps layout="horizontal" %}
{% bootstrap_field form.system_users layout="horizontal" %}
<div class="hr-line-dashed"></div>
<h3>{% trans 'Other' %}</h3>
@ -127,7 +128,7 @@ $(document).ready(function () {
the_url = '{% url "api-perms:remote-app-permission-detail" pk=object.id %}';
method = "PUT";
{% endif %}
objectAttrsIsList(data, ['users', 'user_groups', 'remote_apps']);
objectAttrsIsList(data, ['users', 'user_groups', 'remote_apps', 'system_users']);
objectAttrsIsDatetime(data, ['date_expired', 'date_start']);
objectAttrsIsBool(data, ['is_active']);
var props = {

View File

@ -126,7 +126,42 @@
</table>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading">
<i class="fa fa-info-circle"></i> {% trans 'System user' %}
</div>
<div class="panel-body">
<table class="table" id="system-user-table">
<tbody>
<form>
<tr class="no-borders-tr">
<td colspan="2">
<select data-placeholder="{% trans 'Select system users' %}" class="select2" style="width: 100%" multiple="" tabindex="4">
{% for system_user in system_users_remain %}
<option value="{{ system_user.id }}" id="opt_{{ system_user.id }}">{{ system_user }}</option>
{% endfor %}
</select>
</td>
</tr>
<tr class="no-borders-tr">
<td colspan="2">
<button type="button" class="btn btn-info btn-small" id="btn-add-system-user">{% trans 'Add' %}</button>
</td>
</tr>
</form>
{% for system_user in object.system_users.all %}
<tr {% if forloop.counter == 1 %} class="no-borders-tr" {% endif %} >
<td ><b class="bdg-system-user" data-uid={{ system_user.id }}>{{ system_user }}</b></td>
<td>
<button class="btn btn-danger btn-xs btn-remove-user" data-uid="{{ system_user.id }}" type="button" style="float: right;"><i class="fa fa-minus"></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
@ -136,6 +171,20 @@
{% endblock %}
{% block custom_foot_js %}
<script>
jumpserver.system_users_selected = {};
function updateSystemUser(system_users) {
var the_url = "{% url 'api-perms:remote-app-permission-detail' pk=object.id %}";
var body = {
system_users: Object.assign([], system_users)
};
requestApi({
url: the_url,
body: JSON.stringify(body),
success: function () {window.location.reload()}
});
}
$(document).ready(function () {
$('.select2').select2()
.on('select2:select', function(evt) {
@ -147,7 +196,36 @@ $(document).ready(function () {
delete jumpserver.system_users_selected[data.id]
})
})
.on('click', '.btn-delete', function () {
.on('click', '#btn-add-system-user', function () {
if (Object.keys(jumpserver.system_users_selected).length === 0) {
return false;
}
var system_users = $('.bdg-system-user').map(function() {
return $(this).data('uid');
}).get();
$.map(jumpserver.system_users_selected, function(name, index) {
system_users.push(index);
$('#opt_' + index).remove();
$('.group_edit tbody').append(
'<tr>' +
'<td><b class="bdg-system-user" data-gid="' + index + '">' + name + '</b></td>' +
'<td><button class="btn btn-danger btn-xs pull-right btn-remove-user" type="button"><i class="fa fa-minus"></i></button></td>' +
'</tr>'
)
});
updateSystemUser(system_users);
}).on('click', '.btn-remove-user', function () {
var $this = $(this);
var $tr = $this.closest('tr');
var system_users = $('.bdg-system-user').map(function() {
if ($(this).data('uid') !== $this.data('uid')){
return $(this).data('uid');
}
}).get();
updateSystemUser(system_users);
$tr.remove()
}).on('click', '.btn-delete', function () {
var $this = $(this);
var name = "{{ object.name }}";
var rid = "{{ object.id }}";

View File

@ -91,6 +91,10 @@ remote_app_permission_urlpatterns = [
# 查询用户组授权的RemoteApp
path('user-groups/<uuid:pk>/remote-apps/', api.UserGroupGrantedRemoteAppsApi.as_view(), name='user-group-remote-apps'),
# RemoteApp System users
path('users/<uuid:pk>/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='user-remote-app-system-users'),
path('users/remote-apps/<uuid:remote_app_id>/system-users/', api.UserGrantedRemoteAppSystemUsersApi.as_view(), name='my-remote-app-system-users'),
# 校验用户对RemoteApp的权限
path('remote-app-permissions/user/validate/', api.ValidateUserRemoteAppPermissionApi.as_view(), name='validate-user-remote-app-permission'),

View File

@ -7,7 +7,7 @@ from common.tree import TreeNode
from orgs.utils import set_to_root_org
from ..models import RemoteAppPermission
from ..hands import RemoteApp
from ..hands import RemoteApp, SystemUser
__all__ = [
@ -59,6 +59,16 @@ class RemoteAppPermissionUtil:
)
return remote_apps
def get_remote_app_system_users(self, remote_app):
queryset = self.permissions
kwargs = {"remote_apps": remote_app}
queryset = queryset.filter(**kwargs)
system_users_ids = queryset.values_list('system_users', flat=True)
system_users_ids = system_users_ids.distinct()
system_users = SystemUser.objects.filter(id__in=system_users_ids)
system_users = system_users.order_by('-priority')
return system_users
def construct_remote_apps_tree_root():
tree_root = {

View File

@ -12,7 +12,7 @@ from django.conf import settings
from common.permissions import PermissionsMixin, IsOrgAdmin
from orgs.utils import current_org
from ..hands import RemoteApp, UserGroup
from ..hands import RemoteApp, UserGroup, SystemUser
from ..models import RemoteAppPermission
from ..forms import RemoteAppPermissionCreateUpdateForm
@ -80,6 +80,9 @@ class RemoteAppPermissionDetailView(PermissionsMixin, DetailView):
context = {
'app': _('Perms'),
'action': _('RemoteApp permission detail'),
'system_users_remain': SystemUser.objects.exclude(
granted_by_remote_app_permissions=self.object
),
}
kwargs.update(context)
return super().get_context_data(**kwargs)