mirror of https://github.com/jumpserver/jumpserver
commit
cadf42f3fa
|
@ -14,7 +14,7 @@ from .. import serializers
|
|||
from ..tasks import (
|
||||
update_asset_hardware_info_manual, test_asset_connectivity_manual
|
||||
)
|
||||
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend
|
||||
from ..filters import AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend
|
||||
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
@ -32,7 +32,7 @@ class AssetViewSet(OrgBulkModelViewSet):
|
|||
model = Asset
|
||||
filter_fields = (
|
||||
"hostname", "ip", "systemuser__id", "admin_user__id", "platform__base",
|
||||
"is_active"
|
||||
"is_active", 'ip'
|
||||
)
|
||||
search_fields = ("hostname", "ip")
|
||||
ordering_fields = ("hostname", "ip", "port", "cpu_cores")
|
||||
|
@ -41,7 +41,7 @@ class AssetViewSet(OrgBulkModelViewSet):
|
|||
'display': serializers.AssetDisplaySerializer,
|
||||
}
|
||||
permission_classes = (IsOrgAdminOrAppUser,)
|
||||
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend]
|
||||
extra_filter_backends = [AssetByNodeFilterBackend, LabelFilterBackend, IpInFilterBackend]
|
||||
|
||||
def set_assets_node(self, assets):
|
||||
if not isinstance(assets, list):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
|
||||
import coreapi
|
||||
from rest_framework.compat import coreapi, coreschema
|
||||
from rest_framework import filters
|
||||
from django.db.models import Q
|
||||
|
||||
|
@ -117,3 +117,23 @@ class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
|
|||
def perform_query(pattern, queryset):
|
||||
return queryset.filter(asset__nodes__key__regex=pattern).distinct()
|
||||
|
||||
|
||||
class IpInFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
ips = request.query_params.get('ips')
|
||||
if not ips:
|
||||
return queryset
|
||||
ip_list = [i.strip() for i in ips.split(',')]
|
||||
queryset = queryset.filter(ip__in=ip_list)
|
||||
return queryset
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name='ips', location='query', required=False, type='string',
|
||||
schema=coreschema.String(
|
||||
title='ips',
|
||||
description='ip in filter'
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 2.2.10 on 2020-07-02 08:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0049_systemuser_sftp_root'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='name',
|
||||
field=models.CharField(max_length=128, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='domain',
|
||||
unique_together={('org_id', 'name')},
|
||||
),
|
||||
]
|
|
@ -244,10 +244,6 @@ class Asset(ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
|
|||
def platform_base(self):
|
||||
return self.platform.base
|
||||
|
||||
@lazyproperty
|
||||
def admin_user_display(self):
|
||||
return self.admin_user.name
|
||||
|
||||
@lazyproperty
|
||||
def admin_user_username(self):
|
||||
"""求可连接性时,直接用用户名去取,避免再查一次admin user
|
||||
|
|
|
@ -17,13 +17,14 @@ __all__ = ['Domain', 'Gateway']
|
|||
|
||||
class Domain(OrgModelMixin):
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
|
||||
name = models.CharField(max_length=128, verbose_name=_('Name'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
date_created = models.DateTimeField(auto_now_add=True, null=True,
|
||||
verbose_name=_('Date created'))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Domain")
|
||||
unique_together = [('org_id', 'name')]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
@ -199,6 +199,20 @@ class FamilyMixin:
|
|||
)
|
||||
return child
|
||||
|
||||
def get_or_create_child(self, value, _id=None):
|
||||
"""
|
||||
:return: Node, bool (created)
|
||||
"""
|
||||
children = self.get_children()
|
||||
exist = children.filter(value=value).exists()
|
||||
if exist:
|
||||
child = children.filter(value=value).first()
|
||||
created = False
|
||||
else:
|
||||
child = self.create_child(value, _id)
|
||||
created = True
|
||||
return child, created
|
||||
|
||||
def get_next_child_key(self):
|
||||
mark = self.child_mark
|
||||
self.child_mark += 1
|
||||
|
|
|
@ -67,6 +67,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
slug_field='name', queryset=Platform.objects.all(), label=_("Platform")
|
||||
)
|
||||
protocols = ProtocolsField(label=_('Protocols'), required=False)
|
||||
domain_display = serializers.ReadOnlyField(source='domain.name')
|
||||
admin_user_display = serializers.ReadOnlyField(source='admin_user.name')
|
||||
|
||||
"""
|
||||
资产的数据结构
|
||||
"""
|
||||
|
@ -82,7 +85,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
|
|||
'created_by', 'date_created', 'hardware_info',
|
||||
]
|
||||
fields_fk = [
|
||||
'admin_user', 'admin_user_display', 'domain', 'platform'
|
||||
'admin_user', 'admin_user_display', 'domain', 'domain_display', 'platform'
|
||||
]
|
||||
fk_only_fields = {
|
||||
'platform': ['name']
|
||||
|
|
|
@ -85,7 +85,7 @@ def test_asset_user_connectivity_util(asset_user, task_name):
|
|||
raw, summary = test_user_connectivity(
|
||||
task_name=task_name, asset=asset_user.asset,
|
||||
username=asset_user.username, password=asset_user.password,
|
||||
private_key=asset_user.private_key
|
||||
private_key=asset_user.private_key_file
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warn("Failed run adhoc {}, {}".format(task_name, e))
|
||||
|
|
|
@ -31,7 +31,10 @@ def test_system_user_connectivity_util(system_user, assets, task_name):
|
|||
"""
|
||||
from ops.utils import update_or_create_ansible_task
|
||||
|
||||
hosts = clean_ansible_task_hosts(assets, system_user=system_user)
|
||||
# hosts = clean_ansible_task_hosts(assets, system_user=system_user)
|
||||
# TODO: 这里不传递系统用户,因为clean_ansible_task_hosts会通过system_user来判断是否可以推送,
|
||||
# 不符合测试可连接性逻辑, 后面需要优化此逻辑
|
||||
hosts = clean_ansible_task_hosts(assets)
|
||||
if not hosts:
|
||||
return {}
|
||||
platform_hosts_map = {}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.10 on 2020-06-24 08:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('audits', '0008_auto_20200508_2105'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ftplog',
|
||||
name='operate',
|
||||
field=models.CharField(choices=[('Delete', 'Delete'), ('Upload', 'Upload'), ('Download', 'Download'), ('Rmdir', 'Rmdir'), ('Rename', 'Rename'), ('Mkdir', 'Mkdir'), ('Symlink', 'Symlink')], max_length=16, verbose_name='Operate'),
|
||||
),
|
||||
]
|
|
@ -14,12 +14,30 @@ __all__ = [
|
|||
|
||||
|
||||
class FTPLog(OrgModelMixin):
|
||||
OPERATE_DELETE = 'Delete'
|
||||
OPERATE_UPLOAD = 'Upload'
|
||||
OPERATE_DOWNLOAD = 'Download'
|
||||
OPERATE_RMDIR = 'Rmdir'
|
||||
OPERATE_RENAME = 'Rename'
|
||||
OPERATE_MKDIR = 'Mkdir'
|
||||
OPERATE_SYMLINK = 'Symlink'
|
||||
|
||||
OPERATE_CHOICES = (
|
||||
(OPERATE_DELETE, _('Delete')),
|
||||
(OPERATE_UPLOAD, _('Upload')),
|
||||
(OPERATE_DOWNLOAD, _('Download')),
|
||||
(OPERATE_RMDIR, _('Rmdir')),
|
||||
(OPERATE_RENAME, _('Rename')),
|
||||
(OPERATE_MKDIR, _('Mkdir')),
|
||||
(OPERATE_SYMLINK, _('Symlink'))
|
||||
)
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
user = models.CharField(max_length=128, verbose_name=_('User'))
|
||||
remote_addr = models.CharField(max_length=128, verbose_name=_("Remote addr"), blank=True, null=True)
|
||||
asset = models.CharField(max_length=1024, verbose_name=_("Asset"))
|
||||
system_user = models.CharField(max_length=128, verbose_name=_("System user"))
|
||||
operate = models.CharField(max_length=16, verbose_name=_("Operate"))
|
||||
operate = models.CharField(max_length=16, verbose_name=_("Operate"), choices=OPERATE_CHOICES)
|
||||
filename = models.CharField(max_length=1024, verbose_name=_("Filename"))
|
||||
is_success = models.BooleanField(default=True, verbose_name=_("Success"))
|
||||
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Date start'))
|
||||
|
|
|
@ -12,12 +12,13 @@ from . import models
|
|||
|
||||
|
||||
class FTPLogSerializer(serializers.ModelSerializer):
|
||||
operate_display = serializers.ReadOnlyField(source='get_operate_display')
|
||||
|
||||
class Meta:
|
||||
model = models.FTPLog
|
||||
fields = (
|
||||
'id', 'user', 'remote_addr', 'asset', 'system_user',
|
||||
'operate', 'filename', 'is_success', 'date_start'
|
||||
'operate', 'filename', 'is_success', 'date_start', 'operate_display'
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from users.utils import (
|
|||
)
|
||||
|
||||
reason_password_failed = 'password_failed'
|
||||
reason_password_decrypt_failed = 'password_decrypt_failed'
|
||||
reason_mfa_failed = 'mfa_failed'
|
||||
reason_mfa_unset = 'mfa_unset'
|
||||
reason_user_not_exist = 'user_not_exist'
|
||||
|
@ -19,6 +20,7 @@ reason_user_inactive = 'user_inactive'
|
|||
|
||||
reason_choices = {
|
||||
reason_password_failed: _('Username/password check failed'),
|
||||
reason_password_decrypt_failed: _('Password decrypt failed'),
|
||||
reason_mfa_failed: _('MFA failed'),
|
||||
reason_mfa_unset: _('MFA unset'),
|
||||
reason_user_not_exist: _("Username does not exist"),
|
||||
|
|
|
@ -10,7 +10,7 @@ class UserLoginForm(forms.Form):
|
|||
username = forms.CharField(label=_('Username'), max_length=100)
|
||||
password = forms.CharField(
|
||||
label=_('Password'), widget=forms.PasswordInput,
|
||||
max_length=128, strip=False
|
||||
max_length=1024, strip=False
|
||||
)
|
||||
|
||||
def confirm_login_allowed(self, user):
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form class="m-t" role="form" method="post" action="">
|
||||
<form id="form" class="m-t" role="form" method="post" action="">
|
||||
{% csrf_token %}
|
||||
{% if form.non_field_errors %}
|
||||
<div style="line-height: 17px;">
|
||||
|
@ -26,7 +26,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
<input type="password" class="form-control" id="password" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
{% if form.errors.password %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<div>
|
||||
{{ form.captcha }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b">{% trans 'Login' %}</button>
|
||||
<button type="submit" class="btn btn-primary block full-width m-b" onclick="doLogin();return false;">{% trans 'Login' %}</button>
|
||||
|
||||
{% if demo_mode %}
|
||||
<p class="text-muted font-bold" style="color: red">
|
||||
|
@ -64,4 +64,20 @@
|
|||
{% endif %}
|
||||
|
||||
</form>
|
||||
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
|
||||
<script>
|
||||
function encryptLoginPassword(password, rsaPublicKey){
|
||||
var jsencrypt = new JSEncrypt(); //加密对象
|
||||
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
|
||||
return jsencrypt.encrypt(password); //加密
|
||||
}
|
||||
function doLogin() {
|
||||
//公钥加密
|
||||
var rsaPublicKey = "{{ rsa_public_key }}"
|
||||
var password =$('#password').val(); //明文密码
|
||||
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
|
||||
$('#password').val(passwordEncrypted); //返回给密码输入input
|
||||
$('#form').submit();//post提交
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
<input type="password" class="form-control" id="password" name="{{ form.password.html_name }}" placeholder="{% trans 'Password' %}" required="">
|
||||
{% if form.errors.password %}
|
||||
<div class="help-block field-error">
|
||||
<p class="red-fonts">{{ form.errors.password.as_text }}</p>
|
||||
|
@ -109,7 +109,7 @@
|
|||
{{ form.captcha }}
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 10px">
|
||||
<button type="submit" class="btn btn-transparent">{% trans 'Login' %}</button>
|
||||
<button type="submit" class="btn btn-transparent" onclick="doLogin();return false;">{% trans 'Login' %}</button>
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
<a href="{% url 'authentication:forgot-password' %}">
|
||||
|
@ -127,4 +127,21 @@
|
|||
</div>
|
||||
|
||||
</body>
|
||||
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
|
||||
<script>
|
||||
function encryptLoginPassword(password, rsaPublicKey){
|
||||
var jsencrypt = new JSEncrypt(); //加密对象
|
||||
jsencrypt.setPublicKey(rsaPublicKey); // 设置密钥
|
||||
return jsencrypt.encrypt(password); //加密
|
||||
}
|
||||
function doLogin() {
|
||||
//公钥加密
|
||||
var rsaPublicKey = "{{ rsa_public_key }}"
|
||||
var password =$('#password').val(); //明文密码
|
||||
var passwordEncrypted = encryptLoginPassword(password, rsaPublicKey)
|
||||
$('#password').val(passwordEncrypted); //返回给密码输入input
|
||||
$('#contact-form').submit();//post提交
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1 +1,15 @@
|
|||
from .utils import gen_key_pair, rsa_decrypt, rsa_encrypt
|
||||
|
||||
|
||||
def test_rsa_encrypt_decrypt(message='test-password-$%^&*'):
|
||||
""" 测试加密/解密 """
|
||||
print('Need to encrypt message: {}'.format(message))
|
||||
rsa_private_key, rsa_public_key = gen_key_pair()
|
||||
print('RSA public key: \n{}'.format(rsa_public_key))
|
||||
print('RSA private key: \n{}'.format(rsa_private_key))
|
||||
message_encrypted = rsa_encrypt(message, rsa_public_key)
|
||||
print('Encrypted message: {}'.format(message_encrypted))
|
||||
message_decrypted = rsa_decrypt(message_encrypted, rsa_private_key)
|
||||
print('Decrypted message: {}'.format(message_decrypted))
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,47 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import base64
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5
|
||||
from Crypto import Random
|
||||
from django.contrib.auth import authenticate
|
||||
|
||||
from common.utils import get_logger
|
||||
|
||||
from . import errors
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
def gen_key_pair():
|
||||
""" 生成加密key
|
||||
用于登录页面提交用户名/密码时,对密码进行加密(前端)/解密(后端)
|
||||
"""
|
||||
random_generator = Random.new().read
|
||||
rsa = RSA.generate(1024, random_generator)
|
||||
rsa_private_key = rsa.exportKey().decode()
|
||||
rsa_public_key = rsa.publickey().exportKey().decode()
|
||||
return rsa_private_key, rsa_public_key
|
||||
|
||||
|
||||
def rsa_encrypt(message, rsa_public_key):
|
||||
""" 加密登录密码 """
|
||||
key = RSA.importKey(rsa_public_key)
|
||||
cipher = PKCS1_v1_5.new(key)
|
||||
cipher_text = base64.b64encode(cipher.encrypt(message.encode())).decode()
|
||||
return cipher_text
|
||||
|
||||
|
||||
def rsa_decrypt(cipher_text, rsa_private_key=None):
|
||||
""" 解密登录密码 """
|
||||
if rsa_private_key is None:
|
||||
# rsa_private_key 为 None,可以能是API请求认证,不需要解密
|
||||
return cipher_text
|
||||
key = RSA.importKey(rsa_private_key)
|
||||
cipher = PKCS1_v1_5.new(key)
|
||||
message = cipher.decrypt(base64.b64decode(cipher_text.encode()), 'error').decode()
|
||||
return message
|
||||
|
||||
|
||||
def check_user_valid(**kwargs):
|
||||
password = kwargs.pop('password', None)
|
||||
|
@ -11,6 +49,16 @@ def check_user_valid(**kwargs):
|
|||
username = kwargs.pop('username', None)
|
||||
request = kwargs.get('request')
|
||||
|
||||
# 获取解密密钥,对密码进行解密
|
||||
rsa_private_key = request.session.get('rsa_private_key')
|
||||
if rsa_private_key is not None:
|
||||
try:
|
||||
password = rsa_decrypt(password, rsa_private_key)
|
||||
except Exception as e:
|
||||
logger.error(e, exc_info=True)
|
||||
logger.error('Need decrypt password => {}'.format(password))
|
||||
return None, errors.reason_password_decrypt_failed
|
||||
|
||||
user = authenticate(request, username=username,
|
||||
password=password, public_key=public_key)
|
||||
if not user:
|
||||
|
|
|
@ -22,7 +22,7 @@ from common.utils import get_request_ip, get_object_or_none
|
|||
from users.utils import (
|
||||
redirect_user_first_login_or_index
|
||||
)
|
||||
from .. import forms, mixins, errors
|
||||
from .. import forms, mixins, errors, utils
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -108,9 +108,13 @@ class UserLoginView(mixins.AuthMixin, FormView):
|
|||
return self.form_class
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# 生成加解密密钥对,public_key传递给前端,private_key存入session中供解密使用
|
||||
rsa_private_key, rsa_public_key = utils.gen_key_pair()
|
||||
self.request.session['rsa_private_key'] = rsa_private_key
|
||||
context = {
|
||||
'demo_mode': os.environ.get("DEMO_MODE"),
|
||||
'AUTH_OPENID': settings.AUTH_OPENID,
|
||||
'rsa_public_key': rsa_public_key.replace('\n', '\\n')
|
||||
}
|
||||
kwargs.update(context)
|
||||
return super().get_context_data(**kwargs)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
from django.db.models import Aggregate
|
||||
|
||||
|
||||
class GroupConcat(Aggregate):
|
||||
function = 'GROUP_CONCAT'
|
||||
template = '%(function)s(%(distinct)s %(expressions)s %(order_by)s %(separator))'
|
||||
allow_distinct = False
|
||||
|
||||
def __init__(self, expression, distinct=False, order_by=None, separator=',', **extra):
|
||||
order_by_clause = ''
|
||||
if order_by is not None:
|
||||
order = 'ASC'
|
||||
prefix, body = order_by[1], order_by[1:]
|
||||
if prefix == '-':
|
||||
order = 'DESC'
|
||||
elif prefix == '+':
|
||||
pass
|
||||
else:
|
||||
body = order_by
|
||||
order_by_clause = f'ORDER BY {body} {order}'
|
||||
|
||||
super().__init__(
|
||||
expression,
|
||||
distinct='DISTINCT' if distinct else '',
|
||||
order_by=order_by_clause,
|
||||
separator=f'SEPARATOR {separator}',
|
||||
**extra
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
|
||||
from ..mixins.api import SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin
|
||||
|
||||
|
||||
class JmsGenericViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, GenericViewSet):
|
||||
pass
|
||||
|
||||
|
||||
class JMSModelViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, ModelViewSet):
|
||||
pass
|
|
@ -6,18 +6,27 @@ import chardet
|
|||
import codecs
|
||||
import unicodecsv
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework.parsers import BaseParser
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.exceptions import ParseError, APIException
|
||||
from rest_framework import status
|
||||
|
||||
from common.utils import get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class CsvDataTooBig(APIException):
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
default_code = 'csv_data_too_big'
|
||||
default_detail = _('The max size of CSV is %d bytes')
|
||||
|
||||
|
||||
class JMSCSVParser(BaseParser):
|
||||
"""
|
||||
Parses CSV file to serializer data
|
||||
"""
|
||||
CSV_UPLOAD_MAX_SIZE = 1024 * 1024 * 10
|
||||
|
||||
media_type = 'text/csv'
|
||||
|
||||
|
@ -46,23 +55,31 @@ class JMSCSVParser(BaseParser):
|
|||
return fields_map
|
||||
|
||||
@staticmethod
|
||||
def _process_row(row):
|
||||
def _replace_chinese_quot(str_):
|
||||
trans_table = str.maketrans({
|
||||
'“': '"',
|
||||
'”': '"',
|
||||
'‘': '"',
|
||||
'’': '"',
|
||||
'\'': '"'
|
||||
})
|
||||
return str_.translate(trans_table)
|
||||
|
||||
@classmethod
|
||||
def _process_row(cls, row):
|
||||
"""
|
||||
构建json数据前的行处理
|
||||
"""
|
||||
_row = []
|
||||
|
||||
for col in row:
|
||||
# 列表转换
|
||||
if isinstance(col, str) and col.find("[") != -1 and col.find("]") != -1:
|
||||
# 替换中文格式引号
|
||||
col = col.replace("“", '"').replace("”", '"').\
|
||||
replace("‘", '"').replace('’', '"').replace("'", '"')
|
||||
if isinstance(col, str) and col.startswith('[') and col.endswith(']'):
|
||||
col = cls._replace_chinese_quot(col)
|
||||
col = json.loads(col)
|
||||
# 字典转换
|
||||
if isinstance(col, str) and col.find("{") != -1 and col.find("}") != -1:
|
||||
# 替换中文格式引号
|
||||
col = col.replace("“", '"').replace("”", '"'). \
|
||||
replace("‘", '"').replace('’', '"').replace("'", '"')
|
||||
if isinstance(col, str) and col.startswith("{") and col.endswith("}"):
|
||||
col = cls._replace_chinese_quot(col)
|
||||
col = json.loads(col)
|
||||
_row.append(col)
|
||||
return _row
|
||||
|
@ -82,11 +99,19 @@ class JMSCSVParser(BaseParser):
|
|||
def parse(self, stream, media_type=None, parser_context=None):
|
||||
parser_context = parser_context or {}
|
||||
try:
|
||||
serializer = parser_context["view"].get_serializer()
|
||||
view = parser_context['view']
|
||||
meta = view.request.META
|
||||
serializer = view.get_serializer()
|
||||
except Exception as e:
|
||||
logger.debug(e, exc_info=True)
|
||||
raise ParseError('The resource does not support imports!')
|
||||
|
||||
content_length = int(meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0)))
|
||||
if content_length > self.CSV_UPLOAD_MAX_SIZE:
|
||||
msg = CsvDataTooBig.default_detail % self.CSV_UPLOAD_MAX_SIZE
|
||||
logger.error(msg)
|
||||
raise CsvDataTooBig(msg)
|
||||
|
||||
try:
|
||||
stream_data = stream.read()
|
||||
stream_data = stream_data.strip(codecs.BOM_UTF8)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from rest_framework.serializers import Serializer
|
||||
|
||||
|
||||
class EmptySerializer(Serializer):
|
||||
pass
|
|
@ -1,3 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from rest_framework.exceptions import APIException
|
||||
|
||||
|
||||
class JMSException(APIException):
|
||||
pass
|
||||
|
|
|
@ -4,6 +4,7 @@ import time
|
|||
from hashlib import md5
|
||||
from threading import Thread
|
||||
from collections import defaultdict
|
||||
from itertools import chain
|
||||
|
||||
from django.db.models.signals import m2m_changed
|
||||
from django.core.cache import cache
|
||||
|
@ -15,8 +16,8 @@ from common.drf.filters import IDSpmFilter, CustomFilter, IDInFilter
|
|||
from ..utils import lazyproperty
|
||||
|
||||
__all__ = [
|
||||
"JSONResponseMixin", "CommonApiMixin",
|
||||
'AsyncApiMixin', 'RelationMixin'
|
||||
'JSONResponseMixin', 'CommonApiMixin', 'AsyncApiMixin', 'RelationMixin',
|
||||
'SerializerMixin2', 'QuerySetMixin', 'ExtraFilterFieldsMixin'
|
||||
]
|
||||
|
||||
|
||||
|
@ -54,9 +55,10 @@ class ExtraFilterFieldsMixin:
|
|||
def get_filter_backends(self):
|
||||
if self.filter_backends != self.__class__.filter_backends:
|
||||
return self.filter_backends
|
||||
backends = list(self.filter_backends) + \
|
||||
list(self.default_added_filters) + \
|
||||
list(self.extra_filter_backends)
|
||||
backends = list(chain(
|
||||
self.filter_backends,
|
||||
self.default_added_filters,
|
||||
self.extra_filter_backends))
|
||||
return backends
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
@ -233,3 +235,32 @@ class RelationMixin:
|
|||
def perform_create(self, serializer):
|
||||
instance = serializer.save()
|
||||
self.send_post_add_signal(instance)
|
||||
|
||||
|
||||
class SerializerMixin2:
|
||||
serializer_classes = {}
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.serializer_classes:
|
||||
serializer_class = self.serializer_classes.get(
|
||||
self.action, self.serializer_classes.get('default')
|
||||
)
|
||||
|
||||
if isinstance(serializer_class, dict):
|
||||
serializer_class = serializer_class.get(
|
||||
self.request.method.lower, serializer_class.get('default')
|
||||
)
|
||||
|
||||
assert serializer_class, '`serializer_classes` config error'
|
||||
return serializer_class
|
||||
return super().get_serializer_class()
|
||||
|
||||
|
||||
class QuerySetMixin:
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
serializer_class = self.get_serializer_class()
|
||||
if serializer_class and hasattr(serializer_class, 'setup_eager_loading'):
|
||||
queryset = serializer_class.setup_eager_loading(queryset)
|
||||
|
||||
return queryset
|
||||
|
|
|
@ -226,6 +226,7 @@ class Config(dict):
|
|||
'TERMINAL_COMMAND_STORAGE': {},
|
||||
|
||||
'SECURITY_MFA_AUTH': False,
|
||||
'SECURITY_COMMAND_EXECUTION': True,
|
||||
'SECURITY_SERVICE_ACCOUNT_REGISTRATION': True,
|
||||
'SECURITY_VIEW_AUTH_NEED_MFA': True,
|
||||
'SECURITY_LOGIN_LIMIT_COUNT': 7,
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -22,6 +22,8 @@ logger = get_logger(__file__)
|
|||
|
||||
|
||||
class OrgViewSet(BulkModelViewSet):
|
||||
filter_fields = ('name',)
|
||||
search_fields = ('name', 'comment')
|
||||
queryset = Organization.objects.all()
|
||||
serializer_class = OrgSerializer
|
||||
permission_classes = (IsSuperUserOrAppUser,)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .ticket import *
|
||||
from .request_asset_perm import *
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from django.db.transaction import atomic
|
||||
from django.db.models import F
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from users.models.user import User
|
||||
from common.const.http import POST, GET
|
||||
from common.drf.api import JMSModelViewSet
|
||||
from common.permissions import IsValidUser
|
||||
from common.utils.django import get_object_or_none
|
||||
from common.drf.serializers import EmptySerializer
|
||||
from perms.models.asset_permission import AssetPermission, Asset
|
||||
from assets.models.user import SystemUser
|
||||
from ..exceptions import (
|
||||
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
|
||||
TicketClosed, TicketActionYet, NotHaveConfirmedAssets,
|
||||
NotHaveConfirmedSystemUser
|
||||
)
|
||||
from .. import serializers
|
||||
from ..models import Ticket
|
||||
from ..permissions import IsAssignee
|
||||
|
||||
|
||||
class RequestAssetPermTicketViewSet(JMSModelViewSet):
|
||||
queryset = Ticket.objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
|
||||
serializer_classes = {
|
||||
'default': serializers.RequestAssetPermTicketSerializer,
|
||||
'approve': EmptySerializer,
|
||||
'reject': EmptySerializer,
|
||||
'assignees': serializers.OrgAssigneeSerializer,
|
||||
}
|
||||
permission_classes = (IsValidUser,)
|
||||
filter_fields = ['status', 'title', 'action', 'user_display']
|
||||
search_fields = ['user_display', 'title']
|
||||
|
||||
def _check_can_set_action(self, instance, action):
|
||||
if instance.status == instance.STATUS_CLOSED:
|
||||
raise TicketClosed(detail=_('Ticket closed'))
|
||||
if instance.action == action:
|
||||
action_display = dict(instance.ACTION_CHOICES).get(action)
|
||||
raise TicketActionYet(detail=_('Ticket has %s') % action_display)
|
||||
|
||||
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
|
||||
def assignees(self, request, *args, **kwargs):
|
||||
org_mapper = {}
|
||||
UserTuple = namedtuple('UserTuple', ('id', 'name', 'username'))
|
||||
user = request.user
|
||||
superusers = User.objects.filter(role=User.ROLE_ADMIN)
|
||||
|
||||
admins_with_org = User.objects.filter(related_admin_orgs__users=user).annotate(
|
||||
org_id=F('related_admin_orgs__id'), org_name=F('related_admin_orgs__name')
|
||||
)
|
||||
|
||||
for user in admins_with_org:
|
||||
org_id = user.org_id
|
||||
|
||||
if org_id not in org_mapper:
|
||||
org_mapper[org_id] = {
|
||||
'org_name': user.org_name,
|
||||
'org_admins': set() # 去重
|
||||
}
|
||||
org_mapper[org_id]['org_admins'].add(UserTuple(user.id, user.name, user.username))
|
||||
|
||||
result = [
|
||||
{
|
||||
'org_name': _('Superuser'),
|
||||
'org_admins': set(UserTuple(user.id, user.name, user.username)
|
||||
for user in superusers)
|
||||
}
|
||||
]
|
||||
|
||||
for org in org_mapper.values():
|
||||
result.append(org)
|
||||
serializer_class = self.get_serializer_class()
|
||||
serilizer = serializer_class(instance=result, many=True)
|
||||
return Response(data=serilizer.data)
|
||||
|
||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||
def reject(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
action = instance.ACTION_REJECT
|
||||
self._check_can_set_action(instance, action)
|
||||
instance.perform_action(action, request.user)
|
||||
return Response()
|
||||
|
||||
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
|
||||
def approve(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
action = instance.ACTION_APPROVE
|
||||
self._check_can_set_action(instance, action)
|
||||
|
||||
meta = instance.meta
|
||||
confirmed_assets = meta.get('confirmed_assets', [])
|
||||
assets = list(Asset.objects.filter(id__in=confirmed_assets))
|
||||
if not assets:
|
||||
raise NotHaveConfirmedAssets(detail=_('Confirm assets first'))
|
||||
|
||||
if len(assets) != len(confirmed_assets):
|
||||
raise ConfirmedAssetsChanged(detail=_('Confirmed assets changed'))
|
||||
|
||||
confirmed_system_user = meta.get('confirmed_system_user')
|
||||
if not confirmed_system_user:
|
||||
raise NotHaveConfirmedSystemUser(detail=_('Confirm system-user first'))
|
||||
|
||||
system_user = get_object_or_none(SystemUser, id=confirmed_system_user)
|
||||
if system_user is None:
|
||||
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
|
||||
|
||||
self._create_asset_permission(instance, assets, system_user)
|
||||
return Response({'detail': _('Succeed')})
|
||||
|
||||
def _create_asset_permission(self, instance: Ticket, assets, system_user):
|
||||
meta = instance.meta
|
||||
request = self.request
|
||||
ap_kwargs = {
|
||||
'name': meta.get('name', ''),
|
||||
'created_by': self.request.user.username,
|
||||
'comment': _('{} request assets, approved by {}').format(instance.user_display,
|
||||
instance.assignee_display)
|
||||
}
|
||||
date_start = meta.get('date_start')
|
||||
date_expired = meta.get('date_expired')
|
||||
if date_start:
|
||||
ap_kwargs['date_start'] = date_start
|
||||
if date_expired:
|
||||
ap_kwargs['date_expired'] = date_expired
|
||||
|
||||
with atomic():
|
||||
instance.perform_action(instance.ACTION_APPROVE, request.user)
|
||||
ap = AssetPermission.objects.create(**ap_kwargs)
|
||||
ap.system_users.add(system_user)
|
||||
ap.assets.add(*assets)
|
||||
|
||||
return ap
|
|
@ -0,0 +1,25 @@
|
|||
from common.exceptions import JMSException
|
||||
|
||||
|
||||
class NotHaveConfirmedAssets(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmedAssetsChanged(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class NotHaveConfirmedSystemUser(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmedSystemUserChanged(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class TicketClosed(JMSException):
|
||||
pass
|
||||
|
||||
|
||||
class TicketActionYet(JMSException):
|
||||
pass
|
|
@ -20,9 +20,11 @@ class Ticket(CommonModelMixin):
|
|||
)
|
||||
TYPE_GENERAL = 'general'
|
||||
TYPE_LOGIN_CONFIRM = 'login_confirm'
|
||||
TYPE_REQUEST_ASSET_PERM = 'request_asset'
|
||||
TYPE_CHOICES = (
|
||||
(TYPE_GENERAL, _("General")),
|
||||
(TYPE_LOGIN_CONFIRM, _("Login confirm"))
|
||||
(TYPE_LOGIN_CONFIRM, _("Login confirm")),
|
||||
(TYPE_REQUEST_ASSET_PERM, _('Request asset permission'))
|
||||
)
|
||||
ACTION_APPROVE = 'approve'
|
||||
ACTION_REJECT = 'reject'
|
||||
|
|
|
@ -4,3 +4,6 @@
|
|||
from rest_framework.permissions import BasePermission
|
||||
|
||||
|
||||
class IsAssignee(BasePermission):
|
||||
def has_object_permission(self, request, view, obj):
|
||||
return obj.is_assignee(request.user)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
from .ticket import *
|
||||
from .request_asset_perm import *
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
from rest_framework import serializers
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse
|
||||
from django.db.models import Q
|
||||
|
||||
from users.models.user import User
|
||||
from ..models import Ticket
|
||||
|
||||
|
||||
class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
|
||||
ips = serializers.ListField(child=serializers.IPAddressField(), source='meta.ips',
|
||||
default=list, label=_('IP group'))
|
||||
hostname = serializers.CharField(max_length=256, source='meta.hostname', default=None,
|
||||
allow_blank=True, label=_('Hostname'))
|
||||
system_user = serializers.CharField(max_length=256, source='meta.system_user', default='',
|
||||
allow_blank=True, label=_('System user'))
|
||||
date_start = serializers.DateTimeField(source='meta.date_start', allow_null=True,
|
||||
required=False, label=_('Date start'))
|
||||
date_expired = serializers.DateTimeField(source='meta.date_expired', allow_null=True,
|
||||
required=False, label=_('Date expired'))
|
||||
confirmed_assets = serializers.ListField(child=serializers.UUIDField(),
|
||||
source='meta.confirmed_assets',
|
||||
default=list, required=False,
|
||||
label=_('Confirmed assets'))
|
||||
confirmed_system_user = serializers.ListField(child=serializers.UUIDField(),
|
||||
source='meta.confirmed_system_user',
|
||||
default=list, required=False,
|
||||
label=_('Confirmed system user'))
|
||||
assets_waitlist_url = serializers.SerializerMethodField()
|
||||
system_user_waitlist_url = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
mini_fields = ['id', 'title']
|
||||
small_fields = [
|
||||
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
|
||||
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
|
||||
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
|
||||
'assets_waitlist_url', 'system_user'
|
||||
]
|
||||
m2m_fields = [
|
||||
'user', 'user_display', 'assignees', 'assignees_display',
|
||||
'assignee', 'assignee_display'
|
||||
]
|
||||
|
||||
fields = mini_fields + small_fields + m2m_fields
|
||||
read_only_fields = [
|
||||
'user_display', 'assignees_display', 'type', 'user', 'status',
|
||||
'date_created', 'date_updated', 'action', 'id', 'assignee',
|
||||
'assignee_display',
|
||||
]
|
||||
extra_kwargs = {
|
||||
'status': {'label': _('Status')},
|
||||
'action': {'label': _('Action')},
|
||||
'user_display': {'label': _('User')}
|
||||
}
|
||||
|
||||
def validate_assignees(self, assignees):
|
||||
user = self.context['request'].user
|
||||
|
||||
count = User.objects.filter(Q(related_admin_orgs__users=user) | Q(role=User.ROLE_ADMIN)).filter(
|
||||
id__in=[assignee.id for assignee in assignees]).distinct().count()
|
||||
|
||||
if count != len(assignees):
|
||||
raise serializers.ValidationError(_('Must be organization admin or superuser'))
|
||||
return assignees
|
||||
|
||||
def get_system_user_waitlist_url(self, instance: Ticket):
|
||||
if not self._is_assignee(instance):
|
||||
return None
|
||||
meta = instance.meta
|
||||
url = reverse('api-assets:system-user-list')
|
||||
query = meta.get('system_user', '')
|
||||
return '{}?search={}'.format(url, query)
|
||||
|
||||
def get_assets_waitlist_url(self, instance: Ticket):
|
||||
if not self._is_assignee(instance):
|
||||
return None
|
||||
|
||||
asset_api = reverse('api-assets:asset-list')
|
||||
query = ''
|
||||
|
||||
meta = instance.meta
|
||||
ips = meta.get('ips', [])
|
||||
hostname = meta.get('hostname')
|
||||
|
||||
if ips:
|
||||
query = '?ips=%s' % ','.join(ips)
|
||||
elif hostname:
|
||||
query = '?search=%s' % hostname
|
||||
|
||||
return asset_api + query
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
|
||||
validated_data['user'] = self.context['request'].user
|
||||
self._pop_confirmed_fields()
|
||||
return super().create(validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
meta = self.validated_data.get('meta', {})
|
||||
date_start = meta.get('date_start')
|
||||
if date_start:
|
||||
meta['date_start'] = date_start.strftime('%Y-%m-%d %H:%M:%S%z')
|
||||
|
||||
date_expired = meta.get('date_expired')
|
||||
if date_expired:
|
||||
meta['date_expired'] = date_expired.strftime('%Y-%m-%d %H:%M:%S%z')
|
||||
return super().save(**kwargs)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
new_meta = validated_data['meta']
|
||||
if not self._is_assignee(instance):
|
||||
self._pop_confirmed_fields()
|
||||
old_meta = instance.meta
|
||||
meta = {}
|
||||
meta.update(old_meta)
|
||||
meta.update(new_meta)
|
||||
validated_data['meta'] = meta
|
||||
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def _pop_confirmed_fields(self):
|
||||
meta = self.validated_data['meta']
|
||||
meta.pop('confirmed_assets', None)
|
||||
meta.pop('confirmed_system_user', None)
|
||||
|
||||
def _is_assignee(self, obj: Ticket):
|
||||
user = self.context['request'].user
|
||||
return obj.is_assignee(user)
|
||||
|
||||
|
||||
class AssigneeSerializer(serializers.Serializer):
|
||||
id = serializers.UUIDField()
|
||||
name = serializers.CharField()
|
||||
username = serializers.CharField()
|
||||
|
||||
|
||||
class OrgAssigneeSerializer(serializers.Serializer):
|
||||
org_name = serializers.CharField()
|
||||
org_admins = AssigneeSerializer(many=True)
|
|
@ -7,6 +7,7 @@ from .. import api
|
|||
app_name = 'tickets'
|
||||
router = BulkRouter()
|
||||
|
||||
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
|
||||
router.register('tickets', api.TicketViewSet, 'ticket')
|
||||
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
|||
|
||||
from rest_framework import generics
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.conf import settings
|
||||
|
||||
from common.permissions import (
|
||||
IsCurrentUserOrReadOnly
|
||||
|
@ -64,8 +65,9 @@ class UserProfileApi(generics.RetrieveUpdateAPIView):
|
|||
return self.request.user
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
age = request.session.get_expiry_age()
|
||||
request.session.set_expiry(age)
|
||||
if not settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
||||
age = request.session.get_expiry_age()
|
||||
request.session.set_expiry(age)
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ from ..serializers import UserSerializer, UserRetrieveSerializer
|
|||
from .mixins import UserQuerysetMixin
|
||||
from ..models import User
|
||||
from ..signals import post_user_create
|
||||
from ..filters import OrgRoleUserFilterBackend
|
||||
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
@ -35,6 +36,7 @@ class UserViewSet(CommonApiMixin, UserQuerysetMixin, BulkModelViewSet):
|
|||
'default': UserSerializer,
|
||||
'retrieve': UserRetrieveSerializer
|
||||
}
|
||||
extra_filter_backends = [OrgRoleUserFilterBackend]
|
||||
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().prefetch_related('groups')
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from rest_framework.compat import coreapi, coreschema
|
||||
from rest_framework import filters
|
||||
|
||||
from users.models.user import User
|
||||
from orgs.utils import current_org
|
||||
|
||||
|
||||
class OrgRoleUserFilterBackend(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
org_role = request.query_params.get('org_role')
|
||||
if not org_role:
|
||||
return queryset
|
||||
|
||||
if org_role == 'admins':
|
||||
return queryset & (current_org.get_org_admins() | User.objects.filter(role=User.ROLE_ADMIN))
|
||||
elif org_role == 'auditors':
|
||||
return queryset & current_org.get_org_auditors()
|
||||
elif org_role == 'users':
|
||||
return queryset & current_org.get_org_users()
|
||||
elif org_role == 'members':
|
||||
return queryset & current_org.get_org_members()
|
||||
|
||||
def get_schema_fields(self, view):
|
||||
return [
|
||||
coreapi.Field(
|
||||
name='org_role', location='query', required=False, type='string',
|
||||
schema=coreschema.String(
|
||||
title='Organization role users',
|
||||
description='Organization role users can be {admins|auditors|users|members}'
|
||||
)
|
||||
)
|
||||
]
|
|
@ -42,14 +42,7 @@ class UserGroupSerializer(BulkOrgResourceModelSerializer):
|
|||
def set_fields_queryset(self):
|
||||
users_field = self.fields.get('users')
|
||||
if users_field:
|
||||
users_field.child_relation.queryset = utils.get_current_org_members(exclude=('Auditor',))
|
||||
|
||||
def validate_users(self, users):
|
||||
for user in users:
|
||||
if user.is_super_auditor:
|
||||
msg = _('Auditors cannot be join in the user group')
|
||||
raise serializers.ValidationError(msg)
|
||||
return users
|
||||
users_field.child_relation.queryset = utils.get_current_org_members()
|
||||
|
||||
@classmethod
|
||||
def setup_eager_loading(cls, queryset):
|
||||
|
|
|
@ -320,3 +320,9 @@ class UserUpdatePublicKeySerializer(serializers.ModelSerializer):
|
|||
new_public_key = self.validated_data.get('public_key')
|
||||
instance.set_public_key(new_public_key)
|
||||
return instance
|
||||
|
||||
|
||||
class MiniUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'name', 'username']
|
||||
|
|
Loading…
Reference in New Issue