Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
21ffa8b2
Commit
21ffa8b2
authored
Jul 11, 2019
by
ibuler
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'dev' of github.com:jumpserver/jumpserver into dev
parents
d40d231e
5f9f970a
Show whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
1381 additions
and
1071 deletions
+1381
-1071
node.py
apps/assets/api/node.py
+1
-1
asset.py
apps/assets/models/asset.py
+49
-16
node.py
apps/assets/models/node.py
+1
-3
user.py
apps/assets/models/user.py
+0
-10
admin_user.py
apps/assets/serializers/admin_user.py
+1
-2
cmd_filter.py
apps/assets/serializers/cmd_filter.py
+9
-3
domain.py
apps/assets/serializers/domain.py
+5
-4
node.py
apps/assets/serializers/node.py
+2
-3
system_user.py
apps/assets/serializers/system_user.py
+1
-2
signals_handler.py
apps/assets/signals_handler.py
+1
-0
admin_user_list.html
apps/assets/templates/assets/admin_user_list.html
+42
-44
cmd_filter_create_update.html
apps/assets/templates/assets/cmd_filter_create_update.html
+27
-0
gateway_create_update.html
apps/assets/templates/assets/gateway_create_update.html
+26
-0
system_user_list.html
apps/assets/templates/assets/system_user_list.html
+42
-42
user_asset_list.html
apps/assets/templates/assets/user_asset_list.html
+2
-11
utils.py
apps/assets/utils.py
+54
-14
cmd_filter.py
apps/assets/views/cmd_filter.py
+2
-0
domain.py
apps/assets/views/domain.py
+2
-0
api.py
apps/common/api.py
+10
-0
http.py
apps/common/http.py
+12
-0
tree.py
apps/common/tree.py
+11
-6
common.py
apps/common/utils/common.py
+16
-0
views.py
apps/jumpserver/views.py
+3
-10
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+122
-126
remote_app_permission.py
apps/perms/api/remote_app_permission.py
+1
-0
user_group_permission.py
apps/perms/api/user_group_permission.py
+24
-115
user_permission.py
apps/perms/api/user_permission.py
+262
-182
user_remote_app_permission.py
apps/perms/api/user_remote_app_permission.py
+19
-2
const.py
apps/perms/const.py
+1
-0
asset_permission.py
apps/perms/models/asset_permission.py
+6
-1
user_permission.py
apps/perms/serializers/user_permission.py
+31
-61
api_urls.py
apps/perms/urls/api_urls.py
+42
-27
asset_permission.py
apps/perms/utils/asset_permission.py
+218
-175
stack.py
apps/perms/utils/stack.py
+137
-0
user.py
apps/users/models/user.py
+198
-200
user_granted_asset.html
apps/users/templates/users/user_granted_asset.html
+1
-11
No files found.
apps/assets/api/node.py
View file @
21ffa8b2
...
...
@@ -131,7 +131,7 @@ class NodeChildrenAsTreeApi(generics.ListAPIView):
if
not
include_assets
:
return
queryset
assets
=
self
.
node
.
get_assets
()
.
only
(
"id"
,
"hostname"
,
"ip"
,
'platform'
,
"os"
,
"org_id"
,
"id"
,
"hostname"
,
"ip"
,
'platform'
,
"os"
,
"org_id"
,
"protocols"
,
)
for
asset
in
assets
:
queryset
.
append
(
asset
.
as_tree_node
(
self
.
node
))
...
...
apps/assets/models/asset.py
View file @
21ffa8b2
...
...
@@ -6,7 +6,8 @@ import uuid
import
logging
import
random
from
functools
import
reduce
from
collections
import
OrderedDict
from
collections
import
OrderedDict
,
defaultdict
from
django.core.cache
import
cache
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
...
...
@@ -96,7 +97,53 @@ class ProtocolsMixin:
return
self
.
protocols_as_dict
.
get
(
"ssh"
,
22
)
class
Asset
(
ProtocolsMixin
,
OrgModelMixin
):
class
NodesRelationMixin
:
NODES_CACHE_KEY
=
'ASSET_NODES_{}'
ALL_ASSET_NODES_CACHE_KEY
=
'ALL_ASSETS_NODES'
CACHE_TIME
=
3600
*
24
*
7
id
=
""
_all_nodes_keys
=
None
@classmethod
def
get_all_nodes_keys
(
cls
):
"""
:return: {asset.id: [node.key, ]}
"""
from
.node
import
Node
cache_key
=
cls
.
ALL_ASSET_NODES_CACHE_KEY
cached
=
cache
.
get
(
cache_key
)
if
cached
:
return
cached
assets
=
Asset
.
objects
.
all
()
.
only
(
'id'
)
.
prefetch_related
(
models
.
Prefetch
(
'nodes'
,
queryset
=
Node
.
objects
.
all
()
.
only
(
'key'
))
)
assets_nodes_keys
=
{}
for
asset
in
assets
:
assets_nodes_keys
[
asset
.
id
]
=
[
n
.
key
for
n
in
asset
.
nodes
.
all
()]
cache
.
set
(
cache_key
,
assets_nodes_keys
,
cls
.
CACHE_TIME
)
return
assets_nodes_keys
@classmethod
def
expire_all_nodes_keys_cache
(
cls
):
cache_key
=
cls
.
ALL_ASSET_NODES_CACHE_KEY
cache
.
delete
(
cache_key
)
def
get_nodes
(
self
):
from
.node
import
Node
nodes
=
self
.
nodes
.
all
()
or
[
Node
.
root
()]
return
nodes
def
get_all_nodes
(
self
,
flat
=
False
):
nodes
=
[]
for
node
in
self
.
get_nodes
():
_nodes
=
node
.
get_ancestor
(
with_self
=
True
)
nodes
.
append
(
_nodes
)
if
flat
:
nodes
=
list
(
reduce
(
lambda
x
,
y
:
set
(
x
)
|
set
(
y
),
nodes
))
return
nodes
class
Asset
(
ProtocolsMixin
,
NodesRelationMixin
,
OrgModelMixin
):
# Important
PLATFORM_CHOICES
=
(
(
'Linux'
,
'Linux'
),
...
...
@@ -182,20 +229,6 @@ class Asset(ProtocolsMixin, OrgModelMixin):
def
is_support_ansible
(
self
):
return
self
.
has_protocol
(
'ssh'
)
and
self
.
platform
not
in
(
"Other"
,)
def
get_nodes
(
self
):
from
.node
import
Node
nodes
=
self
.
nodes
.
all
()
or
[
Node
.
root
()]
return
nodes
def
get_all_nodes
(
self
,
flat
=
False
):
nodes
=
[]
for
node
in
self
.
get_nodes
():
_nodes
=
node
.
get_ancestor
(
with_self
=
True
)
nodes
.
append
(
_nodes
)
if
flat
:
nodes
=
list
(
reduce
(
lambda
x
,
y
:
set
(
x
)
|
set
(
y
),
nodes
))
return
nodes
@property
def
cpu_info
(
self
):
info
=
""
...
...
apps/assets/models/node.py
View file @
21ffa8b2
...
...
@@ -212,14 +212,12 @@ class AssetsAmountMixin:
if
cached
is
not
None
:
return
cached
assets_amount
=
self
.
get_all_assets
()
.
count
()
self
.
assets_amount
=
assets_amount
cache
.
set
(
cache_key
,
assets_amount
,
self
.
cache_time
)
return
assets_amount
@assets_amount.setter
def
assets_amount
(
self
,
value
):
self
.
_assets_amount
=
value
cache_key
=
self
.
_assets_amount_cache_key
.
format
(
self
.
key
)
cache
.
set
(
cache_key
,
value
,
self
.
cache_time
)
def
expire_assets_amount
(
self
):
ancestor_keys
=
self
.
get_ancestor_keys
(
with_self
=
True
)
...
...
apps/assets/models/user.py
View file @
21ffa8b2
...
...
@@ -117,16 +117,6 @@ class SystemUser(AssetUser):
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
def
to_json
(
self
):
return
{
'id'
:
self
.
id
,
'name'
:
self
.
name
,
'username'
:
self
.
username
,
'protocol'
:
self
.
protocol
,
'priority'
:
self
.
priority
,
'auto_push'
:
self
.
auto_push
,
}
@property
def
login_mode_display
(
self
):
return
self
.
get_login_mode_display
()
...
...
apps/assets/serializers/admin_user.py
View file @
21ffa8b2
...
...
@@ -21,7 +21,7 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
model
=
AdminUser
fields
=
[
'id'
,
'name'
,
'username'
,
'password'
,
'private_key'
,
'public_key'
,
'comment'
,
'
connectivity_amount'
,
'
assets_amount'
,
'comment'
,
'assets_amount'
,
'date_created'
,
'date_updated'
,
'created_by'
,
]
...
...
@@ -33,7 +33,6 @@ class AdminUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'date_updated'
:
{
'read_only'
:
True
},
'created_by'
:
{
'read_only'
:
True
},
'assets_amount'
:
{
'label'
:
_
(
'Asset'
)},
'connectivity_amount'
:
{
'label'
:
_
(
'Connectivity'
)},
}
...
...
apps/assets/serializers/cmd_filter.py
View file @
21ffa8b2
...
...
@@ -10,13 +10,19 @@ from orgs.mixins import BulkOrgResourceModelSerializer
class
CommandFilterSerializer
(
BulkOrgResourceModelSerializer
):
rules
=
serializers
.
PrimaryKeyRelatedField
(
queryset
=
CommandFilterRule
.
objects
.
all
(),
many
=
True
)
system_users
=
serializers
.
PrimaryKeyRelatedField
(
queryset
=
SystemUser
.
objects
.
all
(),
many
=
True
)
class
Meta
:
model
=
CommandFilter
list_serializer_class
=
AdaptedBulkListSerializer
fields
=
'__all__'
fields
=
[
'id'
,
'name'
,
'org_id'
,
'org_name'
,
'is_active'
,
'comment'
,
'created_by'
,
'date_created'
,
'date_updated'
,
'rules'
,
'system_users'
]
extra_kwargs
=
{
'rules'
:
{
'read_only'
:
True
},
'system_users'
:
{
'read_only'
:
True
}
}
class
CommandFilterRuleSerializer
(
BulkOrgResourceModelSerializer
):
...
...
apps/assets/serializers/domain.py
View file @
21ffa8b2
...
...
@@ -6,6 +6,7 @@ from common.serializers import AdaptedBulkListSerializer
from
orgs.mixins
import
BulkOrgResourceModelSerializer
from
..models
import
Domain
,
Gateway
from
.base
import
AuthSerializerMixin
class
DomainSerializer
(
BulkOrgResourceModelSerializer
):
...
...
@@ -26,14 +27,14 @@ class DomainSerializer(BulkOrgResourceModelSerializer):
return
obj
.
gateway_set
.
all
()
.
count
()
class
GatewaySerializer
(
BulkOrgResourceModelSerializer
):
class
GatewaySerializer
(
AuthSerializerMixin
,
BulkOrgResourceModelSerializer
):
class
Meta
:
model
=
Gateway
list_serializer_class
=
AdaptedBulkListSerializer
fields
=
[
'id'
,
'name'
,
'ip'
,
'port'
,
'protocol'
,
'username'
,
'
domain'
,
'is_active'
,
'date_created'
,
'date_upd
ated'
,
'created_by'
,
'comment'
,
'id'
,
'name'
,
'ip'
,
'port'
,
'protocol'
,
'username'
,
'password'
,
'
private_key'
,
'public_key'
,
'domain'
,
'is_active'
,
'date_cre
ated'
,
'
date_updated'
,
'
created_by'
,
'comment'
,
]
...
...
apps/assets/serializers/node.py
View file @
21ffa8b2
...
...
@@ -17,9 +17,8 @@ class NodeSerializer(BulkOrgResourceModelSerializer):
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'value'
,
'assets_amount'
,
'org_id'
,
]
only_fields
=
[
'id'
,
'key'
,
'value'
,
'org_id'
]
fields
=
only_fields
+
[
'assets_amount'
]
read_only_fields
=
[
'key'
,
'assets_amount'
,
'org_id'
,
]
...
...
apps/assets/serializers/system_user.py
View file @
21ffa8b2
...
...
@@ -21,14 +21,13 @@ class SystemUserSerializer(AuthSerializerMixin, BulkOrgResourceModelSerializer):
'id'
,
'name'
,
'username'
,
'password'
,
'public_key'
,
'private_key'
,
'login_mode'
,
'login_mode_display'
,
'priority'
,
'protocol'
,
'auto_push'
,
'cmd_filters'
,
'sudo'
,
'shell'
,
'comment'
,
'nodes'
,
'assets_amount'
,
'
connectivity_amount'
,
'
auto_generate_key'
'assets_amount'
,
'auto_generate_key'
]
extra_kwargs
=
{
'password'
:
{
"write_only"
:
True
},
'public_key'
:
{
"write_only"
:
True
},
'private_key'
:
{
"write_only"
:
True
},
'assets_amount'
:
{
'label'
:
_
(
'Asset'
)},
'connectivity_amount'
:
{
'label'
:
_
(
'Connectivity'
)},
'login_mode_display'
:
{
'label'
:
_
(
'Login mode display'
)},
'created_by'
:
{
'read_only'
:
True
},
}
...
...
apps/assets/signals_handler.py
View file @
21ffa8b2
...
...
@@ -78,6 +78,7 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
@receiver
(
m2m_changed
,
sender
=
Asset
.
nodes
.
through
)
def
on_asset_node_changed
(
sender
,
instance
=
None
,
**
kwargs
):
logger
.
debug
(
"Asset nodes change signal received"
)
Asset
.
expire_all_nodes_keys_cache
()
if
isinstance
(
instance
,
Asset
):
if
kwargs
[
'action'
]
==
'pre_remove'
:
nodes
=
kwargs
[
'model'
]
.
objects
.
filter
(
pk__in
=
kwargs
[
'pk_set'
])
...
...
apps/assets/templates/assets/admin_user_list.html
View file @
21ffa8b2
...
...
@@ -2,8 +2,6 @@
{% load i18n static %}
{% block help_message %}
<div
class=
"alert alert-info help-message"
>
{# 管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。#}
{# Windows或其它硬件可以随意设置一个#}
{% trans 'Admin users are asset (charged server) on the root, or have NOPASSWD: ALL sudo permissions users, '%}
{% trans 'Jumpserver users of the system using the user to `push system user`, `get assets hardware information`, etc. '%}
{% trans 'You can set any one for Windows or other hardware.' %}
...
...
@@ -47,9 +45,9 @@
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans 'Username' %}
</th>
<th
class=
"text-center"
>
{% trans 'Asset' %}
</th>
<th
class=
"text-center"
>
{% trans 'Reachable' %}
</th>
<th
class=
"text-center"
>
{% trans 'Unreachable' %}
</th>
<th
class=
"text-center"
>
{% trans 'Ratio' %}
</th>
{#
<th
class=
"text-center"
>
{% trans 'Reachable' %}
</th>
#}
{#
<th
class=
"text-center"
>
{% trans 'Unreachable' %}
</th>
#}
{#
<th
class=
"text-center"
>
{% trans 'Ratio' %}
</th>
#}
<th
class=
"text-center"
>
{% trans 'Comment' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
...
...
@@ -73,44 +71,44 @@ function initTable() {
var
detail_btn
=
'<a href="{% url "assets:admin-user-detail" pk=DEFAULT_PK %}">'
+
cellData
+
'</a>'
;
return
detail_btn
.
replace
(
'{{ DEFAULT_PK }}'
,
rowData
.
id
);
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
innerHtml
=
""
;
var
data
=
cellData
.
reachable
;
if
(
data
!==
0
)
{
innerHtml
=
"<span class='text-navy'>"
+
data
+
"</span>"
;
}
else
{
innerHtml
=
"<span>"
+
data
+
"</span>"
;
}
$
(
td
).
html
(
innerHtml
)
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
var
data
=
cellData
.
unreachable
;
var
innerHtml
=
""
;
if
(
data
!==
0
)
{
innerHtml
=
"<span class='text-danger'>"
+
data
+
"</span>"
;
}
else
{
innerHtml
=
"<span>"
+
data
+
"</span>"
;
}
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
data
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
val
=
0
;
var
innerHtml
=
""
;
var
total
=
rowData
.
assets_amount
;
var
reachable
=
cellData
.
reachable
;
if
(
total
!==
0
)
{
val
=
reachable
/
total
*
100
;
}
if
(
val
===
100
)
{
innerHtml
=
"<span class='text-navy'>"
+
val
+
"% </span>"
;
}
else
{
var
num
=
new
Number
(
val
);
innerHtml
=
"<span class='text-danger'>"
+
num
.
toFixed
(
1
)
+
"% </span>"
;
}
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
8
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
#
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
var
data
=
cellData
.
reachable
;
#
}
{
#
if
(
data
!==
0
)
{
#
}
{
#
innerHtml
=
"<span class='text-navy'>"
+
data
+
"</span>"
;
#
}
{
#
}
else
{
#
}
{
#
innerHtml
=
"<span>"
+
data
+
"</span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
innerHtml
)
#
}
{
#
}},
#
}
{
#
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
#
}
{
#
var
data
=
cellData
.
unreachable
;
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
if
(
data
!==
0
)
{
#
}
{
#
innerHtml
=
"<span class='text-danger'>"
+
data
+
"</span>"
;
#
}
{
#
}
else
{
#
}
{
#
innerHtml
=
"<span>"
+
data
+
"</span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
data
+
'">'
+
innerHtml
+
'</span>'
);
#
}
{
#
}},
#
}
{
#
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
#
}
{
#
var
val
=
0
;
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
var
total
=
rowData
.
assets_amount
;
#
}
{
#
var
reachable
=
cellData
.
reachable
;
#
}
{
#
if
(
total
!==
0
)
{
#
}
{
#
val
=
reachable
/
total
*
100
;
#
}
{
#
}
#
}
{
##
}
{
#
if
(
val
===
100
)
{
#
}
{
#
innerHtml
=
"<span class='text-navy'>"
+
val
+
"% </span>"
;
#
}
{
#
}
else
{
#
}
{
#
var
num
=
new
Number
(
val
);
#
}
{
#
innerHtml
=
"<span class='text-danger'>"
+
num
.
toFixed
(
1
)
+
"% </span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
#
}
{
#
}},
#
}
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
'<a href="{% url "assets:admin-user-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_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
$
(
td
).
html
(
update_btn
+
del_btn
)
...
...
@@ -118,7 +116,7 @@ function initTable() {
ajax_url
:
'{% url "api-assets:admin-user-list" %}'
,
columns
:
[
{
data
:
function
(){
return
""
}},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"assets_amount"
},
{
data
:
"connectivity_amount"
},
{
data
:
"connectivity_amount"
},
{
data
:
"connectivity_amount"
},
{
#
{
data
:
"connectivity_amount"
},
{
data
:
"connectivity_amount"
},
{
data
:
"connectivity_amount"
},
#
}
{
data
:
"comment"
},
{
data
:
"id"
}
]
};
...
...
apps/assets/templates/assets/cmd_filter_create_update.html
View file @
21ffa8b2
...
...
@@ -18,3 +18,29 @@
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
ready
(
function
()
{
})
.
on
(
"submit"
,
"form"
,
function
(
evt
)
{
evt
.
preventDefault
();
var
the_url
=
'{% url '
api
-
assets
:
cmd
-
filter
-
list
' %}'
;
var
redirect_to
=
'{% url "assets:cmd-filter-list" %}'
;
var
method
=
"POST"
;
{
%
if
type
==
"update"
%
}
the_url
=
'{% url '
api
-
assets
:
cmd
-
filter
-
detail
' pk=object.id %}'
;
method
=
"PUT"
;
{
%
endif
%
}
var
form
=
$
(
"form"
);
var
data
=
form
.
serializeObject
();
var
props
=
{
url
:
the_url
,
data
:
data
,
method
:
method
,
form
:
form
,
redirect_to
:
redirect_to
};
formSubmit
(
props
);
})
</script>
{% endblock %}
\ No newline at end of file
apps/assets/templates/assets/gateway_create_update.html
View file @
21ffa8b2
...
...
@@ -95,6 +95,32 @@ function protocolChange() {
$
(
document
).
ready
(
function
(){
protocolChange
();
})
.
on
(
"submit"
,
"form"
,
function
(
evt
)
{
evt
.
preventDefault
();
var
form
=
$
(
"form"
);
var
data
=
form
.
serializeObject
();
data
[
"private_key"
]
=
$
(
"#id_private_key_file"
).
data
(
'file'
);
var
method
=
"POST"
;
var
the_url
=
'{% url "api-assets:gateway-list" %}'
;
var
redirect_to
=
'{% url "assets:domain-gateway-list" pk=DEFAULT_PK %}'
.
replace
(
"{{ DEFAULT_PK }}"
,
data
.
domain
);
{
%
if
type
==
"update"
%
}
the_url
=
'{% url '
api
-
assets
:
gateway
-
detail
' pk=object.id %}'
;
method
=
"PUT"
;
{
%
endif
%
}
var
props
=
{
url
:
the_url
,
data
:
data
,
method
:
method
,
form
:
form
,
redirect_to
:
redirect_to
};
formSubmit
(
props
);
})
.
on
(
'change'
,
'#id_private_key_file'
,
function
()
{
readFile
(
$
(
this
)).
on
(
"onload"
,
function
(
evt
,
data
)
{
$
(
this
).
attr
(
"data-file"
,
data
)
})
})
.
on
(
'change'
,
protocol_id
,
function
(){
protocolChange
();
});
...
...
apps/assets/templates/assets/system_user_list.html
View file @
21ffa8b2
...
...
@@ -53,9 +53,9 @@
<th
class=
"text-center"
>
{% trans 'Protocol' %}
</th>
<th
class=
"text-center"
>
{% trans 'Login mode' %}
</th>
<th
class=
"text-center"
>
{% trans 'Asset' %}
</th>
<th
class=
"text-center"
>
{% trans 'Reachable' %}
</th>
<th
class=
"text-center"
>
{% trans 'Unreachable' %}
</th>
<th
class=
"text-center"
>
{% trans 'Ratio' %}
</th>
{#
<th
class=
"text-center"
>
{% trans 'Reachable' %}
</th>
#}
{#
<th
class=
"text-center"
>
{% trans 'Unreachable' %}
</th>
#}
{#
<th
class=
"text-center"
>
{% trans 'Ratio' %}
</th>
#}
<th
class=
"text-center"
>
{% trans 'Comment' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
...
...
@@ -78,44 +78,44 @@ function initTable() {
var
detail_btn
=
'<a href="{% url "assets:system-user-detail" pk=DEFAULT_PK %}">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detail_btn
.
replace
(
'{{ DEFAULT_PK }}'
,
rowData
.
id
));
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
)
{
var
innerHtml
=
""
;
var
data
=
cellData
.
reachable
;
if
(
data
!==
0
)
{
innerHtml
=
"<span class='text-navy'>"
+
data
+
"</span>"
;
}
else
{
innerHtml
=
"<span>"
+
data
+
"</span>"
;
}
$
(
td
).
html
(
innerHtml
)
}},
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
)
{
var
data
=
cellData
.
unreachable
;
var
innerHtml
=
""
;
if
(
data
!==
0
)
{
innerHtml
=
"<span class='text-danger'>"
+
data
+
"</span>"
;
}
else
{
innerHtml
=
"<span>"
+
data
+
"</span>"
;
}
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
data
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
8
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
val
=
0
;
var
innerHtml
=
""
;
var
total
=
rowData
.
assets_amount
;
var
reachable
=
cellData
.
reachable
;
if
(
total
&&
total
!==
0
)
{
val
=
reachable
/
total
*
100
;
}
if
(
val
===
100
)
{
innerHtml
=
"<span class='text-navy'>"
+
val
+
"% </span>"
;
}
else
{
var
num
=
new
Number
(
val
);
innerHtml
=
"<span class='text-danger'>"
+
num
.
toFixed
(
1
)
+
"% </span>"
;
}
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
}},
{
targets
:
10
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
#
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
)
{
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
var
data
=
cellData
.
reachable
;
#
}
{
#
if
(
data
!==
0
)
{
#
}
{
#
innerHtml
=
"<span class='text-navy'>"
+
data
+
"</span>"
;
#
}
{
#
}
else
{
#
}
{
#
innerHtml
=
"<span>"
+
data
+
"</span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
innerHtml
)
#
}
{
#
}},
#
}
{
#
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
)
{
#
}
{
#
var
data
=
cellData
.
unreachable
;
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
if
(
data
!==
0
)
{
#
}
{
#
innerHtml
=
"<span class='text-danger'>"
+
data
+
"</span>"
;
#
}
{
#
}
else
{
#
}
{
#
innerHtml
=
"<span>"
+
data
+
"</span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
data
+
'">'
+
innerHtml
+
'</span>'
);
#
}
{
#
}},
#
}
{
#
{
targets
:
8
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
#
}
{
#
var
val
=
0
;
#
}
{
#
var
innerHtml
=
""
;
#
}
{
#
var
total
=
rowData
.
assets_amount
;
#
}
{
#
var
reachable
=
cellData
.
reachable
;
#
}
{
#
if
(
total
&&
total
!==
0
)
{
#
}
{
#
val
=
reachable
/
total
*
100
;
#
}
{
#
}
#
}
{
##
}
{
#
if
(
val
===
100
)
{
#
}
{
#
innerHtml
=
"<span class='text-navy'>"
+
val
+
"% </span>"
;
#
}
{
#
}
else
{
#
}
{
#
var
num
=
new
Number
(
val
);
#
}
{
#
innerHtml
=
"<span class='text-danger'>"
+
num
.
toFixed
(
1
)
+
"% </span>"
;
#
}
{
#
}
#
}
{
#
$
(
td
).
html
(
'<span href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</span>'
);
#
}
{
#
}},
#
}
{
targets
:
7
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
'<a href="{% url "assets:system-user-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_admin_user_delete" data-uid="{{ DEFAULT_PK }}">{% trans "Delete" %}</a>'
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
$
(
td
).
html
(
update_btn
+
del_btn
)
...
...
@@ -124,7 +124,7 @@ function initTable() {
ajax_url
:
'{% url "api-assets:system-user-list" %}'
,
columns
:
[
{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"protocol"
},
{
data
:
"login_mode_display"
},
{
data
:
"assets_amount"
},
{
data
:
"co
nnectivity_amount"
},
{
data
:
"connectivity_amount"
},
{
data
:
"connectivity_amount"
},
{
data
:
"co
mment"
},
{
data
:
"id"
}
{
data
:
"comment"
},
{
data
:
"id"
}
],
op_html
:
$
(
'#actions'
).
html
()
};
...
...
apps/assets/templates/assets/user_asset_list.html
View file @
21ffa8b2
...
...
@@ -43,7 +43,6 @@
<th
class=
"text-center"
><input
type=
"checkbox"
class=
"ipt_check_all"
></th>
<th
class=
"text-center"
>
{% trans 'Hostname' %}
</th>
<th
class=
"text-center"
>
{% trans 'IP' %}
</th>
<th
class=
"text-center"
>
{% trans 'Active' %}
</th>
<th
class=
"text-center"
>
{% trans 'System users' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
...
...
@@ -62,7 +61,7 @@
{% block custom_foot_js %}
<script>
var
treeUrl
=
"{% url 'api-perms:my-nodes-as
sets-as-tree' %}?show_assets=0
&cache_policy=1"
;
var
treeUrl
=
"{% url 'api-perms:my-nodes-as
-tree' %}?
&cache_policy=1"
;
var
zTree
,
asset_table
,
show
=
0
;
var
inited
=
false
;
var
url
;
...
...
@@ -83,20 +82,13 @@ function initTable() {
$
(
td
).
html
(
detail_btn
.
replace
(
"rowData_id"
,
rowData
.
id
));
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
users
=
[];
$
.
each
(
cellData
,
function
(
id
,
data
)
{
users
.
push
(
data
.
name
);
});
$
(
td
).
html
(
users
.
join
(
', '
))
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
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
)
}}
...
...
@@ -104,7 +96,6 @@ function initTable() {
ajax_url
:
url
,
columns
:
[
{
data
:
"id"
},
{
data
:
"hostname"
},
{
data
:
"ip"
},
{
data
:
"is_active"
,
orderable
:
false
},
{
data
:
"system_users_granted"
,
orderable
:
false
},
{
data
:
"id"
,
orderable
:
false
}
]
...
...
apps/assets/utils.py
View file @
21ffa8b2
...
...
@@ -104,7 +104,7 @@ class NodeUtil:
_node
.
_assets_amount
=
len
(
_node
.
_assets
)
delattr
(
_node
,
'_assets'
)
self
.
stack
.
top
.
_children
.
append
(
_node
)
self
.
stack
.
top
.
_all_children
.
extend
([
_node
]
+
_node
.
_children
)
self
.
stack
.
top
.
_all_children
.
extend
([
_node
]
+
_node
.
_
all_
children
)
def
init
(
self
):
all_nodes
=
self
.
get_all_nodes
()
...
...
@@ -145,29 +145,69 @@ class NodeUtil:
def
nodes
(
self
):
return
list
(
self
.
_nodes
.
values
())
# 使用给定节点生成一颗树
# 找到他们的祖先节点
# 可选找到他们的子孙节点
def
get_family
(
self
,
nodes
,
with_children
=
False
):
def
get_family_by_key
(
self
,
key
):
tree_nodes
=
set
()
for
n
in
nodes
:
node
=
self
.
get_node_by_key
(
n
.
key
)
node
=
self
.
get_node_by_key
(
key
)
if
not
node
:
continue
return
[]
tree_nodes
.
update
(
node
.
_parents
)
tree_nodes
.
add
(
node
)
if
with_children
:
tree_nodes
.
update
(
node
.
_children
)
tree_nodes
.
update
(
node
.
_all_children
)
return
list
(
tree_nodes
)
def
get_nodes_parents
(
self
,
nodes
,
with_self
=
True
):
# 使用给定节点生成一颗树
# 找到他们的祖先节点
# 可选找到他们的子孙节点
def
get_family
(
self
,
node
):
return
self
.
get_family_by_key
(
node
.
key
)
def
get_family_keys_by_key
(
self
,
key
):
nodes
=
self
.
get_family_by_key
(
key
)
return
[
n
.
key
for
n
in
nodes
]
def
get_some_nodes_family_by_keys
(
self
,
keys
):
family
=
set
()
for
key
in
keys
:
family
.
update
(
self
.
get_family_by_key
(
key
))
return
family
def
get_some_nodes_family_keys_by_keys
(
self
,
keys
):
family
=
self
.
get_some_nodes_family_by_keys
(
keys
)
return
[
n
.
key
for
n
in
family
]
def
get_nodes_parents_by_key
(
self
,
key
,
with_self
=
True
):
parents
=
set
()
for
n
in
nodes
:
node
=
self
.
get_node_by_key
(
n
.
key
)
node
=
self
.
get_node_by_key
(
key
)
if
not
node
:
return
[]
parents
.
update
(
set
(
node
.
_parents
))
if
with_self
:
parents
.
add
(
node
)
return
parents
return
list
(
parents
)
def
get_node_parents
(
self
,
node
,
with_self
=
True
):
return
self
.
get_nodes_parents_by_key
(
node
.
key
,
with_self
=
with_self
)
def
get_nodes_parents_keys_by_key
(
self
,
key
,
with_self
=
True
):
nodes
=
self
.
get_nodes_parents_by_key
(
key
,
with_self
=
with_self
)
return
[
n
.
key
for
n
in
nodes
]
def
get_all_children_by_key
(
self
,
key
,
with_self
=
True
):
children
=
set
()
node
=
self
.
get_node_by_key
(
key
)
if
not
node
:
return
[]
children
.
update
(
set
(
node
.
_all_children
))
if
with_self
:
children
.
add
(
node
)
return
list
(
children
)
def
get_children
(
self
,
node
,
with_self
=
True
):
return
self
.
get_all_children_by_key
(
node
.
key
,
with_self
=
with_self
)
def
get_children_keys_by_key
(
self
,
key
,
with_self
=
True
):
nodes
=
self
.
get_all_children_by_key
(
key
,
with_self
=
with_self
)
return
[
n
.
key
for
n
in
nodes
]
def
test_node_tree
():
...
...
apps/assets/views/cmd_filter.py
View file @
21ffa8b2
...
...
@@ -47,6 +47,7 @@ class CommandFilterCreateView(PermissionsMixin, CreateView):
context
=
{
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Create command filter'
),
'type'
:
'create'
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -64,6 +65,7 @@ class CommandFilterUpdateView(PermissionsMixin, UpdateView):
context
=
{
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Update command filter'
),
'type'
:
'update'
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
apps/assets/views/domain.py
View file @
21ffa8b2
...
...
@@ -132,6 +132,7 @@ class DomainGatewayCreateView(PermissionsMixin, CreateView):
context
=
{
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Create gateway'
),
'type'
:
'create'
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
...
...
@@ -152,6 +153,7 @@ class DomainGatewayUpdateView(PermissionsMixin, UpdateView):
context
=
{
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Update gateway'
),
"type"
:
"update"
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
apps/common/api.py
View file @
21ffa8b2
...
...
@@ -4,11 +4,13 @@ import os
import
uuid
from
django.core.cache
import
cache
from
django.views.decorators.csrf
import
csrf_exempt
from
rest_framework.views
import
APIView
from
rest_framework.response
import
Response
from
rest_framework
import
generics
,
serializers
from
.http
import
HttpResponseTemporaryRedirect
from
.const
import
KEY_CACHE_RESOURCES_ID
__all__
=
[
...
...
@@ -86,3 +88,11 @@ class ResourcesIDCacheApi(APIView):
cache_key
=
KEY_CACHE_RESOURCES_ID
.
format
(
spm
)
cache
.
set
(
cache_key
,
resources_id
,
300
)
return
Response
({
'spm'
:
spm
})
@csrf_exempt
def
redirect_plural_name_api
(
request
,
*
args
,
**
kwargs
):
resource
=
kwargs
.
get
(
"resource"
,
""
)
full_path
=
request
.
get_full_path
()
full_path
=
full_path
.
replace
(
resource
,
resource
+
"s"
,
1
)
return
HttpResponseTemporaryRedirect
(
full_path
)
apps/common/http.py
0 → 100644
View file @
21ffa8b2
# -*- coding: utf-8 -*-
#
from
django.http
import
HttpResponse
from
django.utils.encoding
import
iri_to_uri
class
HttpResponseTemporaryRedirect
(
HttpResponse
):
status_code
=
307
def
__init__
(
self
,
redirect_to
):
HttpResponse
.
__init__
(
self
)
self
[
'Location'
]
=
iri_to_uri
(
redirect_to
)
apps/common/tree.py
View file @
21ffa8b2
...
...
@@ -46,12 +46,17 @@ class TreeNode:
def
__gt__
(
self
,
other
):
if
self
.
isParent
and
not
other
.
isParent
:
re
turn
False
re
sult
=
False
elif
not
self
.
isParent
and
other
.
isParent
:
return
True
if
self
.
pId
!=
other
.
pId
:
return
self
.
pId
>
other
.
pId
return
self
.
name
>
other
.
name
result
=
True
elif
self
.
pId
!=
other
.
pId
:
result
=
self
.
pId
>
other
.
pId
else
:
result
=
self
.
name
>
other
.
name
return
result
def
__le__
(
self
,
other
):
return
not
self
.
__gt__
(
other
)
def
__eq__
(
self
,
other
):
return
self
.
id
==
other
.
id
...
...
@@ -74,7 +79,7 @@ class Tree:
raise
ValueError
(
"Parent must not be node parent"
)
node
.
pId
=
parent
.
id
parent
.
isParent
=
True
self
.
nodes
[
node
.
id
]
=
node
self
.
nodes
[
node
.
key
]
=
node
def
get_nodes
(
self
):
return
sorted
(
self
.
nodes
.
values
())
...
...
apps/common/utils/common.py
View file @
21ffa8b2
...
...
@@ -7,6 +7,7 @@ import logging
import
datetime
import
uuid
from
functools
import
wraps
import
time
import
copy
import
ipaddress
...
...
@@ -179,3 +180,18 @@ def random_string(length):
charset
=
string
.
ascii_letters
+
string
.
digits
s
=
[
random
.
choice
(
charset
)
for
i
in
range
(
length
)]
return
''
.
join
(
s
)
logger
=
get_logger
(
__name__
)
def
timeit
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
logger
.
debug
(
"Start call: {}"
.
format
(
func
.
__name__
))
now
=
time
.
time
()
result
=
func
(
*
args
,
**
kwargs
)
using
=
(
time
.
time
()
-
now
)
*
1000
msg
=
"Call {} end, using: {:.1f}ms"
.
format
(
func
.
__name__
,
using
)
logger
.
debug
(
msg
)
return
result
return
wrapper
apps/jumpserver/views.py
View file @
21ffa8b2
...
...
@@ -2,7 +2,7 @@ import datetime
import
re
import
time
from
django.http
import
HttpResponse
,
HttpResponse
Redirect
from
django.http
import
HttpResponseRedirect
from
django.conf
import
settings
from
django.views.generic
import
TemplateView
,
View
from
django.utils
import
timezone
...
...
@@ -13,13 +13,14 @@ from rest_framework.response import Response
from
rest_framework.views
import
APIView
from
django.views.decorators.csrf
import
csrf_exempt
from
django.http
import
HttpResponse
from
django.utils.encoding
import
iri_to_uri
from
users.models
import
User
from
assets.models
import
Asset
from
terminal.models
import
Session
from
orgs.utils
import
current_org
from
common.permissions
import
PermissionsMixin
,
IsValidUser
from
common.http
import
HttpResponseTemporaryRedirect
class
IndexView
(
PermissionsMixin
,
TemplateView
):
...
...
@@ -203,14 +204,6 @@ class I18NView(View):
api_url_pattern
=
re
.
compile
(
r'^/api/(?P<version>\w+)/(?P<app>\w+)/(?P<extra>.*)$'
)
class
HttpResponseTemporaryRedirect
(
HttpResponse
):
status_code
=
307
def
__init__
(
self
,
redirect_to
):
HttpResponse
.
__init__
(
self
)
self
[
'Location'
]
=
iri_to_uri
(
redirect_to
)
@csrf_exempt
def
redirect_format_api
(
request
,
*
args
,
**
kwargs
):
_path
,
query
=
request
.
path
,
request
.
GET
.
urlencode
()
...
...
apps/locale/zh/LC_MESSAGES/django.mo
View file @
21ffa8b2
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
21ffa8b2
...
...
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Jumpserver 0.3.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-
08 15:32
+0800\n"
"POT-Creation-Date: 2019-07-
11 11:23
+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: ibuler <ibuler@qq.com>\n"
"Language-Team: Jumpserver team<ibuler@qq.com>\n"
...
...
@@ -76,10 +76,10 @@ msgstr "运行参数"
#: applications/templates/applications/remote_app_list.html:22
#: applications/templates/applications/user_remote_app_list.html:18
#: assets/forms/domain.py:15 assets/forms/label.py:13
#: assets/models/asset.py:
286
assets/models/authbook.py:24
#: assets/models/asset.py:
342
assets/models/authbook.py:24
#: assets/serializers/admin_user.py:35 assets/serializers/asset_user.py:81
#: assets/serializers/system_user.py:30
#: assets/templates/assets/admin_user_list.html:4
9
#: assets/templates/assets/admin_user_list.html:4
7
#: assets/templates/assets/domain_detail.html:60
#: assets/templates/assets/domain_list.html:26
#: assets/templates/assets/label_list.html:16
...
...
@@ -95,7 +95,7 @@ msgstr "运行参数"
#: terminal/templates/terminal/command_list.html:66
#: terminal/templates/terminal/session_list.html:28
#: terminal/templates/terminal/session_list.html:72
#: xpack/plugins/change_auth_plan/forms.py:11
4
#: xpack/plugins/change_auth_plan/forms.py:11
5
#: xpack/plugins/change_auth_plan/models.py:413
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:46
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:54
...
...
@@ -139,7 +139,7 @@ msgstr "系统用户"
#: assets/models/cmd_filter.py:20 assets/models/domain.py:20
#: assets/models/group.py:20 assets/models/label.py:18
#: assets/templates/assets/admin_user_detail.html:56
#: assets/templates/assets/admin_user_list.html:4
7
#: assets/templates/assets/admin_user_list.html:4
5
#: assets/templates/assets/cmd_filter_detail.html:61
#: assets/templates/assets/cmd_filter_list.html:24
#: assets/templates/assets/domain_detail.html:56
...
...
@@ -173,7 +173,7 @@ msgstr "系统用户"
#: users/templates/users/user_list.html:35
#: users/templates/users/user_profile.html:51
#: users/templates/users/user_pubkey_update.html:53
#: xpack/plugins/change_auth_plan/forms.py:9
7
#: xpack/plugins/change_auth_plan/forms.py:9
8
#: xpack/plugins/change_auth_plan/models.py:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:61
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:12
...
...
@@ -205,7 +205,7 @@ msgstr "参数"
#: applications/models/remote_app.py:43
#: applications/templates/applications/remote_app_detail.html:77
#: assets/models/asset.py:
15
1 assets/models/base.py:36
#: assets/models/asset.py:
22
1 assets/models/base.py:36
#: assets/models/cluster.py:28 assets/models/cmd_filter.py:25
#: assets/models/cmd_filter.py:58 assets/models/group.py:21
#: assets/templates/assets/admin_user_detail.html:68
...
...
@@ -229,7 +229,7 @@ msgstr "创建者"
# msgstr "创建者"
#: applications/models/remote_app.py:46
#: applications/templates/applications/remote_app_detail.html:73
#: assets/models/asset.py:
15
2 assets/models/base.py:34
#: assets/models/asset.py:
22
2 assets/models/base.py:34
#: assets/models/cluster.py:26 assets/models/domain.py:23
#: assets/models/group.py:22 assets/models/label.py:25
#: assets/templates/assets/admin_user_detail.html:64
...
...
@@ -257,12 +257,12 @@ msgstr "创建日期"
#: applications/templates/applications/remote_app_detail.html:81
#: applications/templates/applications/remote_app_list.html:24
#: applications/templates/applications/user_remote_app_list.html:20
#: assets/models/asset.py:
15
3 assets/models/base.py:33
#: assets/models/asset.py:
22
3 assets/models/base.py:33
#: assets/models/cluster.py:29 assets/models/cmd_filter.py:22
#: assets/models/cmd_filter.py:55 assets/models/domain.py:21
#: assets/models/domain.py:53 assets/models/group.py:23
#: assets/models/label.py:23 assets/templates/assets/admin_user_detail.html:72
#: assets/templates/assets/admin_user_list.html:5
3
#: assets/templates/assets/admin_user_list.html:5
1
#: assets/templates/assets/asset_detail.html:132
#: assets/templates/assets/cmd_filter_detail.html:65
#: assets/templates/assets/cmd_filter_list.html:27
...
...
@@ -412,8 +412,8 @@ msgstr "详情"
#: applications/templates/applications/remote_app_list.html:56
#: assets/templates/assets/_asset_user_list.html:70
#: assets/templates/assets/admin_user_detail.html:24
#: assets/templates/assets/admin_user_list.html:2
9
#: assets/templates/assets/admin_user_list.html:11
4
#: assets/templates/assets/admin_user_list.html:2
7
#: assets/templates/assets/admin_user_list.html:11
2
#: assets/templates/assets/asset_detail.html:27
#: assets/templates/assets/asset_list.html:78
#: assets/templates/assets/asset_list.html:169
...
...
@@ -456,7 +456,7 @@ msgstr "更新"
#: applications/templates/applications/remote_app_detail.html:25
#: applications/templates/applications/remote_app_list.html:57
#: assets/templates/assets/admin_user_detail.html:28
#: assets/templates/assets/admin_user_list.html:11
5
#: assets/templates/assets/admin_user_list.html:11
3
#: assets/templates/assets/asset_detail.html:31
#: assets/templates/assets/asset_list.html:170
#: assets/templates/assets/cmd_filter_detail.html:33
...
...
@@ -515,7 +515,7 @@ msgstr "创建远程应用"
#: applications/templates/applications/user_remote_app_list.html:21
#: assets/models/cmd_filter.py:54
#: assets/templates/assets/_asset_user_list.html:20
#: assets/templates/assets/admin_user_list.html:5
4
#: assets/templates/assets/admin_user_list.html:5
2
#: assets/templates/assets/asset_list.html:100
#: assets/templates/assets/cmd_filter_list.html:28
#: assets/templates/assets/cmd_filter_rule_list.html:63
...
...
@@ -598,25 +598,21 @@ msgid "Test if the assets under the node are connectable: {}"
msgstr "测试节点下资产是否可连接: {}"
#: assets/const.py:77 assets/models/utils.py:43
#: assets/templates/assets/admin_user_list.html:51
#: assets/templates/assets/system_user_list.html:57
msgid "Unreachable"
msgstr "不可达"
#: assets/const.py:78 assets/models/utils.py:44
#: assets/templates/assets/admin_user_list.html:50
#: assets/templates/assets/asset_list.html:99
#: assets/templates/assets/system_user_list.html:56
#: users/templates/users/user_group_granted_asset.html:47
msgid "Reachable"
msgstr "可连接"
#: assets/const.py:79 assets/models/utils.py:45 authentication/utils.py:9
#: xpack/plugins/license/models.py:7
9
#: xpack/plugins/license/models.py:7
8
msgid "Unknown"
msgstr "未知"
#: assets/forms/asset.py:24 assets/models/asset.py:1
1
7
#: assets/forms/asset.py:24 assets/models/asset.py:1
8
7
#: assets/models/domain.py:50
#: assets/templates/assets/domain_gateway_list.html:69
#: assets/templates/assets/user_asset_list.html:168
...
...
@@ -624,7 +620,7 @@ msgstr "未知"
msgid "Port"
msgstr "端口"
#: assets/forms/asset.py:45 assets/models/asset.py:1
2
2
#: assets/forms/asset.py:45 assets/models/asset.py:1
9
2
#: assets/models/user.py:107 assets/templates/assets/asset_detail.html:190
#: assets/templates/assets/asset_detail.html:198
#: assets/templates/assets/system_user_assets.html:83
...
...
@@ -633,7 +629,7 @@ msgstr "端口"
msgid "Nodes"
msgstr "节点"
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:1
2
6
#: assets/forms/asset.py:48 assets/forms/asset.py:83 assets/models/asset.py:1
9
6
#: assets/models/cluster.py:19 assets/models/user.py:65
#: assets/templates/assets/asset_detail.html:76 templates/_nav.html:24
#: xpack/plugins/cloud/models.py:124
...
...
@@ -651,7 +647,7 @@ msgstr "管理用户"
msgid "Label"
msgstr "标签"
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:1
2
1
#: assets/forms/asset.py:54 assets/forms/asset.py:89 assets/models/asset.py:1
9
1
#: assets/models/domain.py:26 assets/models/domain.py:52
#: assets/templates/assets/asset_detail.html:80
#: assets/templates/assets/user_asset_list.html:173
...
...
@@ -660,14 +656,14 @@ msgid "Domain"
msgstr "网域"
#: assets/forms/asset.py:58 assets/forms/asset.py:80 assets/forms/asset.py:93
#: assets/forms/asset.py:128 assets/models/node.py:25
5
#: assets/forms/asset.py:128 assets/models/node.py:25
4
#: assets/templates/assets/asset_create.html:42
#: perms/forms/asset_permission.py:71 perms/forms/asset_permission.py:78
#: perms/models/asset_permission.py:101
#: perms/templates/perms/asset_permission_list.html:49
#: perms/templates/perms/asset_permission_list.html:70
#: perms/templates/perms/asset_permission_list.html:120
#: xpack/plugins/change_auth_plan/forms.py:11
5
#: xpack/plugins/change_auth_plan/forms.py:11
6
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_execution_list.html:55
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:15
#: xpack/plugins/cloud/models.py:123
...
...
@@ -696,7 +692,7 @@ msgstr "如果有多个的互相隔离的网络,设置资产属于的网域,
#: assets/forms/asset.py:108 assets/forms/asset.py:112
#: assets/forms/domain.py:17 assets/forms/label.py:15
#: perms/templates/perms/asset_permission_asset.html:88
#: xpack/plugins/change_auth_plan/forms.py:10
5
#: xpack/plugins/change_auth_plan/forms.py:10
6
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:84
msgid "Select assets"
msgstr "选择资产"
...
...
@@ -719,7 +715,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: assets/templates/assets/_asset_user_auth_view_modal.html:21
#: assets/templates/assets/_asset_user_list.html:16
#: assets/templates/assets/admin_user_detail.html:60
#: assets/templates/assets/admin_user_list.html:4
8
#: assets/templates/assets/admin_user_list.html:4
6
#: assets/templates/assets/domain_gateway_list.html:71
#: assets/templates/assets/system_user_detail.html:62
#: assets/templates/assets/system_user_list.html:52 audits/models.py:94
...
...
@@ -734,7 +730,7 @@ msgstr "SSH网关,支持代理SSH,RDP和VNC"
#: users/templates/users/user_detail.html:67
#: users/templates/users/user_list.html:36
#: users/templates/users/user_profile.html:47
#: xpack/plugins/change_auth_plan/forms.py:
99
#: xpack/plugins/change_auth_plan/forms.py:
100
#: xpack/plugins/change_auth_plan/models.py:63
#: xpack/plugins/change_auth_plan/models.py:409
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:65
...
...
@@ -809,7 +805,7 @@ msgstr "如果选择手动登录模式,用户名和密码可以不填写"
msgid "Use comma split multi command, ex: /bin/whoami,/bin/ifconfig"
msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
#: assets/models/asset.py:1
1
2 assets/models/domain.py:49
#: assets/models/asset.py:1
8
2 assets/models/domain.py:49
#: assets/serializers/asset_user.py:28
#: assets/templates/assets/_asset_list_modal.html:46
#: assets/templates/assets/_asset_user_list.html:15
...
...
@@ -826,7 +822,7 @@ msgstr "使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig"
msgid "IP"
msgstr "IP"
#: assets/models/asset.py:1
1
3 assets/serializers/asset_user.py:27
#: assets/models/asset.py:1
8
3 assets/serializers/asset_user.py:27
#: assets/templates/assets/_asset_list_modal.html:45
#: assets/templates/assets/_asset_user_auth_update_modal.html:9
#: assets/templates/assets/_asset_user_auth_view_modal.html:15
...
...
@@ -843,101 +839,100 @@ msgstr "IP"
msgid "Hostname"
msgstr "主机名"
#: assets/models/asset.py:1
1
6 assets/models/domain.py:51
#: assets/models/asset.py:1
8
6 assets/models/domain.py:51
#: assets/models/user.py:110 assets/templates/assets/asset_detail.html:72
#: assets/templates/assets/domain_gateway_list.html:70
#: assets/templates/assets/system_user_detail.html:70
#: assets/templates/assets/system_user_list.html:53
#: assets/templates/assets/user_asset_list.html:169
#: terminal/templates/terminal/session_list.html:31
#: terminal/templates/terminal/session_list.html:75
msgid "Protocol"
msgstr "协议"
#: assets/models/asset.py:1
1
9 assets/serializers/asset.py:63
#: assets/models/asset.py:1
8
9 assets/serializers/asset.py:63
#: assets/templates/assets/asset_create.html:24
msgid "Protocols"
msgstr "协议组"
#: assets/models/asset.py:1
2
0 assets/templates/assets/asset_detail.html:104
#: assets/models/asset.py:1
9
0 assets/templates/assets/asset_detail.html:104
#: assets/templates/assets/user_asset_list.html:170
msgid "Platform"
msgstr "系统平台"
#: assets/models/asset.py:1
2
3 assets/models/cmd_filter.py:21
#: assets/models/asset.py:1
9
3 assets/models/cmd_filter.py:21
#: assets/models/domain.py:54 assets/models/label.py:22
#: assets/templates/assets/asset_detail.html:112
#: assets/templates/assets/user_asset_list.html:174
msgid "Is active"
msgstr "激活"
#: assets/models/asset.py:1
2
9 assets/templates/assets/asset_detail.html:68
#: assets/models/asset.py:1
9
9 assets/templates/assets/asset_detail.html:68
msgid "Public IP"
msgstr "公网IP"
#: assets/models/asset.py:
13
0 assets/templates/assets/asset_detail.html:120
#: assets/models/asset.py:
20
0 assets/templates/assets/asset_detail.html:120
msgid "Asset number"
msgstr "资产编号"
#: assets/models/asset.py:
13
3 assets/templates/assets/asset_detail.html:84
#: assets/models/asset.py:
20
3 assets/templates/assets/asset_detail.html:84
msgid "Vendor"
msgstr "制造商"
#: assets/models/asset.py:
13
4 assets/templates/assets/asset_detail.html:88
#: assets/models/asset.py:
20
4 assets/templates/assets/asset_detail.html:88
msgid "Model"
msgstr "型号"
#: assets/models/asset.py:
13
5 assets/templates/assets/asset_detail.html:116
#: assets/models/asset.py:
20
5 assets/templates/assets/asset_detail.html:116
msgid "Serial number"
msgstr "序列号"
#: assets/models/asset.py:
13
7
#: assets/models/asset.py:
20
7
msgid "CPU model"
msgstr "CPU型号"
#: assets/models/asset.py:
13
8
#: assets/models/asset.py:
20
8
#: xpack/plugins/license/templates/license/license_detail.html:80
msgid "CPU count"
msgstr "CPU数量"
#: assets/models/asset.py:
13
9
#: assets/models/asset.py:
20
9
msgid "CPU cores"
msgstr "CPU核数"
#: assets/models/asset.py:
14
0
#: assets/models/asset.py:
21
0
msgid "CPU vcpus"
msgstr "CPU总数"
#: assets/models/asset.py:
14
1 assets/templates/assets/asset_detail.html:96
#: assets/models/asset.py:
21
1 assets/templates/assets/asset_detail.html:96
msgid "Memory"
msgstr "内存"
#: assets/models/asset.py:
14
2
#: assets/models/asset.py:
21
2
msgid "Disk total"
msgstr "硬盘大小"
#: assets/models/asset.py:
14
3
#: assets/models/asset.py:
21
3
msgid "Disk info"
msgstr "硬盘信息"
#: assets/models/asset.py:
14
5 assets/templates/assets/asset_detail.html:108
#: assets/models/asset.py:
21
5 assets/templates/assets/asset_detail.html:108
#: assets/templates/assets/user_asset_list.html:171
msgid "OS"
msgstr "操作系统"
#: assets/models/asset.py:
14
6
#: assets/models/asset.py:
21
6
msgid "OS version"
msgstr "系统版本"
#: assets/models/asset.py:
14
7
#: assets/models/asset.py:
21
7
msgid "OS arch"
msgstr "系统架构"
#: assets/models/asset.py:
14
8
#: assets/models/asset.py:
21
8
msgid "Hostname raw"
msgstr "主机名原始"
#: assets/models/asset.py:
15
0 assets/templates/assets/asset_create.html:46
#: assets/models/asset.py:
22
0 assets/templates/assets/asset_create.html:46
#: assets/templates/assets/asset_detail.html:227 templates/_nav.html:26
msgid "Labels"
msgstr "标签管理"
...
...
@@ -1003,7 +998,6 @@ msgid "Operator"
msgstr "运营商"
#: assets/models/cluster.py:36 assets/models/group.py:34
#: perms/utils/asset_permission.py:106
msgid "Default"
msgstr "默认"
...
...
@@ -1136,7 +1130,7 @@ msgstr "默认资产组"
msgid "User"
msgstr "用户"
#: assets/models/label.py:19 assets/models/node.py:24
6
#: assets/models/label.py:19 assets/models/node.py:24
5
#: assets/templates/assets/label_list.html:15 settings/models.py:30
msgid "Value"
msgstr "值"
...
...
@@ -1145,11 +1139,11 @@ msgstr "值"
msgid "Category"
msgstr "分类"
#: assets/models/node.py:24
5
#: assets/models/node.py:24
4
msgid "Key"
msgstr "键"
#: assets/models/node.py:30
3
#: assets/models/node.py:30
2
msgid "New node"
msgstr "新节点"
...
...
@@ -1208,12 +1202,6 @@ msgstr "登录模式"
msgid "%(value)s is not an even number"
msgstr "%(value)s is not an even number"
#: assets/serializers/admin_user.py:36 assets/serializers/asset.py:64
#: assets/serializers/asset_user.py:29 assets/serializers/system_user.py:31
#: assets/templates/assets/_asset_user_list.html:18
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset.py:21
msgid "Protocol format should {}/{}"
msgstr "协议格式 {}/{}"
...
...
@@ -1222,6 +1210,11 @@ msgstr "协议格式 {}/{}"
msgid "Protocol duplicate: {}"
msgstr "协议重复: {}"
#: assets/serializers/asset.py:64 assets/serializers/asset_user.py:29
#: assets/templates/assets/_asset_user_list.html:18
msgid "Connectivity"
msgstr "连接"
#: assets/serializers/asset.py:90
msgid "Hardware info"
msgstr "硬件信息"
...
...
@@ -1247,19 +1240,19 @@ msgstr "ssh公钥"
msgid "private key invalid"
msgstr "密钥不合法"
#: assets/serializers/node.py:3
3
#: assets/serializers/node.py:3
2
msgid "The same level node name cannot be the same"
msgstr "同级别节点名字不能重复"
#: assets/serializers/system_user.py:3
2
#: assets/serializers/system_user.py:3
1
msgid "Login mode display"
msgstr "登录模式显示"
#: assets/serializers/system_user.py:6
7
#: assets/serializers/system_user.py:6
6
msgid "* Automatic login mode must fill in the username."
msgstr "自动登录模式,必须填写用户名"
#: assets/serializers/system_user.py:7
6
#: assets/serializers/system_user.py:7
5
msgid "Password or private key required"
msgstr "密码或密钥密码需要一个"
...
...
@@ -1401,7 +1394,7 @@ msgid "Update asset user auth"
msgstr "更新资产用户认证信息"
#: assets/templates/assets/_asset_user_auth_update_modal.html:23
#: xpack/plugins/change_auth_plan/forms.py:10
1
#: xpack/plugins/change_auth_plan/forms.py:10
2
msgid "Please input password"
msgstr "请输入密码"
...
...
@@ -1490,19 +1483,19 @@ msgstr "重命名节点"
msgid "Delete node"
msgstr "删除节点"
#: assets/templates/assets/_node_tree.html:15
5
#: assets/templates/assets/_node_tree.html:15
4
msgid "Create node failed"
msgstr "创建节点失败"
#: assets/templates/assets/_node_tree.html:16
7
#: assets/templates/assets/_node_tree.html:16
6
msgid "Have child node, cancel"
msgstr "存在子节点,不能删除"
#: assets/templates/assets/_node_tree.html:16
9
#: assets/templates/assets/_node_tree.html:16
8
msgid "Have assets, cancel"
msgstr "存在资产,不能删除"
#: assets/templates/assets/_node_tree.html:24
3
#: assets/templates/assets/_node_tree.html:24
2
msgid "Rename success"
msgstr "重命名成功"
...
...
@@ -1582,14 +1575,14 @@ msgstr "替换资产的管理员"
#: assets/templates/assets/admin_user_detail.html:91
#: perms/templates/perms/asset_permission_asset.html:116
#: xpack/plugins/change_auth_plan/forms.py:1
09
#: xpack/plugins/change_auth_plan/forms.py:1
10
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_asset_list.html:112
msgid "Select nodes"
msgstr "选择节点"
#: assets/templates/assets/admin_user_detail.html:100
#: assets/templates/assets/asset_detail.html:207
#: assets/templates/assets/asset_list.html:39
6
#: assets/templates/assets/asset_list.html:39
5
#: assets/templates/assets/cmd_filter_detail.html:106
#: assets/templates/assets/system_user_assets.html:100
#: assets/templates/assets/system_user_detail.html:182
...
...
@@ -1611,24 +1604,24 @@ msgstr "选择节点"
msgid "Confirm"
msgstr "确认"
#: assets/templates/assets/admin_user_list.html:
7
#: assets/templates/assets/admin_user_list.html:
5
msgid ""
"Admin users are asset (charged server) on the root, or have NOPASSWD: ALL "
"sudo permissions users, "
msgstr ""
"管理用户是资产(被控服务器)上的root,或拥有 NOPASSWD: ALL sudo权限的用户,"
#: assets/templates/assets/admin_user_list.html:
8
#: assets/templates/assets/admin_user_list.html:
6
msgid ""
"Jumpserver users of the system using the user to `push system user`, `get "
"assets hardware information`, etc. "
msgstr "Jumpserver使用该用户来 `推送系统用户`、`获取资产硬件信息`等。"
#: assets/templates/assets/admin_user_list.html:
9
#: assets/templates/assets/admin_user_list.html:
7
msgid "You can set any one for Windows or other hardware."
msgstr "Windows或其它硬件可以随意设置一个"
#: assets/templates/assets/admin_user_list.html:1
9
#: assets/templates/assets/admin_user_list.html:1
7
#: assets/templates/assets/asset_list.html:68
#: assets/templates/assets/system_user_list.html:23
#: audits/templates/audits/login_log_list.html:85
...
...
@@ -1638,7 +1631,7 @@ msgstr "Windows或其它硬件可以随意设置一个"
msgid "Export"
msgstr "导出"
#: assets/templates/assets/admin_user_list.html:2
4
#: assets/templates/assets/admin_user_list.html:2
2
#: assets/templates/assets/asset_list.html:73
#: assets/templates/assets/system_user_list.html:28
#: settings/templates/settings/_ldap_list_users_modal.html:100
...
...
@@ -1649,20 +1642,13 @@ msgstr "导出"
msgid "Import"
msgstr "导入"
#: assets/templates/assets/admin_user_list.html:3
9
#: assets/templates/assets/admin_user_list.html:3
7
#: assets/views/admin_user.py:50
msgid "Create admin user"
msgstr "创建管理用户"
#: assets/templates/assets/admin_user_list.html:52
#: assets/templates/assets/system_user_list.html:58
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
msgstr "比例"
#: assets/templates/assets/admin_user_list.html:165
#: assets/templates/assets/admin_user_list.html:196
#: assets/templates/assets/admin_user_list.html:163
#: assets/templates/assets/admin_user_list.html:194
#: assets/templates/assets/asset_list.html:268
#: assets/templates/assets/asset_list.html:305
#: assets/templates/assets/system_user_list.html:225
...
...
@@ -1807,7 +1793,7 @@ msgstr "仅显示当前节点资产"
msgid "Displays all child node assets"
msgstr "显示所有子节点资产"
#: assets/templates/assets/asset_list.html:3
90
#: assets/templates/assets/asset_list.html:3
89
#: assets/templates/assets/system_user_list.html:166
#: users/templates/users/user_detail.html:382
#: users/templates/users/user_detail.html:408
...
...
@@ -1818,11 +1804,11 @@ msgstr "显示所有子节点资产"
msgid "Are you sure?"
msgstr "你确认吗?"
#: assets/templates/assets/asset_list.html:39
1
#: assets/templates/assets/asset_list.html:39
0
msgid "This will delete the selected assets !!!"
msgstr "删除选择资产"
#: assets/templates/assets/asset_list.html:39
4
#: assets/templates/assets/asset_list.html:39
3
#: assets/templates/assets/system_user_list.html:170
#: settings/templates/settings/terminal_setting.html:166
#: users/templates/users/user_detail.html:386
...
...
@@ -1836,16 +1822,16 @@ msgstr "删除选择资产"
msgid "Cancel"
msgstr "取消"
#: assets/templates/assets/asset_list.html:40
7
#: assets/templates/assets/asset_list.html:40
6
msgid "Asset Deleted."
msgstr "已被删除"
#: assets/templates/assets/asset_list.html:40
8
#: assets/templates/assets/asset_list.html:41
2
#: assets/templates/assets/asset_list.html:40
7
#: assets/templates/assets/asset_list.html:41
1
msgid "Asset Delete"
msgstr "删除"
#: assets/templates/assets/asset_list.html:41
1
#: assets/templates/assets/asset_list.html:41
0
msgid "Asset Deleting failed."
msgstr "删除失败"
...
...
@@ -2650,7 +2636,7 @@ msgstr "不能包含特殊字符"
msgid "This field must be unique."
msgstr "字段必须唯一"
#: jumpserver/views.py:19
0
#: jumpserver/views.py:19
1
msgid ""
"<div>Luna is a separately deployed program, you need to deploy Luna, coco, "
"configure nginx for url distribution,</div> </div>If you see this page, "
...
...
@@ -2853,6 +2839,11 @@ msgstr "执行历史"
msgid "F/S/T"
msgstr "失败/成功/总"
#: ops/templates/ops/adhoc_history.html:54
#: ops/templates/ops/task_history.html:60
msgid "Ratio"
msgstr "比例"
#: ops/templates/ops/adhoc_history_detail.html:19 ops/views/adhoc.py:142
msgid "Run history detail"
msgstr "执行历史详情"
...
...
@@ -3005,6 +2996,14 @@ msgstr "命令执行"
msgid "Organization"
msgstr "组织"
#: perms/api/user_permission.py:206
msgid "ungrouped"
msgstr "未分组"
#: perms/api/user_permission.py:211
msgid "empty"
msgstr "空"
#: perms/forms/asset_permission.py:65 perms/forms/remote_app_permission.py:34
#: perms/models/asset_permission.py:102 perms/models/base.py:37
#: perms/templates/perms/asset_permission_list.html:47
...
...
@@ -3207,10 +3206,6 @@ msgstr "添加用户"
msgid "Add user group to this permission"
msgstr "添加用户组"
#: perms/utils/asset_permission.py:115
msgid "Empty"
msgstr "空"
#: perms/views/asset_permission.py:33 perms/views/asset_permission.py:64
#: perms/views/asset_permission.py:81 perms/views/asset_permission.py:98
#: perms/views/asset_permission.py:135 perms/views/asset_permission.py:168
...
...
@@ -5313,25 +5308,28 @@ msgid "Password length"
msgstr "密码长度"
#: xpack/plugins/change_auth_plan/forms.py:45
msgid "* For security, please do not change root user's password"
msgstr "* 为了安全,请不要更改root用户的密码"
#: xpack/plugins/change_auth_plan/models.py:213
#, fuzzy
#| msgid "For security, do not change {} user's password"
msgid "* For security, do not change {} user's password"
msgstr "* 为了安全,禁止更改 {} 用户的密码"
#: xpack/plugins/change_auth_plan/forms.py:5
4
#: xpack/plugins/change_auth_plan/forms.py:5
5
msgid "* Please enter custom password"
msgstr "* 请输入自定义密码"
#: xpack/plugins/change_auth_plan/forms.py:6
3
#: xpack/plugins/change_auth_plan/forms.py:6
4
msgid "* Please enter a valid crontab expression"
msgstr "* 请输入有效的 crontab 表达式"
#: xpack/plugins/change_auth_plan/forms.py:11
6
#: xpack/plugins/change_auth_plan/forms.py:11
7
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_create_update.html:60
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_detail.html:81
#: xpack/plugins/change_auth_plan/templates/change_auth_plan/plan_list.html:17
msgid "Periodic perform"
msgstr "定时执行"
#: xpack/plugins/change_auth_plan/forms.py:12
0
#: xpack/plugins/change_auth_plan/forms.py:12
1
msgid ""
"Tips: The username of the user on the asset to be modified. if the user "
"exists, change the password; If the user does not exist, create the user."
...
...
@@ -5339,11 +5337,11 @@ msgstr ""
"提示:用户名为将要修改的资产上的用户的用户名。如果用户存在,则修改密码;如果"
"用户不存在,则创建用户。"
#: xpack/plugins/change_auth_plan/forms.py:12
4
#: xpack/plugins/change_auth_plan/forms.py:12
5
msgid "Tips: (Units: hour)"
msgstr "提示:(单位: 时)"
#: xpack/plugins/change_auth_plan/forms.py:12
5
#: xpack/plugins/change_auth_plan/forms.py:12
6
msgid ""
"eg: Every Sunday 03:05 run <5 3 * * 0> <br> Tips: Using 5 digits linux "
"crontab expressions <min hour day month week> (<a href='https://tool.lu/"
...
...
@@ -5396,10 +5394,6 @@ msgstr "定期执行"
msgid "Password rules"
msgstr "密码规则"
#: xpack/plugins/change_auth_plan/models.py:213
msgid "For security, do not change {} user's password"
msgstr "* 为了安全,禁止更改 {} 用户的密码"
#: xpack/plugins/change_auth_plan/models.py:217
msgid "Assets is empty, please add the asset"
msgstr "资产为空,请添加资产"
...
...
@@ -5768,7 +5762,7 @@ msgid "Interface settings"
msgstr "界面设置"
#: xpack/plugins/interface/templates/interface/interface.html:15
#: xpack/plugins/interface/views.py:25
#: xpack/plugins/interface/views.py:2
4 xpack/plugins/interface/views.py:2
5
msgid "Interface setting"
msgstr "界面设置"
...
...
@@ -5791,26 +5785,22 @@ msgstr "恢复默认成功!"
msgid "Restore default failed."
msgstr "恢复默认失败!"
#: xpack/plugins/interface/views.py:24
msgid "Interface"
msgstr "界面"
#: xpack/plugins/interface/views.py:51
msgid "It is already in the default setting state!"
msgstr "当前已经是初始化状态!"
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:9
8
#: xpack/plugins/license/meta.py:11 xpack/plugins/license/models.py:9
4
#: xpack/plugins/license/templates/license/license_detail.html:50
#: xpack/plugins/license/templates/license/license_detail.html:55
#: xpack/plugins/license/views.py:32
msgid "License"
msgstr "许可证"
#: xpack/plugins/license/models.py:7
5
#: xpack/plugins/license/models.py:7
4
msgid "Standard edition"
msgstr "标准版"
#: xpack/plugins/license/models.py:7
7
#: xpack/plugins/license/models.py:7
6
msgid "Enterprise edition"
msgstr "企业版"
...
...
@@ -5898,7 +5888,9 @@ msgstr "无效的许可证"
msgid "Admin"
msgstr "管理员"
#: xpack/plugins/orgs/meta.py:8
#: xpack/plugins/orgs/meta.py:8 xpack/plugins/orgs/views.py:26
#: xpack/plugins/orgs/views.py:43 xpack/plugins/orgs/views.py:60
#: xpack/plugins/orgs/views.py:77
msgid "Organizations"
msgstr "组织管理"
...
...
@@ -5915,11 +5907,6 @@ msgstr "添加管理员"
msgid "Create organization "
msgstr "创建组织"
#: xpack/plugins/orgs/views.py:26 xpack/plugins/orgs/views.py:43
#: xpack/plugins/orgs/views.py:60 xpack/plugins/orgs/views.py:77
msgid "Orgs"
msgstr "组织管理"
#: xpack/plugins/orgs/views.py:27
msgid "Org list"
msgstr "组织列表"
...
...
@@ -5949,6 +5936,15 @@ msgstr "密码匣子"
msgid "vault create"
msgstr "创建"
#~ msgid "* For security, please do not change root user's password"
#~ msgstr "* 为了安全,请不要更改root用户的密码"
#~ msgid "Interface"
#~ msgstr "界面"
#~ msgid "Orgs"
#~ msgstr "组织管理"
#~ msgid "Org"
#~ msgstr "组织"
...
...
apps/perms/api/remote_app_permission.py
View file @
21ffa8b2
...
...
@@ -99,3 +99,4 @@ class RemoteAppPermissionRemoveRemoteAppApi(generics.RetrieveUpdateAPIView):
else
:
return
Response
({
"error"
:
serializer
.
errors
})
apps/perms/api/user_group_permission.py
View file @
21ffa8b2
...
...
@@ -7,148 +7,57 @@ from rest_framework.generics import (
)
from
common.permissions
import
IsOrgAdmin
,
IsOrgAdminOrAppUser
from
common.tree
import
TreeNodeSerializer
from
..utils
import
(
AssetPermissionUtil
,
parse_asset_to_tree_node
,
parse_node_to_tree_node
,
RemoteAppPermissionUtil
,
)
from
..hands
import
(
UserGroup
,
Node
,
NodeSerializer
,
RemoteAppSerializer
,
)
from
..hands
import
UserGroup
from
..
import
serializers
,
const
from
.user_permission
import
(
UserGrantedAssetsApi
,
UserGrantedNodesApi
,
UserGrantedNodesWithAssetsApi
,
UserGrantedNodesWithAssetsAsTreeApi
,
UserGrantedNodeAssetsApi
,
)
__all__
=
[
'UserGroupGrantedAssetsApi'
,
'UserGroupGrantedNodesApi'
,
'UserGroupGrantedNodesWithAssetsApi'
,
'UserGroupGrantedNodeAssetsApi'
,
'UserGroupGrantedNodesWithAssetsAsTreeApi'
,
'UserGroupGrantedRemoteAppsApi'
,
]
class
UserGroupGrantedAssetsApi
(
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
AssetGrantedSerializer
def
get_queryset
(
self
):
class
UserGroupGrantedAssetsApi
(
UserGrantedAssetsApi
):
def
get_object
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
queryset
=
[]
if
not
user_group_id
:
return
queryset
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
AssetPermissionUtil
(
user_group
)
assets
=
util
.
get_assets
()
for
k
,
v
in
assets
.
items
():
k
.
system_users_granted
=
v
queryset
.
append
(
k
)
return
queryset
return
user_group
class
UserGroupGrantedNodesApi
(
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
NodeSerializer
def
get_queryset
(
self
):
group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
queryset
=
[]
if
group_id
:
group
=
get_object_or_404
(
UserGroup
,
id
=
group_id
)
util
=
AssetPermissionUtil
(
group
)
nodes
=
util
.
get_nodes_with_assets
()
return
nodes
.
keys
()
return
queryset
class
UserGroupGrantedNodesApi
(
UserGrantedNodesApi
):
def
get_object
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
return
user_group
class
UserGroupGrantedNodesWithAssetsApi
(
ListAPIView
):
class
UserGroupGrantedNodesWithAssetsApi
(
UserGrantedNodesWithAssetsApi
):
permission_classes
=
(
IsOrgAdmin
,)
serializer_class
=
serializers
.
NodeGrantedSerializer
def
get_
queryse
t
(
self
):
def
get_
objec
t
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
queryset
=
[]
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
return
user_group
if
not
user_group_id
:
return
queryset
class
UserGroupGrantedNodesWithAssetsAsTreeApi
(
UserGrantedNodesWithAssetsAsTreeApi
):
def
get_object
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
AssetPermissionUtil
(
user_group
)
nodes
=
util
.
get_nodes_with_assets
()
for
node
,
_assets
in
nodes
.
items
():
assets
=
_assets
.
keys
()
for
asset
,
system_users
in
_assets
.
items
():
asset
.
system_users_granted
=
system_users
node
.
assets_granted
=
assets
queryset
.
append
(
node
)
return
queryset
class
UserGroupGrantedNodesWithAssetsAsTreeApi
(
ListAPIView
):
serializer_class
=
TreeNodeSerializer
permission_classes
=
(
IsOrgAdminOrAppUser
,)
show_assets
=
True
system_user_id
=
None
return
user_group
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
show_assets
=
request
.
query_params
.
get
(
'show_assets'
,
'1'
)
==
'1'
self
.
system_user_id
=
request
.
query_params
.
get
(
'system_user'
)
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_queryset
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
queryset
=
[]
group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
AssetPermissionUtil
(
group
)
if
self
.
system_user_id
:
util
.
filter_permissions
(
system_users
=
self
.
system_user_id
)
nodes
=
util
.
get_nodes_with_assets
()
for
node
,
assets
in
nodes
.
items
():
data
=
parse_node_to_tree_node
(
node
)
queryset
.
append
(
data
)
if
not
self
.
show_assets
:
continue
for
asset
,
system_users
in
assets
.
items
():
data
=
parse_asset_to_tree_node
(
node
,
asset
,
system_users
)
queryset
.
append
(
data
)
queryset
=
sorted
(
queryset
)
return
queryset
class
UserGroupGrantedNodeAssetsApi
(
ListAPIView
):
class
UserGroupGrantedNodeAssetsApi
(
UserGrantedNodeAssetsApi
):
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
serializers
.
AssetGrantedSerializer
def
get_
queryse
t
(
self
):
def
get_
objec
t
(
self
):
user_group_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
node_id
=
self
.
kwargs
.
get
(
'node_id'
)
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
AssetPermissionUtil
(
user_group
)
if
str
(
node_id
)
==
const
.
UNGROUPED_NODE_ID
:
node
=
util
.
tree
.
ungrouped_node
else
:
node
=
get_object_or_404
(
Node
,
id
=
node_id
)
nodes
=
util
.
get_nodes_with_assets
()
assets
=
nodes
.
get
(
node
,
[])
for
asset
,
system_users
in
assets
.
items
():
asset
.
system_users_granted
=
system_users
return
assets
# RemoteApp permission
class
UserGroupGrantedRemoteAppsApi
(
ListAPIView
):
permission_classes
=
(
IsOrgAdmin
,
)
serializer_class
=
RemoteAppSerializer
def
get_queryset
(
self
):
queryset
=
[]
user_group_id
=
self
.
kwargs
.
get
(
'pk'
)
if
not
user_group_id
:
return
queryset
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
RemoteAppPermissionUtil
(
user_group
)
queryset
=
util
.
get_remote_apps
()
return
queryset
return
user_group
apps/perms/api/user_permission.py
View file @
21ffa8b2
...
...
@@ -2,40 +2,44 @@
#
import
time
import
traceback
import
uuid
from
hashlib
import
md5
from
django.core.cache
import
cache
from
django.conf
import
settings
from
django.db.models
import
Q
from
django.shortcuts
import
get_object_or_404
from
rest_framework.views
import
APIView
,
Response
from
rest_framework.generics
import
(
ListAPIView
,
get_object_or_404
,
RetrieveAPIView
)
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.pagination
import
LimitOffsetPagination
from
common.permissions
import
IsValidUser
,
IsOrgAdminOrAppUser
from
common.tree
import
TreeNodeSerializer
from
common.utils
import
get_logger
from
common.utils
import
get_logger
,
get_object_or_none
from
..utils
import
(
AssetPermissionUtil
,
parse_asset_to_tree_node
,
parse_node_to_tree_n
ode
,
AssetPermissionUtil
,
ParserN
ode
,
)
from
..
import
const
from
..hands
import
User
,
Asset
,
Node
,
SystemUser
,
NodeSerializer
from
..
import
serializers
,
const
from
..mixins
import
AssetsFilterMixin
from
..
import
serializers
from
..models
import
Action
logger
=
get_logger
(
__name__
)
__all__
=
[
'UserGrantedAssetsApi'
,
'UserGrantedNodesApi'
,
'UserGrantedNodesWithAssetsApi'
,
'UserGrantedNodeAssetsApi'
,
'ValidateUserAssetPermissionApi'
,
'UserGrantedNode
Children
Api'
,
'ValidateUserAssetPermissionApi'
,
'UserGrantedNode
sAsTree
Api'
,
'UserGrantedNodesWithAssetsAsTreeApi'
,
'GetUserAssetPermissionActionsApi'
,
]
class
UserPermissionCacheMixin
:
cache_policy
=
'0'
RESP_CACHE_KEY
=
'_PERMISSION_RESPONSE_CACHE_{}'
RESP_CACHE_KEY
=
'_PERMISSION_RESPONSE_CACHE_
V2_
{}'
CACHE_TIME
=
settings
.
ASSETS_PERM_CACHE_TIME
_object
=
None
...
...
@@ -130,12 +134,61 @@ class UserPermissionCacheMixin:
return
resp
class
UserGrantedAssetsApi
(
UserPermissionCacheMixin
,
AssetsFilterMixin
,
ListAPIView
):
class
GrantAssetsMixin
:
serializer_class
=
serializers
.
AssetGrantedSerializer
def
get_serializer
(
self
,
queryset
,
many
=
True
):
assets_ids
=
[]
system_users_ids
=
set
()
for
asset
in
queryset
:
assets_ids
.
append
(
asset
[
"id"
])
system_users_ids
.
update
(
set
(
asset
[
"system_users"
]))
assets
=
Asset
.
objects
.
filter
(
id__in
=
assets_ids
)
.
only
(
*
self
.
serializer_class
.
Meta
.
only_fields
)
assets_map
=
{
asset
.
id
:
asset
for
asset
in
assets
}
system_users
=
SystemUser
.
objects
.
filter
(
id__in
=
system_users_ids
)
.
only
(
*
self
.
serializer_class
.
system_users_only_fields
)
system_users_map
=
{
s
.
id
:
s
for
s
in
system_users
}
data
=
[]
for
item
in
queryset
:
i
=
item
[
"id"
]
asset
=
assets_map
.
get
(
i
)
if
not
asset
:
continue
_system_users
=
item
[
"system_users"
]
system_users_granted
=
[]
for
sid
,
action
in
_system_users
.
items
():
system_user
=
system_users_map
.
get
(
sid
)
if
not
system_user
:
continue
system_user
.
actions
=
action
system_users_granted
.
append
(
system_user
)
asset
.
system_users_granted
=
system_users_granted
data
.
append
(
asset
)
return
super
()
.
get_serializer
(
data
,
many
=
True
)
def
search_queryset
(
self
,
assets
):
search
=
self
.
request
.
query_params
.
get
(
"search"
)
if
not
search
:
return
assets
assets_map
=
{
asset
[
'id'
]:
asset
for
asset
in
assets
}
assets_ids
=
set
(
assets_map
.
keys
())
assets_ids_search
=
Asset
.
objects
.
filter
(
id__in
=
assets_ids
)
.
filter
(
Q
(
hostname__icontains
=
search
)
|
Q
(
ip__icontains
=
search
)
)
.
values_list
(
'id'
,
flat
=
True
)
assets_ids
&=
set
(
assets_ids_search
)
return
[
assets_map
.
get
(
asset_id
)
for
asset_id
in
assets_ids
]
class
UserGrantedAssetsApi
(
UserPermissionCacheMixin
,
GrantAssetsMixin
,
ListAPIView
):
"""
用户授权的所有资产
"""
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
serializers
.
AssetGrantedSerializer
pagination_class
=
LimitOffsetPagination
def
get_object
(
self
):
...
...
@@ -147,17 +200,10 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
return
user
def
get_queryset
(
self
):
queryset
=
[]
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
assets
=
util
.
get_assets
()
for
asset
,
system_users
in
assets
.
items
():
system_users_granted
=
[]
for
system_user
,
actions
in
system_users
.
items
():
system_user
.
actions
=
actions
system_users_granted
.
append
(
system_user
)
asset
.
system_users_granted
=
system_users_granted
queryset
.
append
(
asset
)
queryset
=
util
.
get_assets
()
queryset
=
self
.
search_queryset
(
queryset
)
return
queryset
def
get_permissions
(
self
):
...
...
@@ -166,12 +212,39 @@ class UserGrantedAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, ListAPIV
return
super
()
.
get_permissions
()
class
UserGrantedNodesApi
(
UserPermissionCacheMixin
,
ListAPIView
):
class
NodesWithUngroupMixin
:
util
=
None
@staticmethod
def
get_ungrouped_node
(
ungroup_key
):
return
Node
(
key
=
ungroup_key
,
id
=
const
.
UNGROUPED_NODE_ID
,
value
=
_
(
"ungrouped"
))
@staticmethod
def
get_empty_node
():
return
Node
(
key
=
const
.
EMPTY_NODE_KEY
,
id
=
const
.
EMPTY_NODE_ID
,
value
=
_
(
"empty"
))
def
add_ungrouped_nodes
(
self
,
node_map
,
node_keys
):
ungroup_key
=
'1:-1'
for
key
in
node_keys
:
if
key
.
endswith
(
'-1'
):
ungroup_key
=
key
break
ungroup_node
=
self
.
get_ungrouped_node
(
ungroup_key
)
empty_node
=
self
.
get_empty_node
()
node_map
[
ungroup_key
]
=
ungroup_node
node_map
[
const
.
EMPTY_NODE_KEY
]
=
empty_node
class
UserGrantedNodesApi
(
UserPermissionCacheMixin
,
NodesWithUngroupMixin
,
ListAPIView
):
"""
查询用户授权的所有节点的API
"""
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
NodeSerializer
pagination_class
=
LimitOffsetPagination
only_fields
=
NodeSerializer
.
Meta
.
only_fields
def
get_object
(
self
):
user_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
...
...
@@ -181,11 +254,31 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
user
=
self
.
request
.
user
return
user
def
get_nodes
(
self
,
nodes_with_assets
):
node_keys
=
[
n
[
"key"
]
for
n
in
nodes_with_assets
]
nodes
=
Node
.
objects
.
filter
(
key__in
=
node_keys
)
.
only
(
*
self
.
only_fields
)
nodes_map
=
{
n
.
key
:
n
for
n
in
nodes
}
self
.
add_ungrouped_nodes
(
nodes_map
,
node_keys
)
_nodes
=
[]
for
n
in
nodes_with_assets
:
key
=
n
[
"key"
]
node
=
nodes_map
.
get
(
key
)
node
.
_assets_amount
=
n
[
"assets_amount"
]
_nodes
.
append
(
node
)
return
_nodes
def
get_serializer
(
self
,
nodes_with_assets
,
many
=
True
):
nodes
=
self
.
get_nodes
(
nodes_with_assets
)
return
super
()
.
get_serializer
(
nodes
,
many
=
True
)
def
get_queryset
(
self
):
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
nodes
=
util
.
get_node
s
()
return
nodes
self
.
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
nodes
_with_assets
=
self
.
util
.
get_nodes_with_asset
s
()
return
nodes
_with_assets
def
get_permissions
(
self
):
if
self
.
kwargs
.
get
(
'pk'
)
is
None
:
...
...
@@ -193,12 +286,30 @@ class UserGrantedNodesApi(UserPermissionCacheMixin, ListAPIView):
return
super
()
.
get_permissions
()
class
UserGrantedNodesWithAssetsApi
(
UserPermissionCacheMixin
,
AssetsFilterMixin
,
ListAPIView
):
class
UserGrantedNodesAsTreeApi
(
UserGrantedNodesApi
):
serializer_class
=
TreeNodeSerializer
only_fields
=
ParserNode
.
nodes_only_fields
def
get_serializer
(
self
,
nodes_with_assets
,
many
=
True
):
nodes
=
self
.
get_nodes
(
nodes_with_assets
)
queryset
=
[]
for
node
in
nodes
:
data
=
ParserNode
.
parse_node_to_tree_node
(
node
)
queryset
.
append
(
data
)
return
self
.
get_serializer_class
()(
queryset
,
many
=
many
)
class
UserGrantedNodesWithAssetsApi
(
UserPermissionCacheMixin
,
NodesWithUngroupMixin
,
ListAPIView
):
"""
用户授权的节点并带着节点下资产的api
"""
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
serializers
.
NodeGrantedSerializer
pagination_class
=
LimitOffsetPagination
nodes_only_fields
=
serializers
.
NodeGrantedSerializer
.
Meta
.
only_fields
assets_only_fields
=
serializers
.
NodeGrantedSerializer
.
assets_only_fields
system_users_only_fields
=
serializers
.
NodeGrantedSerializer
.
system_users_only_fields
def
get_object
(
self
):
user_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
...
...
@@ -208,23 +319,92 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
user
=
get_object_or_404
(
User
,
id
=
user_id
)
return
user
def
get_queryset
(
self
):
def
get_maps
(
self
,
nodes_items
):
"""
查库,并加入构造的ungrouped节点
:return:
({asset.id: asset}, {node.key: node}, {system_user.id: system_user})
"""
_nodes_keys
=
set
()
_assets_ids
=
set
()
_system_users_ids
=
set
()
for
item
in
nodes_items
:
_nodes_keys
.
add
(
item
[
"key"
])
_assets_ids
.
update
(
set
(
item
[
"assets"
]
.
keys
()))
for
_system_users_id
in
item
[
"assets"
]
.
values
():
_system_users_ids
.
update
(
_system_users_id
.
keys
())
_nodes
=
Node
.
objects
.
filter
(
key__in
=
_nodes_keys
)
.
only
(
*
self
.
nodes_only_fields
)
_assets
=
Asset
.
objects
.
filter
(
id__in
=
_assets_ids
)
.
only
(
*
self
.
assets_only_fields
)
_system_users
=
SystemUser
.
objects
.
filter
(
id__in
=
_system_users_ids
)
.
only
(
*
self
.
system_users_only_fields
)
_nodes_map
=
{
n
.
key
:
n
for
n
in
_nodes
}
self
.
add_ungrouped_nodes
(
_nodes_map
,
_nodes_keys
)
_assets_map
=
{
a
.
id
:
a
for
a
in
_assets
}
_system_users_map
=
{
s
.
id
:
s
for
s
in
_system_users
}
return
_nodes_map
,
_assets_map
,
_system_users_map
def
get_serializer_queryset
(
self
,
nodes_items
):
"""
将id转为object,同时构造queryset
:param nodes_items:
[
{
'key': node.key,
'assets_amount': 10
'assets': {
asset.id: {
system_user.id: actions,
},
},
},
]
"""
queryset
=
[]
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
nodes
=
util
.
get_nodes_with_assets
()
for
node
,
_assets
in
nodes
.
items
():
assets
=
_assets
.
keys
()
for
k
,
v
in
_assets
.
items
():
k
.
system_users_granted
=
v
node
.
assets_granted
=
assets
_node_map
,
_assets_map
,
_system_users_map
=
self
.
get_maps
(
nodes_items
)
for
item
in
nodes_items
:
key
=
item
[
"key"
]
node
=
_node_map
.
get
(
key
)
if
not
node
:
continue
node
.
_assets_amount
=
item
[
"assets_amount"
]
assets_granted
=
[]
for
asset_id
,
system_users_ids_action
in
item
[
"assets"
]
.
items
():
asset
=
_assets_map
.
get
(
asset_id
)
if
not
asset
:
continue
system_user_granted
=
[]
for
system_user_id
,
action
in
system_users_ids_action
.
items
():
system_user
=
_system_users_map
.
get
(
system_user_id
)
if
not
system_user
:
continue
system_user
.
actions
=
action
system_user_granted
.
append
(
system_user
)
asset
.
system_users_granted
=
system_user_granted
assets_granted
.
append
(
asset
)
node
.
assets_granted
=
assets_granted
queryset
.
append
(
node
)
return
queryset
def
sort_assets
(
self
,
queryset
):
for
node
in
queryset
:
node
.
assets_granted
=
super
()
.
sort_assets
(
node
.
assets_granted
)
return
queryset
def
get_serializer
(
self
,
nodes_items
,
many
=
True
):
queryset
=
self
.
get_serializer_queryset
(
nodes_items
)
return
super
()
.
get_serializer
(
queryset
,
many
=
many
)
def
get_queryset
(
self
):
user
=
self
.
get_object
()
self
.
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
system_user_id
=
self
.
request
.
query_params
.
get
(
'system_user'
)
if
system_user_id
:
self
.
util
.
filter_permissions
(
system_users
=
system_user_id
)
nodes_items
=
self
.
util
.
get_nodes_with_assets
()
return
nodes_items
def
get_permissions
(
self
):
if
self
.
kwargs
.
get
(
'pk'
)
is
None
:
...
...
@@ -232,54 +412,34 @@ class UserGrantedNodesWithAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin,
return
super
()
.
get_permissions
()
class
UserGrantedNodesWithAssetsAsTreeApi
(
User
PermissionCacheMixin
,
ListAPIView
):
class
UserGrantedNodesWithAssetsAsTreeApi
(
User
GrantedNodesWithAssetsApi
):
serializer_class
=
TreeNodeSerializer
permission_classes
=
(
IsOrgAdminOrAppUser
,)
show_assets
=
True
system_user_id
=
None
nodes_only_fields
=
ParserNode
.
nodes_only_fields
assets_only_fields
=
ParserNode
.
assets_only_fields
system_users_only_fields
=
ParserNode
.
system_users_only_fields
def
get_permissions
(
self
):
if
self
.
kwargs
.
get
(
'pk'
)
is
None
:
self
.
permission_classes
=
(
IsValidUser
,)
return
super
()
.
get_permissions
()
def
get_object
(
self
):
user_id
=
self
.
kwargs
.
get
(
'pk'
,
''
)
if
not
user_id
:
user
=
self
.
request
.
user
else
:
user
=
get_object_or_404
(
User
,
id
=
user_id
)
return
user
def
get_queryset
(
self
):
def
get_serializer
(
self
,
nodes_items
,
many
=
True
):
_queryset
=
super
()
.
get_serializer_queryset
(
nodes_items
)
queryset
=
[]
self
.
show_assets
=
self
.
request
.
query_params
.
get
(
'show_assets'
,
'1'
)
==
'1'
self
.
system_user_id
=
self
.
request
.
query_params
.
get
(
'system_user'
)
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
if
self
.
system_user_id
:
util
.
filter_permissions
(
system_users
=
self
.
system_user_id
)
nodes
=
util
.
get_nodes_with_assets
()
for
node
,
assets
in
nodes
.
items
():
data
=
parse_node_to_tree_node
(
node
)
for
node
in
_queryset
:
data
=
ParserNode
.
parse_node_to_tree_node
(
node
)
queryset
.
append
(
data
)
if
not
self
.
show_assets
:
continue
for
asset
,
system_users
in
assets
.
items
():
data
=
parse_asset_to_tree_node
(
node
,
asset
,
system_users
)
for
asset
in
node
.
assets_granted
:
system_users
=
asset
.
system_users_granted
data
=
ParserNode
.
parse_asset_to_tree_node
(
node
,
asset
,
system_users
)
queryset
.
append
(
data
)
queryset
=
sorted
(
queryset
)
return
queryset
return
self
.
serializer_class
(
queryset
,
many
=
True
)
class
UserGrantedNodeAssetsApi
(
UserPermissionCacheMixin
,
AssetsFilter
Mixin
,
ListAPIView
):
class
UserGrantedNodeAssetsApi
(
UserPermissionCacheMixin
,
GrantAssets
Mixin
,
ListAPIView
):
"""
查询用户授权的节点下的资产的api, 与上面api不同的是,只返回某个节点下的资产
"""
permission_classes
=
(
IsOrgAdminOrAppUser
,)
serializer_class
=
serializers
.
AssetGrantedSerializer
pagination_class
=
LimitOffsetPagination
def
get_object
(
self
):
...
...
@@ -291,25 +451,31 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
user
=
self
.
request
.
user
return
user
def
get_queryset
(
self
):
user
=
self
.
get_object
()
def
get_node_key
(
self
):
node_id
=
self
.
kwargs
.
get
(
'node_id'
)
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
nodes
=
util
.
get_nodes_with_assets
()
if
str
(
node_id
)
==
const
.
UNGROUPED_NODE_ID
:
node
=
util
.
tree
.
ungrouped_node
key
=
self
.
util
.
tree
.
ungrouped_key
elif
str
(
node_id
)
==
const
.
EMPTY_NODE_ID
:
node
=
util
.
tree
.
empty_node
key
=
const
.
EMPTY_NODE_KEY
else
:
node
=
get_object_or_404
(
Node
,
id
=
node_id
)
if
node
==
util
.
tree
.
root_node
:
assets
=
util
.
get_assets
()
else
:
assets
=
nodes
.
get
(
node
,
{})
for
asset
,
system_users
in
assets
.
items
():
asset
.
system_users_granted
=
system_users
key
=
node
.
key
return
key
assets
=
list
(
assets
.
keys
())
def
get_queryset
(
self
):
user
=
self
.
get_object
()
self
.
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
key
=
self
.
get_node_key
()
nodes_items
=
self
.
util
.
get_nodes_with_assets
()
assets_system_users
=
{}
for
item
in
nodes_items
:
if
item
[
"key"
]
==
key
:
assets_system_users
=
item
[
"assets"
]
break
assets
=
[]
for
asset_id
,
system_users
in
assets_system_users
.
items
():
assets
.
append
({
"id"
:
asset_id
,
"system_users"
:
system_users
})
assets
=
self
.
search_queryset
(
assets
)
return
assets
def
get_permissions
(
self
):
...
...
@@ -318,92 +484,6 @@ class UserGrantedNodeAssetsApi(UserPermissionCacheMixin, AssetsFilterMixin, List
return
super
()
.
get_permissions
()
class
UserGrantedNodeChildrenApi
(
UserPermissionCacheMixin
,
ListAPIView
):
"""
获取用户自己授权节点下子节点的api
"""
permission_classes
=
(
IsValidUser
,)
serializer_class
=
serializers
.
AssetPermissionNodeSerializer
def
get_object
(
self
):
return
self
.
request
.
user
def
get_children_queryset
(
self
):
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
node_id
=
self
.
request
.
query_params
.
get
(
'id'
)
nodes_granted
=
util
.
get_nodes_with_assets
()
if
not
nodes_granted
:
return
[]
root_nodes
=
[
node
for
node
in
nodes_granted
.
keys
()
if
node
.
is_root
()]
queryset
=
[]
if
node_id
and
node_id
in
[
str
(
node
.
id
)
for
node
in
nodes_granted
]:
node
=
[
node
for
node
in
nodes_granted
if
str
(
node
.
id
)
==
node_id
][
0
]
elif
len
(
root_nodes
)
==
1
:
node
=
root_nodes
[
0
]
node
.
assets_amount
=
len
(
nodes_granted
[
node
])
queryset
.
append
(
node
)
else
:
for
node
in
root_nodes
:
node
.
assets_amount
=
len
(
nodes_granted
[
node
])
queryset
.
append
(
node
)
return
queryset
children
=
[]
for
child
in
node
.
get_children
():
if
child
in
nodes_granted
:
child
.
assets_amount
=
len
(
nodes_granted
[
node
])
children
.
append
(
child
)
children
=
sorted
(
children
,
key
=
lambda
x
:
x
.
value
)
queryset
.
extend
(
children
)
fake_nodes
=
[]
for
asset
,
system_users
in
nodes_granted
[
node
]
.
items
():
fake_node
=
asset
.
as_node
()
fake_node
.
assets_amount
=
0
system_users
=
[
s
for
s
in
system_users
if
asset
.
has_protocol
(
s
.
protocol
)]
fake_node
.
asset
.
system_users_granted
=
system_users
fake_node
.
key
=
node
.
key
+
':0'
fake_nodes
.
append
(
fake_node
)
fake_nodes
=
sorted
(
fake_nodes
,
key
=
lambda
x
:
x
.
value
)
queryset
.
extend
(
fake_nodes
)
return
queryset
def
get_search_queryset
(
self
,
keyword
):
user
=
self
.
get_object
()
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
nodes_granted
=
util
.
get_nodes_with_assets
()
queryset
=
[]
for
node
,
assets
in
nodes_granted
.
items
():
matched_assets
=
[]
node_matched
=
node
.
value
.
lower
()
.
find
(
keyword
.
lower
())
>=
0
asset_has_matched
=
False
for
asset
,
system_users
in
assets
.
items
():
asset_matched
=
(
asset
.
hostname
.
lower
()
.
find
(
keyword
.
lower
())
>=
0
)
\
or
(
asset
.
ip
.
find
(
keyword
.
lower
())
>=
0
)
if
node_matched
or
asset_matched
:
asset_has_matched
=
True
fake_node
=
asset
.
as_node
()
fake_node
.
assets_amount
=
0
system_users
=
[
s
for
s
in
system_users
if
asset
.
has_protocol
(
s
.
protocol
)]
fake_node
.
asset
.
system_users_granted
=
system_users
fake_node
.
key
=
node
.
key
+
':0'
matched_assets
.
append
(
fake_node
)
if
asset_has_matched
:
node
.
assets_amount
=
len
(
matched_assets
)
queryset
.
append
(
node
)
queryset
.
extend
(
sorted
(
matched_assets
,
key
=
lambda
x
:
x
.
value
))
return
queryset
def
get_queryset
(
self
):
keyword
=
self
.
request
.
query_params
.
get
(
'search'
)
if
keyword
:
return
self
.
get_search_queryset
(
keyword
)
else
:
return
self
.
get_children_queryset
()
class
ValidateUserAssetPermissionApi
(
UserPermissionCacheMixin
,
APIView
):
permission_classes
=
(
IsOrgAdminOrAppUser
,)
...
...
@@ -412,24 +492,24 @@ class ValidateUserAssetPermissionApi(UserPermissionCacheMixin, APIView):
asset_id
=
request
.
query_params
.
get
(
'asset_id'
,
''
)
system_id
=
request
.
query_params
.
get
(
'system_user_id'
,
''
)
action_name
=
request
.
query_params
.
get
(
'action_name'
,
''
)
cache_policy
=
self
.
request
.
query_params
.
get
(
"cache_policy"
,
'0'
)
user
=
get_object_or_404
(
User
,
id
=
user_id
)
asset
=
get_object_or_404
(
Asset
,
id
=
asset_id
)
su
=
get_object_or_404
(
SystemUser
,
id
=
system_id
)
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
self
.
cache_policy
)
granted_assets
=
util
.
get_assets
()
granted_system_users
=
granted_assets
.
get
(
asset
,
{})
if
su
not
in
granted_system_users
:
return
Response
({
'msg'
:
False
},
status
=
403
)
action
=
granted_system_users
[
su
]
choices
=
Action
.
value_to_choices
(
action
)
if
action_name
not
in
choices
:
try
:
asset_id
=
uuid
.
UUID
(
asset_id
)
system_id
=
uuid
.
UUID
(
system_id
)
except
ValueError
:
return
Response
({
'msg'
:
False
},
status
=
403
)
user
=
get_object_or_404
(
User
,
id
=
user_id
)
util
=
AssetPermissionUtil
(
user
,
cache_policy
=
cache_policy
)
assets
=
util
.
get_assets
()
for
asset
in
assets
:
if
asset_id
==
asset
[
"id"
]:
action
=
asset
[
"system_users"
]
.
get
(
system_id
)
if
action
and
action_name
in
Action
.
value_to_choices
(
action
):
return
Response
({
'msg'
:
True
},
status
=
200
)
break
return
Response
({
'msg'
:
False
},
status
=
403
)
class
GetUserAssetPermissionActionsApi
(
UserPermissionCacheMixin
,
RetrieveAPIView
):
...
...
apps/perms/api/user_remote_app_permission.py
View file @
21ffa8b2
...
...
@@ -13,13 +13,13 @@ from ..utils import (
RemoteAppPermissionUtil
,
construct_remote_apps_tree_root
,
parse_remote_app_to_tree_node
,
)
from
..hands
import
User
,
RemoteApp
,
RemoteAppSerializer
from
..hands
import
User
,
RemoteApp
,
RemoteAppSerializer
,
UserGroup
from
..mixins
import
RemoteAppFilterMixin
__all__
=
[
'UserGrantedRemoteAppsApi'
,
'ValidateUserRemoteAppPermissionApi'
,
'UserGrantedRemoteAppsAsTreeApi'
,
'UserGrantedRemoteAppsAsTreeApi'
,
'UserGroupGrantedRemoteAppsApi'
,
]
...
...
@@ -94,3 +94,20 @@ class ValidateUserRemoteAppPermissionApi(APIView):
if
remote_app
not
in
remote_apps
:
return
Response
({
'msg'
:
False
},
status
=
403
)
return
Response
({
'msg'
:
True
},
status
=
200
)
# RemoteApp permission
class
UserGroupGrantedRemoteAppsApi
(
ListAPIView
):
permission_classes
=
(
IsOrgAdminOrAppUser
,
)
serializer_class
=
RemoteAppSerializer
def
get_queryset
(
self
):
queryset
=
[]
user_group_id
=
self
.
kwargs
.
get
(
'pk'
)
if
not
user_group_id
:
return
queryset
user_group
=
get_object_or_404
(
UserGroup
,
id
=
user_group_id
)
util
=
RemoteAppPermissionUtil
(
user_group
)
queryset
=
util
.
get_remote_apps
()
return
queryset
apps/perms/const.py
View file @
21ffa8b2
...
...
@@ -3,3 +3,4 @@
UNGROUPED_NODE_ID
=
"00000000-0000-0000-0000-000000000002"
EMPTY_NODE_ID
=
"00000000-0000-0000-0000-000000000003"
EMPTY_NODE_KEY
=
"1:-2"
apps/perms/models/asset_permission.py
View file @
21ffa8b2
...
...
@@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from
common.utils
import
date_expired_default
,
set_or_append_attr_bulk
from
orgs.mixins
import
OrgModelMixin
from
assets.models
import
Asset
,
SystemUser
,
Node
from
.base
import
BasePermission
...
...
@@ -85,7 +86,11 @@ class AssetPermission(BasePermission):
@classmethod
def
get_queryset_with_prefetch
(
cls
):
return
cls
.
objects
.
all
()
.
valid
()
.
prefetch_related
(
'nodes'
,
'assets'
,
'system_users'
)
return
cls
.
objects
.
all
()
.
valid
()
.
prefetch_related
(
models
.
Prefetch
(
'nodes'
,
queryset
=
Node
.
objects
.
all
()
.
only
(
'key'
)),
models
.
Prefetch
(
'assets'
,
queryset
=
Asset
.
objects
.
all
()
.
only
(
'id'
)),
models
.
Prefetch
(
'system_users'
,
queryset
=
SystemUser
.
objects
.
all
()
.
only
(
'id'
))
)
def
get_all_assets
(
self
):
assets
=
set
(
self
.
assets
.
all
())
...
...
apps/perms/serializers/user_permission.py
View file @
21ffa8b2
...
...
@@ -2,16 +2,16 @@
#
from
rest_framework
import
serializers
from
django.utils.translation
import
ugettext_lazy
as
_
from
assets.models
import
Node
,
SystemUser
from
assets.serializers
import
AssetSerializer
from
assets.models
import
Node
,
SystemUser
,
Asset
from
assets.serializers
import
ProtocolsField
from
.asset_permission
import
ActionsField
__all__
=
[
'
AssetPermissionNodeSerializer'
,
'
GrantedNodeSerializer'
,
'GrantedNodeSerializer'
,
'NodeGrantedSerializer'
,
'AssetGrantedSerializer'
,
'ActionsSerializer'
,
'ActionsSerializer'
,
'AssetSystemUserSerializer'
,
]
...
...
@@ -23,58 +23,36 @@ class AssetSystemUserSerializer(serializers.ModelSerializer):
class
Meta
:
model
=
SystemUser
fields
=
(
'id'
,
'name'
,
'username'
,
'priority'
,
"actions"
,
only_
fields
=
(
'id'
,
'name'
,
'username'
,
'priority'
,
'protocol'
,
'login_mode'
,
)
fields
=
list
(
only_fields
)
+
[
"actions"
]
read_only_fields
=
fields
class
AssetGrantedSerializer
(
Asset
Serializer
):
class
AssetGrantedSerializer
(
serializers
.
Model
Serializer
):
"""
被授权资产的数据结构
"""
protocols
=
ProtocolsField
(
label
=
_
(
'Protocols'
),
required
=
False
,
read_only
=
True
)
system_users_granted
=
AssetSystemUserSerializer
(
many
=
True
,
read_only
=
True
)
system_users_join
=
serializers
.
SerializerMethodField
()
@staticmethod
def
get_system_users_join
(
obj
):
system_users
=
[
s
.
username
for
s
in
obj
.
system_users_granted
]
return
', '
.
join
(
system_users
)
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
(
"id"
,
"hostname"
,
"ip"
,
"protocols"
,
"system_users_granted"
,
"is_active"
,
"system_users_join"
,
"os"
,
'domain'
,
"platform"
,
"comment"
,
"org_id"
,
"org_name"
,
)
return
fields
class
AssetPermissionNodeSerializer
(
serializers
.
ModelSerializer
):
asset
=
AssetGrantedSerializer
(
required
=
False
)
assets_amount
=
serializers
.
SerializerMethodField
()
tree_id
=
serializers
.
SerializerMethodField
()
tree_parent
=
serializers
.
SerializerMethodField
()
system_users_only_fields
=
AssetSystemUserSerializer
.
Meta
.
only_fields
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'value'
,
'asset'
,
'is_node'
,
'org_id
'
,
'tree_id'
,
'tree_parent'
,
'assets_amount'
,
model
=
Asset
only_
fields
=
[
"id"
,
"hostname"
,
"ip"
,
"protocols"
,
"os"
,
'domain
'
,
"platform"
,
"org_id"
,
]
fields
=
only_fields
+
[
'system_users_granted'
,
'system_users_join'
,
"org_name"
]
read_only_fields
=
fields
@staticmethod
def
get_assets_amount
(
obj
):
return
obj
.
assets_amount
@staticmethod
def
get_tree_id
(
obj
):
return
obj
.
key
@staticmethod
def
get_tree_parent
(
obj
):
return
obj
.
parent_key
def
get_system_users_join
(
obj
):
system_users
=
[
s
.
username
for
s
in
obj
.
system_users_granted
]
return
', '
.
join
(
system_users
)
class
NodeGrantedSerializer
(
serializers
.
ModelSerializer
):
...
...
@@ -82,28 +60,19 @@ class NodeGrantedSerializer(serializers.ModelSerializer):
授权资产组
"""
assets_granted
=
AssetGrantedSerializer
(
many
=
True
,
read_only
=
True
)
assets_amount
=
serializers
.
SerializerMethodField
()
parent
=
serializers
.
SerializerMethodField
()
name
=
serializers
.
SerializerMethodField
()
assets_amount
=
serializers
.
ReadOnlyField
()
name
=
serializers
.
ReadOnlyField
(
source
=
'value'
)
assets_only_fields
=
AssetGrantedSerializer
.
Meta
.
only_fields
system_users_only_fields
=
AssetGrantedSerializer
.
system_users_only_fields
class
Meta
:
model
=
Node
fields
=
[
'id'
,
'key'
,
'name'
,
'value'
,
'parent'
,
'
assets_granted'
,
'assets_amount'
,
'org_id
'
,
only_fields
=
[
'id'
,
'key'
,
'value'
,
"org_id"
]
fields
=
only_fields
+
[
'
name'
,
'assets_granted'
,
'assets_amount
'
,
]
@staticmethod
def
get_assets_amount
(
obj
):
return
len
(
obj
.
assets_granted
)
@staticmethod
def
get_name
(
obj
):
return
obj
.
name
@staticmethod
def
get_parent
(
obj
):
return
obj
.
parent
.
id
read_only_fields
=
fields
class
GrantedNodeSerializer
(
serializers
.
ModelSerializer
):
...
...
@@ -112,6 +81,7 @@ class GrantedNodeSerializer(serializers.ModelSerializer):
fields
=
[
'id'
,
'name'
,
'key'
,
'value'
,
]
read_only_fields
=
fields
class
ActionsSerializer
(
serializers
.
Serializer
):
...
...
apps/perms/urls/api_urls.py
View file @
21ffa8b2
# coding:utf-8
from
django.urls
import
path
from
django.urls
import
path
,
re_path
from
rest_framework
import
routers
from
common
import
api
as
capi
from
..
import
api
app_name
=
'perms'
...
...
@@ -12,26 +13,36 @@ router.register('remote-app-permissions', api.RemoteAppPermissionViewSet, 'remot
asset_permission_urlpatterns
=
[
# 查询某个用户授权的资产和资产组
path
(
'user/<uuid:pk>/assets/'
,
api
.
UserGrantedAssetsApi
.
as_view
()),
# Assets
path
(
'users/<uuid:pk>/assets/'
,
api
.
UserGrantedAssetsApi
.
as_view
(),
name
=
'user-assets'
),
path
(
'user/assets/'
,
api
.
UserGrantedAssetsApi
.
as_view
(),
name
=
'my-assets'
),
path
(
'user/<uuid:pk>/nodes/'
,
api
.
UserGrantedNodesApi
.
as_view
(),
name
=
'user-nodes'
),
path
(
'user/nodes/'
,
api
.
UserGrantedNodesApi
.
as_view
(),
name
=
'my-nodes'
),
path
(
'user/nodes/children/'
,
api
.
UserGrantedNodeChildrenApi
.
as_view
(),
name
=
'my-node-children'
),
path
(
'user/<uuid:pk>/nodes/<uuid:node_id>/assets/'
,
api
.
UserGrantedNodeAssetsApi
.
as_view
(),
name
=
'user-node-assets'
),
path
(
'user/nodes/<uuid:node_id>/assets/'
,
api
.
UserGrantedNodeAssetsApi
.
as_view
(),
name
=
'my-node-assets'
),
path
(
'user/<uuid:pk>/nodes-assets/'
,
api
.
UserGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'user-nodes-assets'
),
path
(
'user/nodes-assets/'
,
api
.
UserGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'my-nodes-assets'
),
path
(
'user/<uuid:pk>/nodes-assets/tree/'
,
api
.
UserGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'user-nodes-assets-as-tree'
),
path
(
'user/nodes-assets/tree/'
,
api
.
UserGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'my-nodes-assets-as-tree'
),
path
(
'users/assets/'
,
api
.
UserGrantedAssetsApi
.
as_view
(),
name
=
'my-assets'
),
# Node as tree
path
(
'users/<uuid:pk>/nodes/tree/'
,
api
.
UserGrantedNodesAsTreeApi
.
as_view
(),
name
=
'user-nodes-as-tree'
),
path
(
'users/nodes/tree/'
,
api
.
UserGrantedNodesAsTreeApi
.
as_view
(),
name
=
'my-nodes-as-tree'
),
# Nodes
path
(
'users/<uuid:pk>/nodes/'
,
api
.
UserGrantedNodesApi
.
as_view
(),
name
=
'user-nodes'
),
path
(
'users/nodes/'
,
api
.
UserGrantedNodesApi
.
as_view
(),
name
=
'my-nodes'
),
# Node assets
path
(
'users/<uuid:pk>/nodes/<uuid:node_id>/assets/'
,
api
.
UserGrantedNodeAssetsApi
.
as_view
(),
name
=
'user-node-assets'
),
path
(
'users/nodes/<uuid:node_id>/assets/'
,
api
.
UserGrantedNodeAssetsApi
.
as_view
(),
name
=
'my-node-assets'
),
# Node with assets
path
(
'users/<uuid:pk>/nodes-assets/'
,
api
.
UserGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'user-nodes-assets'
),
path
(
'users/nodes-assets/'
,
api
.
UserGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'my-nodes-assets'
),
# Node assets as tree
path
(
'users/<uuid:pk>/nodes-assets/tree/'
,
api
.
UserGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'user-nodes-assets-as-tree'
),
path
(
'users/nodes-assets/tree/'
,
api
.
UserGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'my-nodes-assets-as-tree'
),
# 查询某个用户组授权的资产和资产组
path
(
'user-group/<uuid:pk>/assets/'
,
api
.
UserGroupGrantedAssetsApi
.
as_view
(),
name
=
'user-group-assets'
),
path
(
'user-group/<uuid:pk>/nodes/'
,
api
.
UserGroupGrantedNodesApi
.
as_view
(),
name
=
'user-group-nodes'
),
path
(
'user-group/<uuid:pk>/nodes-assets/'
,
api
.
UserGroupGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'user-group-nodes-assets'
),
path
(
'user-group/<uuid:pk>/nodes-assets/tree/'
,
api
.
UserGroupGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'user-group-nodes-assets-as-tree'
),
path
(
'user-group/<uuid:pk>/nodes/<uuid:node_id>/assets/'
,
api
.
UserGroupGrantedNodeAssetsApi
.
as_view
(),
name
=
'user-group-node-assets'
),
path
(
'user-group
s
/<uuid:pk>/assets/'
,
api
.
UserGroupGrantedAssetsApi
.
as_view
(),
name
=
'user-group-assets'
),
path
(
'user-group
s
/<uuid:pk>/nodes/'
,
api
.
UserGroupGrantedNodesApi
.
as_view
(),
name
=
'user-group-nodes'
),
path
(
'user-group
s
/<uuid:pk>/nodes-assets/'
,
api
.
UserGroupGrantedNodesWithAssetsApi
.
as_view
(),
name
=
'user-group-nodes-assets'
),
path
(
'user-group
s
/<uuid:pk>/nodes-assets/tree/'
,
api
.
UserGroupGrantedNodesWithAssetsAsTreeApi
.
as_view
(),
name
=
'user-group-nodes-assets-as-tree'
),
path
(
'user-group
s
/<uuid:pk>/nodes/<uuid:node_id>/assets/'
,
api
.
UserGroupGrantedNodeAssetsApi
.
as_view
(),
name
=
'user-group-node-assets'
),
# 用户和资产授权变更
path
(
'asset-permissions/<uuid:pk>/user/remove/'
,
api
.
AssetPermissionRemoveUserApi
.
as_view
(),
name
=
'asset-permission-remove-user'
),
...
...
@@ -40,25 +51,25 @@ asset_permission_urlpatterns = [
path
(
'asset-permissions/<uuid:pk>/asset/add/'
,
api
.
AssetPermissionAddAssetApi
.
as_view
(),
name
=
'asset-permission-add-asset'
),
# 验证用户是否有某个资产和系统用户的权限
path
(
'asset-permission/user/validate/'
,
api
.
ValidateUserAssetPermissionApi
.
as_view
(),
name
=
'validate-user-asset-permission'
),
path
(
'asset-permission/user/actions/'
,
api
.
GetUserAssetPermissionActionsApi
.
as_view
(),
name
=
'get-user-asset-permission-actions'
),
path
(
'asset-permission
s
/user/validate/'
,
api
.
ValidateUserAssetPermissionApi
.
as_view
(),
name
=
'validate-user-asset-permission'
),
path
(
'asset-permission
s
/user/actions/'
,
api
.
GetUserAssetPermissionActionsApi
.
as_view
(),
name
=
'get-user-asset-permission-actions'
),
]
remote_app_permission_urlpatterns
=
[
# 查询用户授权的RemoteApp
path
(
'user/<uuid:pk>/remote-apps/'
,
api
.
UserGrantedRemoteAppsApi
.
as_view
(),
name
=
'user-remote-apps'
),
path
(
'user/remote-apps/'
,
api
.
UserGrantedRemoteAppsApi
.
as_view
(),
name
=
'my-remote-apps'
),
path
(
'user
s
/<uuid:pk>/remote-apps/'
,
api
.
UserGrantedRemoteAppsApi
.
as_view
(),
name
=
'user-remote-apps'
),
path
(
'user
s
/remote-apps/'
,
api
.
UserGrantedRemoteAppsApi
.
as_view
(),
name
=
'my-remote-apps'
),
# 获取用户授权的RemoteApp树
path
(
'user/<uuid:pk>/remote-apps/tree/'
,
api
.
UserGrantedRemoteAppsAsTreeApi
.
as_view
(),
name
=
'user-remote-apps-as-tree'
),
path
(
'user/remote-apps/tree/'
,
api
.
UserGrantedRemoteAppsAsTreeApi
.
as_view
(),
name
=
'my-remote-apps-as-tree'
),
path
(
'user
s
/<uuid:pk>/remote-apps/tree/'
,
api
.
UserGrantedRemoteAppsAsTreeApi
.
as_view
(),
name
=
'user-remote-apps-as-tree'
),
path
(
'user
s
/remote-apps/tree/'
,
api
.
UserGrantedRemoteAppsAsTreeApi
.
as_view
(),
name
=
'my-remote-apps-as-tree'
),
# 查询用户组授权的RemoteApp
path
(
'user-group/<uuid:pk>/remote-apps/'
,
api
.
UserGroupGrantedRemoteAppsApi
.
as_view
(),
name
=
'user-group-remote-apps'
),
path
(
'user-group
s
/<uuid:pk>/remote-apps/'
,
api
.
UserGroupGrantedRemoteAppsApi
.
as_view
(),
name
=
'user-group-remote-apps'
),
# 校验用户对RemoteApp的权限
path
(
'remote-app-permission/user/validate/'
,
api
.
ValidateUserRemoteAppPermissionApi
.
as_view
(),
name
=
'validate-user-remote-app-permission'
),
path
(
'remote-app-permission
s
/user/validate/'
,
api
.
ValidateUserRemoteAppPermissionApi
.
as_view
(),
name
=
'validate-user-remote-app-permission'
),
# 用户和RemoteApp变更
path
(
'remote-app-permissions/<uuid:pk>/user/add/'
,
api
.
RemoteAppPermissionAddUserApi
.
as_view
(),
name
=
'remote-app-permission-add-user'
),
...
...
@@ -67,7 +78,11 @@ remote_app_permission_urlpatterns = [
path
(
'remote-app-permissions/<uuid:pk>/remote-app/add/'
,
api
.
RemoteAppPermissionAddRemoteAppApi
.
as_view
(),
name
=
'remote-app-permission-add-remote-app'
),
]
urlpatterns
=
asset_permission_urlpatterns
+
remote_app_permission_urlpatterns
old_version_urlpatterns
=
[
re_path
(
'(?P<resource>user|user-group|asset-permission|remote-app-permission)/.*'
,
capi
.
redirect_plural_name_api
)
]
urlpatterns
=
asset_permission_urlpatterns
+
remote_app_permission_urlpatterns
+
old_version_urlpatterns
urlpatterns
+=
router
.
urls
apps/perms/utils/asset_permission.py
View file @
21ffa8b2
# coding: utf-8
import
time
import
uuid
from
collections
import
defaultdict
import
json
from
hashlib
import
md5
import
time
import
itertools
from
django.utils
import
timezone
from
django.db.models
import
Q
from
django.core.cache
import
cache
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
orgs.utils
import
set_to_root_org
from
common.utils
import
get_logger
from
common.utils
import
get_logger
,
timeit
from
common.tree
import
TreeNode
from
assets.utils
import
NodeUtil
from
..
import
const
from
..models
import
AssetPermission
,
Action
from
..hands
import
Node
,
Asset
from
assets.utils
import
Node
Util
from
.stack
import
PermSystemUserNodeUtil
,
PermAssetsAmount
Util
logger
=
get_logger
(
__file__
)
__all__
=
[
'AssetPermissionUtil'
,
'is_obj_attr_has'
,
'sort_assets'
,
'
parse_asset_to_tree_node'
,
'parse_node_to_tree_n
ode'
,
'
ParserN
ode'
,
]
class
TreeNodeCounter
(
NodeUtil
):
def
__init__
(
self
,
nodes
):
self
.
__nodes
=
nodes
super
()
.
__init__
(
with_assets_amount
=
True
)
def
get_queryset
(
self
):
return
self
.
__nodes
def
timeit
(
func
):
def
wrapper
(
*
args
,
**
kwargs
):
logger
.
debug
(
"Start call: {}"
.
format
(
func
.
__name__
))
now
=
time
.
time
()
result
=
func
(
*
args
,
**
kwargs
)
using
=
time
.
time
()
-
now
logger
.
debug
(
"Call {} end, using: {:.2}s"
.
format
(
func
.
__name__
,
using
))
return
result
return
wrapper
class
GenerateTree
:
def
__init__
(
self
):
"""
nodes = {
"<node1>"
: {
node.key
: {
"system_users": {
"system_user": action,
"system_user2": action,
system_user.id: actions,
},
"assets": set([asset.id,]),
},
"assets": set([<asset_instance>]),
}
}
assets = {
"<asset_instance2>": {
"system_user": action,
"system_user2": action,
asset.id: {
system_user.id: actions,
},
}
"""
self
.
_node_util
=
None
self
.
nodes
=
defaultdict
(
lambda
:
{
"system_users"
:
defaultdict
(
int
),
"assets"
:
set
(),
"assets_amount"
:
0
})
self
.
nodes
=
defaultdict
(
lambda
:
{
"system_users"
:
defaultdict
(
int
),
"assets"
:
set
(),
"assets_amount"
:
0
,
"all_assets"
:
set
(),
})
self
.
assets
=
defaultdict
(
lambda
:
defaultdict
(
int
))
self
.
_root_node
=
None
self
.
_ungroup_node
=
None
self
.
_nodes_with_assets
=
None
self
.
_all_assets_nodes_key
=
None
self
.
_asset_counter
=
0
self
.
_system_user_counter
=
0
self
.
_nodes_assets_counter
=
0
@property
def
node_util
(
self
):
...
...
@@ -82,115 +67,160 @@ class GenerateTree:
self
.
_node_util
=
NodeUtil
()
return
self
.
_node_util
@staticmethod
def
key_sort
(
key
):
key_list
=
[
int
(
i
)
for
i
in
key
.
split
(
':'
)]
return
len
(
key_list
),
key_list
@property
def
root_
node
(
self
):
def
root_
key
(
self
):
if
self
.
_root_node
:
return
self
.
_root_node
all_
node
s
=
self
.
nodes
.
keys
()
all_
key
s
=
self
.
nodes
.
keys
()
# 如果没有授权节点,就放到默认的根节点下
if
not
all_
node
s
:
if
not
all_
key
s
:
return
None
root_
node
=
min
(
all_nodes
)
self
.
_root_
node
=
root_node
return
root_
node
root_
key
=
min
(
all_keys
,
key
=
self
.
key_sort
)
self
.
_root_
key
=
root_key
return
root_
key
@property
def
ungrouped_node
(
self
):
if
self
.
_ungroup_node
:
return
self
.
_ungroup_node
node_id
=
const
.
UNGROUPED_NODE_ID
if
self
.
root_node
:
node_key
=
"{}:{}"
.
format
(
self
.
root_node
.
key
,
self
.
root_node
.
child_mark
)
else
:
node_key
=
'0:0'
node_value
=
_
(
"Default"
)
node
=
Node
(
id
=
node_id
,
key
=
node_key
,
value
=
node_value
)
self
.
add_node
(
node
,
{})
self
.
_ungroup_node
=
node
return
node
def
all_assets_nodes_keys
(
self
):
if
not
self
.
_all_assets_nodes_key
:
self
.
_all_assets_nodes_key
=
Asset
.
get_all_nodes_keys
()
return
self
.
_all_assets_nodes_key
@property
def
empty_node
(
self
):
node_id
=
const
.
EMPTY_NODE_ID
value
=
_
(
'Empty'
)
node
=
Node
(
id
=
node_id
,
value
=
value
)
return
node
#@timeit
def
add_assets_without_system_users
(
self
,
assets
):
for
asset
in
assets
:
self
.
add_asset
(
asset
,
{})
#@timeit
def
add_assets
(
self
,
assets
):
for
asset
,
system_users
in
assets
.
items
():
self
.
add_asset
(
asset
,
system_users
)
# #@timeit
def
add_asset
(
self
,
asset
,
system_users
=
None
):
nodes
=
asset
.
nodes
.
all
()
nodes
=
self
.
node_util
.
get_nodes_by_queryset
(
nodes
)
if
not
system_users
:
system_users
=
defaultdict
(
int
)
def
ungrouped_key
(
self
):
if
self
.
_ungroup_node
:
return
self
.
_ungroup_node
if
self
.
root_key
:
node_key
=
"{}:{}"
.
format
(
self
.
root_key
,
'-1'
)
else
:
system_users
=
{
k
:
v
for
k
,
v
in
system_users
.
items
()}
_system_users
=
self
.
assets
[
asset
]
for
system_user
,
action
in
_system_users
.
items
():
system_users
[
system_user
]
|=
action
# 获取父节点们
parents
=
self
.
node_util
.
get_nodes_parents
(
nodes
,
with_self
=
True
)
for
node
in
parents
:
_system_users
=
self
.
nodes
[
node
][
"system_users"
]
self
.
nodes
[
node
][
"assets_amount"
]
+=
1
for
system_user
,
action
in
_system_users
.
items
():
system_users
[
system_user
]
|=
action
# 过滤系统用户的协议
system_users
=
{
s
:
v
for
s
,
v
in
system_users
.
items
()
if
asset
.
has_protocol
(
s
.
protocol
)}
self
.
assets
[
asset
]
=
system_users
in_nodes
=
set
(
self
.
nodes
.
keys
())
&
set
(
nodes
)
node_key
=
'1:-1'
self
.
_ungroup_node
=
node_key
return
node_key
@timeit
def
add_assets_without_system_users
(
self
,
assets_ids
):
for
asset_id
in
assets_ids
:
self
.
add_asset
(
asset_id
,
{})
@timeit
def
add_assets
(
self
,
assets_ids_with_system_users
):
for
asset_id
,
system_users_ids
in
assets_ids_with_system_users
.
items
():
self
.
add_asset
(
asset_id
,
system_users_ids
)
# @timeit
def
add_asset
(
self
,
asset_id
,
system_users_ids
=
None
):
"""
:param asset_id:
:param system_users_ids: {system_user.id: actions, }
:return:
"""
if
not
system_users_ids
:
system_users_ids
=
defaultdict
(
int
)
# 获取已有资产的系统用户和actions,并更新到最新系统用户信息中
old_system_users_ids
=
self
.
assets
[
asset_id
]
for
system_user_id
,
action
in
old_system_users_ids
.
items
():
system_users_ids
[
system_user_id
]
|=
action
asset_nodes_keys
=
self
.
all_assets_nodes_keys
.
get
(
asset_id
,
[])
# {asset.id: [node.key, ], }
# 获取用户在的节点
in_nodes
=
set
(
self
.
nodes
.
keys
())
&
set
(
asset_nodes_keys
)
if
not
in_nodes
:
self
.
nodes
[
self
.
ungrouped_
node
][
"assets_amount"
]
+=
1
self
.
nodes
[
self
.
ungrouped_node
][
"assets"
]
.
add
(
system_users
)
self
.
nodes
[
self
.
ungrouped_
key
][
"assets"
]
.
add
(
asset_id
)
self
.
assets
[
asset_id
]
=
system_users_ids
return
for
node
in
in_nodes
:
self
.
nodes
[
node
][
"assets"
]
.
add
(
asset
)
def
add_node
(
self
,
node
,
system_users
=
None
):
if
not
system_users
:
system_users
=
defaultdict
(
int
)
self
.
nodes
[
node
][
"system_users"
]
=
system_users
# 遍历用户应该在的节点
for
key
in
in_nodes
:
# 把自己加入到树上的节点中
self
.
nodes
[
key
][
"assets"
]
.
add
(
asset_id
)
# 获取自己所在节点的系统用户,并添加进去
node_system_users_ids
=
self
.
nodes
[
key
][
"system_users"
]
for
system_user_id
,
action
in
node_system_users_ids
.
items
():
system_users_ids
[
system_user_id
]
|=
action
self
.
assets
[
asset_id
]
=
system_users_ids
def
add_node
(
self
,
node_key
,
system_users_ids
=
None
):
"""
:param node_key: node.key
:param system_users_ids: {system_user.id: actions,}
:return:
"""
if
not
system_users_ids
:
system_users_ids
=
defaultdict
(
int
)
self
.
nodes
[
node_key
][
"system_users"
]
=
system_users_ids
# 添加树节点
#@timeit
def
add_nodes
(
self
,
nodes
):
_nodes
=
nodes
.
keys
()
family
=
self
.
node_util
.
get_family
(
_nodes
,
with_children
=
True
)
for
node
in
family
:
self
.
add_node
(
node
,
nodes
.
get
(
node
,
{}))
@timeit
def
add_nodes
(
self
,
nodes_keys_with_system_users_ids
):
"""
:param nodes_keys_with_system_users_ids:
{node.key: {system_user.id: actions,}, }
:return:
"""
util
=
PermSystemUserNodeUtil
()
family
=
util
.
get_nodes_family_and_system_users
(
nodes_keys_with_system_users_ids
)
for
key
,
system_users
in
family
.
items
():
self
.
add_node
(
key
,
system_users
)
def
get_assets
(
self
):
return
dict
(
self
.
assets
)
"""
:return:
[
{"id": asset.id, "system_users": {system_user.id: actions, }},
]
"""
assets
=
[]
for
asset_id
,
system_users
in
self
.
assets
.
items
():
assets
.
append
({
"id"
:
asset_id
,
"system_users"
:
system_users
})
return
assets
#
@timeit
@timeit
def
get_nodes_with_assets
(
self
):
"""
:return:
[
{
'key': node.key,
'assets_amount': 10
'assets': {
asset.id: {
system_user.id: actions,
},
},
},
]
"""
if
self
.
_nodes_with_assets
:
return
self
.
_nodes_with_assets
nodes
=
{}
for
node
,
values
in
self
.
nodes
.
items
():
node
.
_assets_amount
=
values
[
"assets_amount"
]
nodes
[
node
]
=
{
asset
:
self
.
assets
.
get
(
asset
,
{})
for
asset
in
values
[
"assets"
]}
util
=
PermAssetsAmountUtil
()
nodes_with_assets_amount
=
util
.
compute_nodes_assets_amount
(
self
.
nodes
)
nodes
=
[]
for
key
,
values
in
nodes_with_assets_amount
.
items
():
assets
=
{
asset_id
:
self
.
assets
.
get
(
asset_id
)
for
asset_id
in
values
[
"assets"
]}
nodes
.
append
({
"key"
:
key
,
"assets"
:
assets
,
"assets_amount"
:
values
[
"assets_amount"
]
})
# 如果返回空节点,页面构造授权资产树报错
if
not
nodes
:
nodes
[
self
.
empty_node
]
=
{}
nodes
.
append
({
"key"
:
const
.
EMPTY_NODE_KEY
,
"assets"
:
{},
"assets_amount"
:
0
})
nodes
.
sort
(
key
=
lambda
n
:
self
.
key_sort
(
n
[
"key"
]))
self
.
_nodes_with_assets
=
nodes
return
dict
(
nodes
)
return
nodes
def
get_nodes
(
self
):
return
list
(
self
.
nodes
.
keys
())
nodes
=
list
(
self
.
nodes
.
keys
())
if
not
nodes
:
nodes
.
append
(
const
.
EMPTY_NODE_KEY
)
return
list
(
nodes
)
def
get_user_permissions
(
user
,
include_group
=
True
):
...
...
@@ -228,8 +258,8 @@ def get_system_user_permissions(system_user):
class
AssetPermissionCacheMixin
:
CACHE_KEY_PREFIX
=
'_ASSET_PERM_CACHE_'
CACHE_META_KEY_PREFIX
=
'_ASSET_PERM_META_KEY_'
CACHE_KEY_PREFIX
=
'_ASSET_PERM_CACHE_
V2_
'
CACHE_META_KEY_PREFIX
=
'_ASSET_PERM_META_KEY_
V2_
'
CACHE_TIME
=
settings
.
ASSETS_PERM_CACHE_TIME
CACHE_POLICY_MAP
=
((
'0'
,
'never'
),
(
'1'
,
'using'
),
(
'2'
,
'refresh'
))
cache_policy
=
'1'
...
...
@@ -283,6 +313,7 @@ class AssetPermissionCacheMixin:
return
self
.
get_cache_key
(
'SYSTEM_USER'
)
def
get_resource_from_cache
(
self
,
resource
):
logger
.
debug
(
"Try get resource from cache"
)
key_map
=
{
"assets"
:
self
.
asset_key
,
"nodes"
:
self
.
node_key
,
...
...
@@ -294,18 +325,22 @@ class AssetPermissionCacheMixin:
raise
ValueError
(
"Not a valid resource: {}"
.
format
(
resource
))
cached
=
cache
.
get
(
key
)
if
not
cached
:
logger
.
debug
(
"Not found resource cache, update it"
)
self
.
update_cache
()
cached
=
cache
.
get
(
key
)
return
cached
def
get_resource
(
self
,
resource
):
if
self
.
_is_using_cache
():
logger
.
debug
(
"Using cache to get resource"
)
return
self
.
get_resource_from_cache
(
resource
)
elif
self
.
_is_refresh_cache
():
logger
.
debug
(
"Need refresh cache"
)
self
.
expire_cache
()
data
=
self
.
get_resource_from_cache
(
resource
)
return
data
else
:
logger
.
debug
(
"Not using cache get source"
)
return
self
.
get_resource_without_cache
(
resource
)
def
get_resource_without_cache
(
self
,
resource
):
...
...
@@ -430,88 +465,91 @@ class AssetPermissionUtil(AssetPermissionCacheMixin):
self
.
_permissions
=
permissions
return
permissions
#
@timeit
@timeit
def
filter_permissions
(
self
,
**
filters
):
filters_json
=
json
.
dumps
(
filters
,
sort_keys
=
True
)
self
.
_permissions
=
self
.
permissions
.
filter
(
**
filters
)
self
.
_filter_id
=
md5
(
filters_json
.
encode
())
.
hexdigest
()
#
@timeit
@timeit
def
get_nodes_direct
(
self
):
"""
返回用户/组授权规则直接关联的节点
:return: {node1: {system_user1: {'actions': set()},}}
返回直接授权的节点,
并将节点添加到tree.nodes中,并将节点下的资产添加到tree.assets中
:return:
{node.key: {system_user.id: actions,}, }
"""
if
self
.
_nodes_direct
:
return
self
.
_nodes_direct
nodes
=
defaultdict
(
lambda
:
defaultdict
(
int
))
nodes
_keys
=
defaultdict
(
lambda
:
defaultdict
(
int
))
for
perm
in
self
.
permissions
:
actions
=
[
perm
.
actions
]
system_users
=
perm
.
system_users
.
all
()
_nodes
=
perm
.
nodes
.
all
()
for
node
,
system_user
,
action
in
itertools
.
product
(
_nodes
,
system_users
,
actions
):
nodes
[
node
][
system_user
]
|=
action
self
.
tree
.
add_nodes
(
nodes
)
self
.
_nodes_direct
=
nodes
return
nodes
system_users_ids
=
[
s
.
id
for
s
in
perm
.
system_users
.
all
()]
_nodes_keys
=
[
n
.
key
for
n
in
perm
.
nodes
.
all
()]
iterable
=
itertools
.
product
(
_nodes_keys
,
system_users_ids
,
actions
)
for
node_key
,
sys_id
,
action
in
iterable
:
nodes_keys
[
node_key
][
sys_id
]
|=
action
self
.
tree
.
add_nodes
(
nodes_keys
)
pattern
=
set
()
for
key
in
nodes_keys
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
if
pattern
:
assets_ids
=
Asset
.
objects
.
filter
(
nodes__key__regex
=
pattern
)
.
values_list
(
"id"
,
flat
=
True
)
.
distinct
()
else
:
assets_ids
=
[]
self
.
tree
.
add_assets_without_system_users
(
assets_ids
)
self
.
_nodes_direct
=
nodes_keys
return
nodes_keys
def
get_nodes_without_cache
(
self
):
self
.
get_assets_
direct
()
self
.
get_assets_
without_cache
()
return
self
.
tree
.
get_nodes
()
#
@timeit
@timeit
def
get_assets_direct
(
self
):
"""
返回用户授权规则直接关联的资产
:return: {asset1: {system_user1: 1,}}
返回直接授权的资产,
并添加到tree.assets中
:return:
{asset.id: {system_user.id: actions, }, }
"""
if
self
.
_assets_direct
:
return
self
.
_assets_direct
assets
=
defaultdict
(
lambda
:
defaultdict
(
int
))
assets
_ids
=
defaultdict
(
lambda
:
defaultdict
(
int
))
for
perm
in
self
.
permissions
:
actions
=
[
perm
.
actions
]
_assets
=
perm
.
assets
.
valid
()
.
only
(
*
self
.
assets_only
)
system_users
=
perm
.
system_users
.
all
()
iterable
=
itertools
.
product
(
_assets
,
system_user
s
,
actions
)
for
asset
,
system_user
,
action
in
iterable
:
assets
[
asset
][
system_user
]
|=
action
self
.
tree
.
add_assets
(
assets
)
self
.
_assets_direct
=
assets
return
assets
#
@timeit
_assets
_ids
=
[
a
.
id
for
a
in
perm
.
assets
.
all
()]
system_users
_ids
=
[
s
.
id
for
s
in
perm
.
system_users
.
all
()]
iterable
=
itertools
.
product
(
_assets
_ids
,
system_users_id
s
,
actions
)
for
asset
_id
,
sys_id
,
action
in
iterable
:
assets
_ids
[
asset_id
][
sys_id
]
|=
action
self
.
tree
.
add_assets
(
assets
_ids
)
self
.
_assets_direct
=
assets
_ids
return
assets
_ids
@timeit
def
get_assets_without_cache
(
self
):
"""
:return: {asset1: set(system_user1,)}
:return:
[
{"id": asset.id, "system_users": {system_user.id: actions, }},
]
"""
if
self
.
_assets
:
return
self
.
_assets
self
.
get_nodes_direct
()
self
.
get_assets_direct
()
nodes
=
self
.
get_nodes_direct
()
pattern
=
set
()
for
node
in
nodes
:
pattern
.
add
(
r'^{0}$|^{0}:'
.
format
(
node
.
key
))
pattern
=
'|'
.
join
(
list
(
pattern
))
if
pattern
:
assets
=
Asset
.
objects
.
filter
(
nodes__key__regex
=
pattern
)
.
valid
()
\
.
prefetch_related
(
'nodes'
)
\
.
only
(
*
self
.
assets_only
)
\
.
distinct
()
else
:
assets
=
[]
assets
=
list
(
assets
)
self
.
tree
.
add_assets_without_system_users
(
assets
)
assets
=
self
.
tree
.
get_assets
()
self
.
_assets
=
assets
return
assets
#
@timeit
@timeit
def
get_nodes_with_assets_without_cache
(
self
):
"""
返回节点并且包含资产
{"node": {"asset": {"system_user": 1})}}
:return:
"""
self
.
get_assets_without_cache
()
nodes_assets
=
self
.
tree
.
get_nodes_with_assets
()
return
nodes_assets
...
...
@@ -545,7 +583,15 @@ def sort_assets(assets, order_by='hostname', reverse=False):
return
assets
def
parse_node_to_tree_node
(
node
):
class
ParserNode
:
nodes_only_fields
=
(
"key"
,
"value"
,
"id"
)
assets_only_fields
=
(
"platform"
,
"hostname"
,
"id"
,
"ip"
,
"protocols"
)
system_users_only_fields
=
(
"id"
,
"name"
,
"username"
,
"protocol"
,
"priority"
,
"login_mode"
,
)
@staticmethod
def
parse_node_to_tree_node
(
node
):
name
=
'{} ({})'
.
format
(
node
.
value
,
node
.
assets_amount
)
data
=
{
'id'
:
node
.
key
,
...
...
@@ -566,15 +612,15 @@ def parse_node_to_tree_node(node):
tree_node
=
TreeNode
(
**
data
)
return
tree_node
def
parse_asset_to_tree_node
(
node
,
asset
,
system_users
):
@staticmethod
def
parse_asset_to_tree_node
(
node
,
asset
,
system_users
):
icon_skin
=
'file'
if
asset
.
platform
.
lower
()
==
'windows'
:
icon_skin
=
'windows'
elif
asset
.
platform
.
lower
()
==
'linux'
:
icon_skin
=
'linux'
_system_users
=
[]
for
system_user
,
action
in
system_users
.
items
()
:
for
system_user
in
system_users
:
_system_users
.
append
({
'id'
:
system_user
.
id
,
'name'
:
system_user
.
name
,
...
...
@@ -582,7 +628,7 @@ def parse_asset_to_tree_node(node, asset, system_users):
'protocol'
:
system_user
.
protocol
,
'priority'
:
system_user
.
priority
,
'login_mode'
:
system_user
.
login_mode
,
'actions'
:
[
Action
.
value_to_choices
(
action
)],
'actions'
:
[
Action
.
value_to_choices
(
system_user
.
actions
)],
})
data
=
{
'id'
:
str
(
asset
.
id
),
...
...
@@ -601,9 +647,6 @@ def parse_asset_to_tree_node(node, asset, system_users):
'ip'
:
asset
.
ip
,
'protocols'
:
asset
.
protocols_as_list
,
'platform'
:
asset
.
platform
,
'domain'
:
None
if
not
asset
.
domain
else
asset
.
domain
.
id
,
'is_active'
:
asset
.
is_active
,
'comment'
:
asset
.
comment
},
}
}
...
...
apps/perms/utils/stack.py
0 → 100644
View file @
21ffa8b2
# -*- coding: utf-8 -*-
#
from
collections
import
defaultdict
from
common.struct
import
Stack
from
common.utils
import
timeit
from
assets.utils
import
NodeUtil
class
PermStackUtilMixin
:
def
__init__
(
self
,
debug
=
False
):
self
.
stack
=
None
self
.
_nodes
=
{}
self
.
_debug
=
debug
@staticmethod
def
sorted_by
(
node_dict
):
return
[
int
(
i
)
for
i
in
node_dict
[
'key'
]
.
split
(
':'
)]
@staticmethod
def
is_children
(
item1
,
item2
):
key1
=
item1
[
"key"
]
key2
=
item2
[
"key"
]
return
key2
.
startswith
(
key1
+
':'
)
and
(
len
(
key2
.
split
(
':'
))
-
len
(
key1
.
split
(
':'
))
)
==
1
def
debug
(
self
,
msg
):
self
.
_debug
and
print
(
msg
)
class
PermSystemUserNodeUtil
(
PermStackUtilMixin
):
"""
self._nodes: {node.key: {system_user.id: actions,}}
"""
@timeit
def
get_nodes_family_and_system_users
(
self
,
nodes_with_system_users
):
"""
返回所有nodes_with_system_users中的node的家族节点的信息,
并子会继承祖先的系统用户和actions信息
:param nodes_with_system_users:
{node.key: {system_user.id: actions,}, }
:return:
{node.key: {system_user.id: actions,}, }
"""
node_util
=
NodeUtil
()
_nodes_keys
=
nodes_with_system_users
.
keys
()
family_keys
=
node_util
.
get_some_nodes_family_keys_by_keys
(
_nodes_keys
)
nodes_items
=
[]
for
i
in
family_keys
:
system_users
=
nodes_with_system_users
.
get
(
i
,
defaultdict
(
int
))
item
=
{
"key"
:
i
,
"system_users"
:
system_users
}
nodes_items
.
append
(
item
)
# 按照父子关系排序
nodes_items
.
sort
(
key
=
self
.
sorted_by
)
nodes_items
.
append
({
"key"
:
""
,
"system_users"
:
defaultdict
(
int
)})
self
.
stack
=
Stack
()
for
item
in
nodes_items
:
self
.
debug
(
"准备: {} 栈顶: {}"
.
format
(
item
[
'key'
],
self
.
stack
.
top
[
"key"
]
if
self
.
stack
.
top
else
None
)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while
self
.
stack
.
top
and
not
self
.
is_children
(
self
.
stack
.
top
,
item
):
# 出栈
self
.
pop_from_stack_system_users
()
# 入栈
self
.
push_to_stack_system_users
(
item
)
# 出栈最后一个
self
.
debug
(
"剩余: {}"
.
format
(
', '
.
join
([
n
[
"key"
]
for
n
in
self
.
stack
])))
return
self
.
_nodes
def
push_to_stack_system_users
(
self
,
item
):
"""
:param item:
{"key": node.key, "system_users": {system_user.id: actions,},}
"""
if
not
self
.
stack
.
is_empty
():
item_system_users
=
item
[
"system_users"
]
for
system_user
,
action
in
self
.
stack
.
top
[
"system_users"
]
.
items
():
# 更新栈顶的系统用户和action到将要入栈的item中
item_system_users
[
system_user
]
|=
action
item
[
"system_users"
]
=
item_system_users
self
.
debug
(
"入栈: {}"
.
format
(
item
[
'key'
]))
self
.
stack
.
push
(
item
)
# 出栈
def
pop_from_stack_system_users
(
self
):
_node
=
self
.
stack
.
pop
()
self
.
_nodes
[
_node
[
"key"
]]
=
_node
[
"system_users"
]
self
.
debug
(
"出栈: {} 栈顶: {}"
.
format
(
_node
[
'key'
],
self
.
stack
.
top
[
'key'
]
if
self
.
stack
.
top
else
None
))
class
PermAssetsAmountUtil
(
PermStackUtilMixin
):
def
push_to_stack_nodes_amount
(
self
,
item
):
self
.
debug
(
"入栈: {}"
.
format
(
item
[
'key'
]))
self
.
stack
.
push
(
item
)
def
pop_from_stack_nodes_amount
(
self
):
_node
=
self
.
stack
.
pop
()
self
.
debug
(
"出栈: {} 栈顶: {}"
.
format
(
_node
[
'key'
],
self
.
stack
.
top
[
'key'
]
if
self
.
stack
.
top
else
None
)
)
_node
[
"assets_amount"
]
=
len
(
_node
[
"all_assets"
]
|
_node
[
"assets"
])
self
.
_nodes
[
_node
.
pop
(
"key"
)]
=
_node
if
not
self
.
stack
.
top
:
return
self
.
stack
.
top
[
"all_assets"
]
\
.
update
(
_node
[
"all_assets"
]
|
_node
[
"assets"
])
def
compute_nodes_assets_amount
(
self
,
nodes_with_assets
):
self
.
stack
=
Stack
()
nodes_items
=
[]
for
key
,
values
in
nodes_with_assets
.
items
():
nodes_items
.
append
({
"key"
:
key
,
"assets"
:
values
[
"assets"
],
"all_assets"
:
values
[
"all_assets"
],
"assets_amount"
:
0
})
nodes_items
.
sort
(
key
=
self
.
sorted_by
)
nodes_items
.
append
({
"key"
:
""
,
"assets"
:
set
(),
"all_assets"
:
set
(),
"assets_amount"
:
0
})
self
.
stack
=
Stack
()
for
item
in
nodes_items
:
self
.
debug
(
"准备: {} 栈顶: {}"
.
format
(
item
[
'key'
],
self
.
stack
.
top
[
"key"
]
if
self
.
stack
.
top
else
None
)
)
# 入栈之前检查,该节点是不是栈顶节点的子节点
# 如果不是,则栈顶出栈
while
self
.
stack
.
top
and
not
self
.
is_children
(
self
.
stack
.
top
,
item
):
self
.
pop_from_stack_nodes_amount
()
self
.
push_to_stack_nodes_amount
(
item
)
# 出栈最后一个
self
.
debug
(
"剩余: {}"
.
format
(
', '
.
join
([
n
[
"key"
]
for
n
in
self
.
stack
])))
return
self
.
_nodes
\ No newline at end of file
apps/users/models/user.py
View file @
21ffa8b2
...
...
@@ -27,97 +27,7 @@ signer = get_signer()
logger
=
get_logger
(
__file__
)
class
User
(
AbstractUser
):
ROLE_ADMIN
=
'Admin'
ROLE_USER
=
'User'
ROLE_APP
=
'App'
ROLE_AUDITOR
=
'Auditor'
ROLE_CHOICES
=
(
(
ROLE_ADMIN
,
_
(
'Administrator'
)),
(
ROLE_USER
,
_
(
'User'
)),
(
ROLE_APP
,
_
(
'Application'
)),
(
ROLE_AUDITOR
,
_
(
"Auditor"
))
)
OTP_LEVEL_CHOICES
=
(
(
0
,
_
(
'Disable'
)),
(
1
,
_
(
'Enable'
)),
(
2
,
_
(
"Force enable"
)),
)
SOURCE_LOCAL
=
'local'
SOURCE_LDAP
=
'ldap'
SOURCE_OPENID
=
'openid'
SOURCE_RADIUS
=
'radius'
SOURCE_CHOICES
=
(
(
SOURCE_LOCAL
,
'Local'
),
(
SOURCE_LDAP
,
'LDAP/AD'
),
(
SOURCE_OPENID
,
'OpenID'
),
(
SOURCE_RADIUS
,
'Radius'
),
)
CACHE_KEY_USER_RESET_PASSWORD_PREFIX
=
"_KEY_USER_RESET_PASSWORD_{}"
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
username
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Username'
)
)
name
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
'Name'
))
email
=
models
.
EmailField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Email'
)
)
groups
=
models
.
ManyToManyField
(
'users.UserGroup'
,
related_name
=
'users'
,
blank
=
True
,
verbose_name
=
_
(
'User group'
)
)
role
=
models
.
CharField
(
choices
=
ROLE_CHOICES
,
default
=
'User'
,
max_length
=
10
,
blank
=
True
,
verbose_name
=
_
(
'Role'
)
)
avatar
=
models
.
ImageField
(
upload_to
=
"avatar"
,
null
=
True
,
verbose_name
=
_
(
'Avatar'
)
)
wechat
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Wechat'
)
)
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
)
)
otp_level
=
models
.
SmallIntegerField
(
default
=
0
,
choices
=
OTP_LEVEL_CHOICES
,
verbose_name
=
_
(
'MFA'
)
)
otp_secret_key
=
fields
.
EncryptCharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
)
# Todo: Auto generate key, let user download
private_key
=
fields
.
EncryptTextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Private key'
)
)
public_key
=
fields
.
EncryptTextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Public key'
)
)
comment
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Comment'
)
)
is_first_login
=
models
.
BooleanField
(
default
=
True
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
db_index
=
True
,
verbose_name
=
_
(
'Date expired'
)
)
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
)
)
source
=
models
.
CharField
(
max_length
=
30
,
default
=
SOURCE_LOCAL
,
choices
=
SOURCE_CHOICES
,
verbose_name
=
_
(
'Source'
)
)
date_password_last_updated
=
models
.
DateTimeField
(
auto_now_add
=
True
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date password last updated'
)
)
user_cache_key_prefix
=
'_User_{}'
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
class
AuthMixin
:
@property
def
password_raw
(
self
):
raise
AttributeError
(
'Password raw is not a readable attribute'
)
...
...
@@ -134,9 +44,12 @@ class User(AbstractUser):
def
set_password
(
self
,
raw_password
):
self
.
_set_password
=
True
if
self
.
can_update_password
():
return
super
()
.
set_password
(
raw_password
)
self
.
date_password_last_updated
=
timezone
.
now
()
super
()
.
set_password
(
raw_password
)
self
.
save
()
else
:
error
=
_
(
"User auth from {}, go there change password"
)
.
format
(
self
.
source
)
error
=
_
(
"User auth from {}, go there change password"
)
.
format
(
self
.
source
)
raise
PermissionError
(
error
)
def
can_update_password
(
self
):
...
...
@@ -146,9 +59,6 @@ class User(AbstractUser):
from
..utils
import
check_otp_code
return
check_otp_code
(
self
.
otp_secret_key
,
code
)
def
get_absolute_url
(
self
):
return
reverse
(
'users:user-detail'
,
args
=
(
self
.
id
,))
def
is_public_key_valid
(
self
):
"""
Check if the user's ssh public key is valid.
...
...
@@ -159,42 +69,64 @@ class User(AbstractUser):
return
False
@property
def
groups_display
(
self
):
return
' '
.
join
([
group
.
name
for
group
in
self
.
groups
.
all
()])
def
public_key_obj
(
self
):
class
PubKey
(
object
):
def
__getattr__
(
self
,
item
):
return
''
if
self
.
public_key
:
import
sshpubkeys
try
:
return
sshpubkeys
.
SSHKey
(
self
.
public_key
)
except
(
TabError
,
TypeError
):
pass
return
PubKey
()
def
reset_password
(
self
,
new_password
):
self
.
set_password
(
new_password
)
@property
def
role_display
(
self
):
return
self
.
get_role_display
()
def
date_password_expired
(
self
):
interval
=
settings
.
SECURITY_PASSWORD_EXPIRATION_TIME
date_expired
=
self
.
date_password_last_updated
+
timezone
.
timedelta
(
days
=
int
(
interval
))
return
date_expired
@property
def
source_display
(
self
):
return
self
.
get_source_display
()
def
password_expired_remain_days
(
self
):
date_remain
=
self
.
date_password_expired
-
timezone
.
now
()
return
date_remain
.
days
@property
def
i
s_expired
(
self
):
if
self
.
date_expired
and
self
.
date_expired
<
timezone
.
now
()
:
def
password_ha
s_expired
(
self
):
if
self
.
is_local
and
self
.
password_expired_remain_days
<
0
:
return
True
else
:
return
False
@property
def
is_vali
d
(
self
):
if
self
.
is_
active
and
not
self
.
is_expired
:
def
password_will_expire
d
(
self
):
if
self
.
is_
local
and
self
.
password_expired_remain_days
<
5
:
return
True
return
False
class
RoleMixin
:
ROLE_ADMIN
=
'Admin'
ROLE_USER
=
'User'
ROLE_APP
=
'App'
ROLE_AUDITOR
=
'Auditor'
ROLE_CHOICES
=
(
(
ROLE_ADMIN
,
_
(
'Administrator'
)),
(
ROLE_USER
,
_
(
'User'
)),
(
ROLE_APP
,
_
(
'Application'
)),
(
ROLE_AUDITOR
,
_
(
"Auditor"
))
)
role
=
ROLE_USER
@property
def
public_key_obj
(
self
):
class
PubKey
(
object
):
def
__getattr__
(
self
,
item
):
return
''
if
self
.
public_key
:
import
sshpubkeys
try
:
return
sshpubkeys
.
SSHKey
(
self
.
public_key
)
except
(
TabError
,
TypeError
):
pass
return
PubKey
()
def
role_display
(
self
):
return
self
.
get_role_display
()
@property
def
is_superuser
(
self
):
...
...
@@ -251,41 +183,21 @@ class User(AbstractUser):
def
is_staff
(
self
,
value
):
pass
@property
def
is_local
(
self
):
return
self
.
source
==
self
.
SOURCE_LOCAL
@property
def
date_password_expired
(
self
):
interval
=
settings
.
SECURITY_PASSWORD_EXPIRATION_TIME
date_expired
=
self
.
date_password_last_updated
+
timezone
.
timedelta
(
days
=
int
(
interval
))
return
date_expired
@property
def
password_expired_remain_days
(
self
):
date_remain
=
self
.
date_password_expired
-
timezone
.
now
()
return
date_remain
.
days
@property
def
password_has_expired
(
self
):
if
self
.
is_local
and
self
.
password_expired_remain_days
<
0
:
return
True
return
False
@classmethod
def
create_app_user
(
cls
,
name
,
comment
):
app
=
cls
.
objects
.
create
(
username
=
name
,
name
=
name
,
email
=
'{}@local.domain'
.
format
(
name
),
is_active
=
False
,
role
=
'App'
,
comment
=
comment
,
is_first_login
=
False
,
created_by
=
'System'
)
access_key
=
app
.
create_access_key
()
return
app
,
access_key
@property
def
password_will_expired
(
self
):
if
self
.
is_local
and
self
.
password_expired_remain_days
<
5
:
return
True
return
False
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
name
:
self
.
name
=
self
.
username
if
self
.
username
==
'admin'
:
self
.
role
=
'Admin'
self
.
is_active
=
True
super
()
.
save
(
*
args
,
**
kwargs
)
class
TokenMixin
:
CACHE_KEY_USER_RESET_PASSWORD_PREFIX
=
"_KEY_USER_RESET_PASSWORD_{}"
email
=
''
id
=
None
@property
def
private_token
(
self
):
...
...
@@ -333,31 +245,12 @@ class User(AbstractUser):
def
access_key
(
self
):
return
self
.
access_keys
.
first
()
def
is_member_of
(
self
,
user_group
):
if
user_group
in
self
.
groups
.
all
():
return
True
return
False
def
avatar_url
(
self
):
admin_default
=
settings
.
STATIC_URL
+
"img/avatar/admin.png"
user_default
=
settings
.
STATIC_URL
+
"img/avatar/user.png"
if
self
.
avatar
:
return
self
.
avatar
.
url
if
self
.
is_superuser
:
return
admin_default
else
:
return
user_default
def
generate_reset_token
(
self
):
letter
=
string
.
ascii_letters
+
string
.
digits
token
=
''
.
join
([
random
.
choice
(
letter
)
for
_
in
range
(
50
)])
self
.
set_cache
(
token
)
return
token
def
set_cache
(
self
,
token
):
key
=
self
.
CACHE_KEY_USER_RESET_PASSWORD_PREFIX
.
format
(
token
)
cache
.
set
(
key
,
{
'id'
:
self
.
id
,
'email'
:
self
.
email
},
3600
)
@classmethod
def
validate_reset_password_token
(
cls
,
token
):
try
:
...
...
@@ -371,11 +264,25 @@ class User(AbstractUser):
user
=
None
return
user
def
set_cache
(
self
,
token
):
key
=
self
.
CACHE_KEY_USER_RESET_PASSWORD_PREFIX
.
format
(
token
)
cache
.
set
(
key
,
{
'id'
:
self
.
id
,
'email'
:
self
.
email
},
3600
)
@classmethod
def
expired_reset_password_token
(
cls
,
token
):
key
=
cls
.
CACHE_KEY_USER_RESET_PASSWORD_PREFIX
.
format
(
token
)
cache
.
delete
(
key
)
class
MFAMixin
:
otp_level
=
0
otp_secret_key
=
''
OTP_LEVEL_CHOICES
=
(
(
0
,
_
(
'Disable'
)),
(
1
,
_
(
'Enable'
)),
(
2
,
_
(
"Force enable"
)),
)
@property
def
otp_enabled
(
self
):
return
self
.
otp_force_enabled
or
self
.
otp_level
>
0
...
...
@@ -397,39 +304,130 @@ class User(AbstractUser):
self
.
otp_level
=
0
self
.
otp_secret_key
=
None
def
to_json
(
self
):
return
OrderedDict
({
'id'
:
self
.
id
,
'username'
:
self
.
username
,
'name'
:
self
.
name
,
'email'
:
self
.
email
,
'is_active'
:
self
.
is_active
,
'is_superuser'
:
self
.
is_superuser
,
'role'
:
self
.
get_role_display
(),
'groups'
:
[
group
.
name
for
group
in
self
.
groups
.
all
()],
'source'
:
self
.
get_source_display
(),
'wechat'
:
self
.
wechat
,
'phone'
:
self
.
phone
,
'otp_level'
:
self
.
otp_level
,
'comment'
:
self
.
comment
,
'date_expired'
:
self
.
date_expired
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
\
if
self
.
date_expired
is
not
None
else
None
})
@classmethod
def
create_app_user
(
cls
,
name
,
comment
):
app
=
cls
.
objects
.
create
(
username
=
name
,
name
=
name
,
email
=
'{}@local.domain'
.
format
(
name
),
is_active
=
False
,
role
=
'App'
,
comment
=
comment
,
is_first_login
=
False
,
created_by
=
'System'
class
User
(
AuthMixin
,
TokenMixin
,
RoleMixin
,
MFAMixin
,
AbstractUser
):
SOURCE_LOCAL
=
'local'
SOURCE_LDAP
=
'ldap'
SOURCE_OPENID
=
'openid'
SOURCE_RADIUS
=
'radius'
SOURCE_CHOICES
=
(
(
SOURCE_LOCAL
,
'Local'
),
(
SOURCE_LDAP
,
'LDAP/AD'
),
(
SOURCE_OPENID
,
'OpenID'
),
(
SOURCE_RADIUS
,
'Radius'
),
)
access_key
=
app
.
create_access_key
()
return
app
,
access_key
def
reset_password
(
self
,
new_password
):
self
.
set_password
(
new_password
)
self
.
date_password_last_updated
=
timezone
.
now
()
self
.
save
()
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
username
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Username'
)
)
name
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
'Name'
))
email
=
models
.
EmailField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Email'
)
)
groups
=
models
.
ManyToManyField
(
'users.UserGroup'
,
related_name
=
'users'
,
blank
=
True
,
verbose_name
=
_
(
'User group'
)
)
role
=
models
.
CharField
(
choices
=
RoleMixin
.
ROLE_CHOICES
,
default
=
'User'
,
max_length
=
10
,
blank
=
True
,
verbose_name
=
_
(
'Role'
)
)
avatar
=
models
.
ImageField
(
upload_to
=
"avatar"
,
null
=
True
,
verbose_name
=
_
(
'Avatar'
)
)
wechat
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Wechat'
)
)
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
)
)
otp_level
=
models
.
SmallIntegerField
(
default
=
0
,
choices
=
MFAMixin
.
OTP_LEVEL_CHOICES
,
verbose_name
=
_
(
'MFA'
)
)
otp_secret_key
=
fields
.
EncryptCharField
(
max_length
=
128
,
blank
=
True
,
null
=
True
)
# Todo: Auto generate key, let user download
private_key
=
fields
.
EncryptTextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Private key'
)
)
public_key
=
fields
.
EncryptTextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Public key'
)
)
comment
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Comment'
)
)
is_first_login
=
models
.
BooleanField
(
default
=
True
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
db_index
=
True
,
verbose_name
=
_
(
'Date expired'
)
)
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
)
)
source
=
models
.
CharField
(
max_length
=
30
,
default
=
SOURCE_LOCAL
,
choices
=
SOURCE_CHOICES
,
verbose_name
=
_
(
'Source'
)
)
date_password_last_updated
=
models
.
DateTimeField
(
auto_now_add
=
True
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date password last updated'
)
)
user_cache_key_prefix
=
'_User_{}'
def
__str__
(
self
):
return
'{0.name}({0.username})'
.
format
(
self
)
def
get_absolute_url
(
self
):
return
reverse
(
'users:user-detail'
,
args
=
(
self
.
id
,))
@property
def
groups_display
(
self
):
return
' '
.
join
([
group
.
name
for
group
in
self
.
groups
.
all
()])
@property
def
source_display
(
self
):
return
self
.
get_source_display
()
@property
def
is_expired
(
self
):
if
self
.
date_expired
and
self
.
date_expired
<
timezone
.
now
():
return
True
else
:
return
False
@property
def
is_valid
(
self
):
if
self
.
is_active
and
not
self
.
is_expired
:
return
True
return
False
@property
def
is_local
(
self
):
return
self
.
source
==
self
.
SOURCE_LOCAL
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
name
:
self
.
name
=
self
.
username
if
self
.
username
==
'admin'
:
self
.
role
=
'Admin'
self
.
is_active
=
True
super
()
.
save
(
*
args
,
**
kwargs
)
def
is_member_of
(
self
,
user_group
):
if
user_group
in
self
.
groups
.
all
():
return
True
return
False
def
avatar_url
(
self
):
admin_default
=
settings
.
STATIC_URL
+
"img/avatar/admin.png"
user_default
=
settings
.
STATIC_URL
+
"img/avatar/user.png"
if
self
.
avatar
:
return
self
.
avatar
.
url
if
self
.
is_superuser
:
return
admin_default
else
:
return
user_default
def
delete
(
self
,
using
=
None
,
keep_parents
=
False
):
if
self
.
pk
==
1
or
self
.
username
==
'admin'
:
...
...
apps/users/templates/users/user_granted_asset.html
View file @
21ffa8b2
...
...
@@ -29,7 +29,6 @@
<div
class=
"file-manager "
>
<div
id=
"assetTree"
class=
"ztree"
>
</div>
<div
class=
"clearfix"
></div>
</div>
</div>
...
...
@@ -43,7 +42,6 @@
<th
class=
"text-center"
><input
type=
"checkbox"
class=
"ipt_check_all"
></th>
<th
class=
"text-center"
>
{% trans 'Hostname' %}
</th>
<th
class=
"text-center"
>
{% trans 'IP' %}
</th>
<th
class=
"text-center"
>
{% trans 'Active' %}
</th>
<th
class=
"text-center"
>
{% trans 'System users' %}
</th>
</tr>
</thead>
...
...
@@ -64,7 +62,7 @@ var zTree;
var
inited
=
false
;
var
url
;
var
asset_table
;
var
treeUrl
=
"{% url 'api-perms:user-nodes-as
sets-as-tree' pk=object.id %}?show_assets=0
&cache_policy=1"
;
var
treeUrl
=
"{% url 'api-perms:user-nodes-as
-tree' pk=object.id %}?
&cache_policy=1"
;
function
initTable
()
{
if
(
inited
){
...
...
@@ -83,13 +81,6 @@ function initTable() {
$
(
td
).
html
(
detail_btn
.
replace
(
'{{ DEFAULT_PK }}'
,
rowData
.
id
));
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
users
=
[];
$
.
each
(
cellData
,
function
(
id
,
data
)
{
var
name
=
htmlEscape
(
data
.
name
);
...
...
@@ -101,7 +92,6 @@ function initTable() {
ajax_url
:
url
,
columns
:
[
{
data
:
"id"
},
{
data
:
"hostname"
},
{
data
:
"ip"
},
{
data
:
"is_active"
,
orderable
:
false
},
{
data
:
"system_users_granted"
,
orderable
:
false
}
]
};
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment