mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-06-20 20:22:25 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
df05973da3 | ||
|
d2a4e79b31 | ||
|
f65e2ac15f | ||
|
96f69c821b | ||
|
13fffa52dd | ||
|
fe37913ed9 | ||
|
4009457000 | ||
|
6c9c64a55f | ||
|
24949c6013 | ||
|
d02e56da78 | ||
|
9b6c1806af | ||
|
ed640e094f | ||
|
f21272af7d | ||
|
93cd00702d | ||
|
115550793e | ||
|
db28e98a02 | ||
|
1851783dbd | ||
|
8648b131f2 | ||
|
43112eaa8f | ||
|
9dbbc57454 | ||
|
a0d8c297b0 | ||
|
e15c5b853e | ||
|
e015dd7bcb | ||
|
8f59e49099 | ||
|
316df6f9d9 | ||
|
fcfd7bb469 | ||
|
ed0932deea | ||
|
c4f76c5512 | ||
|
7f3426fecf | ||
|
8e08e291a0 |
@ -22,6 +22,7 @@ ARG DEPENDENCIES=" \
|
|||||||
libpq-dev \
|
libpq-dev \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
|
libkrb5-dev \
|
||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
libsasl2-dev \
|
libsasl2-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
@ -95,7 +96,6 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
|||||||
pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl; \
|
pip install https://download.jumpserver.org/pypi/simple/cryptography/cryptography-38.0.4-cp39-cp39-linux_loongarch64.whl; \
|
||||||
pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl; \
|
pip install https://download.jumpserver.org/pypi/simple/greenlet/greenlet-1.1.2-cp39-cp39-linux_loongarch64.whl; \
|
||||||
pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl; \
|
pip install https://download.jumpserver.org/pypi/simple/PyNaCl/PyNaCl-1.5.0-cp39-cp39-linux_loongarch64.whl; \
|
||||||
pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl; \
|
|
||||||
fi \
|
fi \
|
||||||
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
&& pip install $(grep -E 'jms|jumpserver' requirements/requirements.txt) -i ${PIP_JMS_MIRROR} \
|
||||||
&& pip install -r requirements/requirements.txt
|
&& pip install -r requirements/requirements.txt
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
||||||
FROM jumpserver/core:${VERSION}
|
FROM jumpserver/core:${VERSION}
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
WORKDIR /opt/jumpserver
|
||||||
|
ARG ORACLE_VERSION=1.4.0b1
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
set -ex \
|
set -ex \
|
||||||
|
&& \
|
||||||
|
if [ "${TARGETARCH}" == "amd64" ] || [ "${TARGETARCH}" == "arm64" ] || [ "${TARGETARCH}" == "loong64" ]; then \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/oracledb/oracledb-${ORACLE_VERSION}-cp39-cp39-linux_$(uname -m).whl; \
|
||||||
|
fi \
|
||||||
|
&& \
|
||||||
|
if [ "${TARGETARCH}" == "loong64" ]; then \
|
||||||
|
pip install https://download.jumpserver.org/pypi/simple/grpcio/grpcio-1.54.2-cp39-cp39-linux_loongarch64.whl; \
|
||||||
|
fi \
|
||||||
&& pip install -r requirements/requirements_xpack.txt
|
&& pip install -r requirements/requirements_xpack.txt
|
||||||
|
@ -1,10 +1,41 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test privileged account
|
- name: "Test privileged {{ jms_account.username }} account"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
|
|
||||||
- name: Change password
|
- name: "Check if {{ account.username }} user exists"
|
||||||
|
getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ account.username }}"
|
||||||
|
register: user_info
|
||||||
|
ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败
|
||||||
|
|
||||||
|
- name: "Add {{ account.username }} user"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
shell: "{{ params.shell }}"
|
||||||
|
home: "{{ params.home | default('/home/' + account.username, true) }}"
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
expires: -1
|
||||||
|
state: present
|
||||||
|
when: user_info.failed
|
||||||
|
|
||||||
|
- name: "Add {{ account.username }} group"
|
||||||
|
ansible.builtin.group:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
state: present
|
||||||
|
when: user_info.failed
|
||||||
|
|
||||||
|
- name: "Add {{ account.username }} user to group"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: "{{ account.username }}"
|
||||||
|
groups: "{{ params.groups }}"
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.groups
|
||||||
|
|
||||||
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
@ -12,11 +43,6 @@
|
|||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: "{{ ssh_params.dest }}"
|
dest: "{{ ssh_params.dest }}"
|
||||||
@ -26,17 +52,28 @@
|
|||||||
- account.secret_type == "ssh_key"
|
- account.secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- ssh_params.strategy == "set_jms"
|
||||||
|
|
||||||
- name: Change SSH key
|
- name: "Change {{ account.username }} SSH key"
|
||||||
ansible.builtin.authorized_key:
|
ansible.builtin.authorized_key:
|
||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
- name: Verify password
|
- name: "Verify {{ account.username }} password"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
become: no
|
become: no
|
||||||
vars:
|
vars:
|
||||||
@ -45,7 +82,7 @@
|
|||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: Verify SSH key
|
- name: "Verify {{ account.username }} SSH key"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
become: no
|
become: no
|
||||||
vars:
|
vars:
|
||||||
|
@ -9,7 +9,7 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: "{{ 'Params sudo help text' | trans }}"
|
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Test privileged account
|
- name: "Test privileged {{ jms_account.username }} account"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
|
|
||||||
- name: Check user
|
- name: "Check if {{ account.username }} user exists"
|
||||||
|
getent:
|
||||||
|
database: passwd
|
||||||
|
key: "{{ account.username }}"
|
||||||
|
register: user_info
|
||||||
|
ignore_errors: yes # 忽略错误,如果用户不存在时不会导致playbook失败
|
||||||
|
|
||||||
|
- name: "Add {{ account.username }} user"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
shell: "{{ params.shell }}"
|
shell: "{{ params.shell }}"
|
||||||
@ -12,19 +19,23 @@
|
|||||||
groups: "{{ params.groups }}"
|
groups: "{{ params.groups }}"
|
||||||
expires: -1
|
expires: -1
|
||||||
state: present
|
state: present
|
||||||
|
when: user_info.failed
|
||||||
|
|
||||||
- name: "Add {{ account.username }} group"
|
- name: "Add {{ account.username }} group"
|
||||||
ansible.builtin.group:
|
ansible.builtin.group:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
state: present
|
state: present
|
||||||
|
when: user_info.failed
|
||||||
|
|
||||||
- name: Add user groups
|
- name: "Add {{ account.username }} user to group"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
groups: "{{ params.groups }}"
|
groups: "{{ params.groups }}"
|
||||||
when: params.groups
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.groups
|
||||||
|
|
||||||
- name: Change password
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
@ -32,11 +43,6 @@
|
|||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: create user If it already exists, no operation will be performed
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
when: account.secret_type == "ssh_key"
|
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: "{{ ssh_params.dest }}"
|
dest: "{{ ssh_params.dest }}"
|
||||||
@ -46,14 +52,14 @@
|
|||||||
- account.secret_type == "ssh_key"
|
- account.secret_type == "ssh_key"
|
||||||
- ssh_params.strategy == "set_jms"
|
- ssh_params.strategy == "set_jms"
|
||||||
|
|
||||||
- name: Change SSH key
|
- name: "Change {{ account.username }} SSH key"
|
||||||
ansible.builtin.authorized_key:
|
ansible.builtin.authorized_key:
|
||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
- name: Set sudo setting
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
ansible.builtin.lineinfile:
|
ansible.builtin.lineinfile:
|
||||||
dest: /etc/sudoers
|
dest: /etc/sudoers
|
||||||
state: present
|
state: present
|
||||||
@ -61,12 +67,13 @@
|
|||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
validate: visudo -cf %s
|
validate: visudo -cf %s
|
||||||
when:
|
when:
|
||||||
|
- user_info.failed
|
||||||
- params.sudo
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
- name: Verify password
|
- name: "Verify {{ account.username }} password"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
become: no
|
become: no
|
||||||
vars:
|
vars:
|
||||||
@ -75,7 +82,7 @@
|
|||||||
ansible_become: no
|
ansible_become: no
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: Verify SSH key
|
- name: "Verify {{ account.username }} SSH key"
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
become: no
|
become: no
|
||||||
vars:
|
vars:
|
||||||
|
@ -10,7 +10,7 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: "{{ 'Params sudo help text' | trans }}"
|
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
|
@ -262,7 +262,7 @@ class BasePlaybookManager:
|
|||||||
info = self.file_to_json(runner.inventory)
|
info = self.file_to_json(runner.inventory)
|
||||||
servers, not_valid = [], []
|
servers, not_valid = [], []
|
||||||
for k, host in info['all']['hosts'].items():
|
for k, host in info['all']['hosts'].items():
|
||||||
jms_asset, jms_gateway = host['jms_asset'], host.get('gateway')
|
jms_asset, jms_gateway = host.get('jms_asset'), host.get('gateway')
|
||||||
if not jms_gateway:
|
if not jms_gateway:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ def migrate_asset_accounts(apps, schema_editor):
|
|||||||
count += len(auth_books)
|
count += len(auth_books)
|
||||||
# auth book 和 account 相同的属性
|
# auth book 和 account 相同的属性
|
||||||
same_attrs = [
|
same_attrs = [
|
||||||
'id', 'username', 'comment', 'date_created', 'date_updated',
|
'username', 'comment', 'date_created', 'date_updated',
|
||||||
'created_by', 'asset_id', 'org_id',
|
'created_by', 'asset_id', 'org_id',
|
||||||
]
|
]
|
||||||
# 认证的属性,可能是 auth_book 的,可能是 system_user 的
|
# 认证的属性,可能是 auth_book 的,可能是 system_user 的
|
||||||
|
@ -403,12 +403,7 @@ class NodeAssetsMixin(NodeAllAssetsMappingMixin):
|
|||||||
return Asset.objects.filter(q).distinct()
|
return Asset.objects.filter(q).distinct()
|
||||||
|
|
||||||
def get_assets_amount(self):
|
def get_assets_amount(self):
|
||||||
q = Q(node__key__startswith=f'{self.key}:') | Q(node__key=self.key)
|
return self.get_all_assets().count()
|
||||||
return self.assets.through.objects.filter(q).count()
|
|
||||||
|
|
||||||
def get_assets_account_by_children(self):
|
|
||||||
children = self.get_all_children().values_list()
|
|
||||||
return self.assets.through.objects.filter(node_id__in=children).count()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_all_assets_by_key_v2(cls, key):
|
def get_node_all_assets_by_key_v2(cls, key):
|
||||||
|
@ -157,6 +157,8 @@ class AssetSerializer(BulkOrgResourceModelSerializer, WritableNestedModelSeriali
|
|||||||
def _extract_accounts(self):
|
def _extract_accounts(self):
|
||||||
if not getattr(self, 'initial_data', None):
|
if not getattr(self, 'initial_data', None):
|
||||||
return
|
return
|
||||||
|
if isinstance(self.initial_data, list):
|
||||||
|
return
|
||||||
accounts = self.initial_data.pop('accounts', None)
|
accounts = self.initial_data.pop('accounts', None)
|
||||||
self._accounts = accounts
|
self._accounts = accounts
|
||||||
|
|
||||||
|
@ -22,7 +22,12 @@ class KubernetesClient:
|
|||||||
@property
|
@property
|
||||||
def api(self):
|
def api(self):
|
||||||
configuration = client.Configuration()
|
configuration = client.Configuration()
|
||||||
configuration.host = self.url
|
scheme = urlparse(self.url).scheme
|
||||||
|
if not self.server:
|
||||||
|
host = self.url
|
||||||
|
else:
|
||||||
|
host = f'{scheme}://127.0.0.1:{self.server.local_bind_port}'
|
||||||
|
configuration.host = host
|
||||||
configuration.verify_ssl = False
|
configuration.verify_ssl = False
|
||||||
configuration.api_key = {"authorization": "Bearer " + self.token}
|
configuration.api_key = {"authorization": "Bearer " + self.token}
|
||||||
c = api_client.ApiClient(configuration=configuration)
|
c = api_client.ApiClient(configuration=configuration)
|
||||||
|
@ -306,9 +306,6 @@ class ConnectionTokenViewSet(ExtraActionApiMixin, RootOrgViewMixin, JMSModelView
|
|||||||
|
|
||||||
if account.username != AliasAccount.INPUT:
|
if account.username != AliasAccount.INPUT:
|
||||||
data['input_username'] = ''
|
data['input_username'] = ''
|
||||||
elif account.username == AliasAccount.USER:
|
|
||||||
data['input_username'] = user.username
|
|
||||||
|
|
||||||
ticket = self._validate_acl(user, asset, account)
|
ticket = self._validate_acl(user, asset, account)
|
||||||
if ticket:
|
if ticket:
|
||||||
data['from_ticket'] = ticket
|
data['from_ticket'] = ticket
|
||||||
|
@ -52,7 +52,11 @@ class UserResetPasswordSendCodeApi(CreateAPIView):
|
|||||||
other_args = {}
|
other_args = {}
|
||||||
|
|
||||||
target = serializer.validated_data[form_type]
|
target = serializer.validated_data[form_type]
|
||||||
query_key = 'phone' if form_type == 'sms' else form_type
|
if form_type == 'sms':
|
||||||
|
query_key = 'phone'
|
||||||
|
target = target.lstrip('+')
|
||||||
|
else:
|
||||||
|
query_key = form_type
|
||||||
user, err = self.is_valid_user(username=username, **{query_key: target})
|
user, err = self.is_valid_user(username=username, **{query_key: target})
|
||||||
if not user:
|
if not user:
|
||||||
return Response({'error': err}, status=400)
|
return Response({'error': err}, status=400)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from common.permissions import ServiceAccountSignaturePermission
|
||||||
from .base import JMSBaseAuthBackend
|
from .base import JMSBaseAuthBackend
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
@ -18,6 +19,10 @@ class PublicKeyAuthBackend(JMSBaseAuthBackend):
|
|||||||
def authenticate(self, request, username=None, public_key=None, **kwargs):
|
def authenticate(self, request, username=None, public_key=None, **kwargs):
|
||||||
if not public_key:
|
if not public_key:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
permission = ServiceAccountSignaturePermission()
|
||||||
|
if not permission.has_permission(request, None):
|
||||||
|
return None
|
||||||
if username is None:
|
if username is None:
|
||||||
username = kwargs.get(UserModel.USERNAME_FIELD)
|
username = kwargs.get(UserModel.USERNAME_FIELD)
|
||||||
try:
|
try:
|
||||||
|
@ -225,9 +225,20 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||||||
account.asset = self.asset
|
account.asset = self.asset
|
||||||
account.org_id = self.asset.org_id
|
account.org_id = self.asset.org_id
|
||||||
|
|
||||||
if self.account in [AliasAccount.INPUT, AliasAccount.USER]:
|
# 手动账号
|
||||||
|
if self.account == AliasAccount.INPUT:
|
||||||
account.username = self.input_username
|
account.username = self.input_username
|
||||||
account.secret = self.input_secret
|
account.secret = self.input_secret
|
||||||
|
|
||||||
|
# 同名账号
|
||||||
|
elif self.account == AliasAccount.USER:
|
||||||
|
account.username = self.user.username
|
||||||
|
account.secret = self.input_secret
|
||||||
|
|
||||||
|
# 匿名账号
|
||||||
|
elif self.account == AliasAccount.ANON:
|
||||||
|
account.username = ''
|
||||||
|
account.secret = ''
|
||||||
else:
|
else:
|
||||||
account = self.asset.accounts.filter(name=self.account).first()
|
account = self.asset.accounts.filter(name=self.account).first()
|
||||||
if not account.secret and self.input_secret:
|
if not account.secret and self.input_secret:
|
||||||
|
@ -12,7 +12,7 @@ from common.utils import get_object_or_none
|
|||||||
from orgs.utils import tmp_to_root_org
|
from orgs.utils import tmp_to_root_org
|
||||||
|
|
||||||
|
|
||||||
class IsValidUser(permissions.IsAuthenticated, permissions.BasePermission):
|
class IsValidUser(permissions.IsAuthenticated):
|
||||||
"""Allows access to valid user, is active and not expired"""
|
"""Allows access to valid user, is active and not expired"""
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
@ -86,3 +86,38 @@ class UserConfirmation(permissions.BasePermission):
|
|||||||
min_level = ConfirmType.values.index(confirm_type) + 1
|
min_level = ConfirmType.values.index(confirm_type) + 1
|
||||||
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
|
name = 'UserConfirmationLevel{}TTL{}'.format(min_level, ttl)
|
||||||
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
|
return type(name, (cls,), {'min_level': min_level, 'ttl': ttl, 'confirm_type': confirm_type})
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceAccountSignaturePermission(permissions.BasePermission):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
from authentication.models import AccessKey
|
||||||
|
from common.utils.crypto import get_aes_crypto
|
||||||
|
signature = request.META.get('HTTP_X_JMS_SVC', '')
|
||||||
|
if not signature or not signature.startswith('Sign'):
|
||||||
|
return False
|
||||||
|
data = signature[4:].strip()
|
||||||
|
if not data or ':' not in data:
|
||||||
|
return False
|
||||||
|
ak_id, time_sign = data.split(':', 1)
|
||||||
|
if not ak_id or not time_sign:
|
||||||
|
return False
|
||||||
|
ak = AccessKey.objects.filter(id=ak_id).first()
|
||||||
|
if not ak or not ak.is_active:
|
||||||
|
return False
|
||||||
|
if not ak.user or not ak.user.is_active or not ak.user.is_service_account:
|
||||||
|
return False
|
||||||
|
aes = get_aes_crypto(str(ak.secret).replace('-', ''), mode='ECB')
|
||||||
|
try:
|
||||||
|
timestamp = aes.decrypt(time_sign)
|
||||||
|
if not timestamp or not timestamp.isdigit():
|
||||||
|
return False
|
||||||
|
timestamp = int(timestamp)
|
||||||
|
interval = abs(int(time.time()) - timestamp)
|
||||||
|
if interval > 30:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
return False
|
||||||
|
@ -117,8 +117,8 @@ class FeiShu(RequestMixin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
body = {
|
body = {
|
||||||
'msg_type': 'text',
|
'msg_type': 'interactive',
|
||||||
'content': json.dumps({'text': msg})
|
'content': json.dumps({'elements': [{'tag': 'markdown', 'content': msg}]})
|
||||||
}
|
}
|
||||||
|
|
||||||
invalid_users = []
|
invalid_users = []
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
import struct
|
|
||||||
import random
|
import random
|
||||||
import socket
|
import socket
|
||||||
import string
|
import string
|
||||||
|
import struct
|
||||||
|
|
||||||
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
|
string_punctuation = '!#$%&()*+,-.:;<=>?@[]^_~'
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ def random_ip():
|
|||||||
|
|
||||||
|
|
||||||
def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
|
def random_string(length: int, lower=True, upper=True, digit=True, special_char=False):
|
||||||
|
random.seed(None)
|
||||||
args_names = ['lower', 'upper', 'digit', 'special_char']
|
args_names = ['lower', 'upper', 'digit', 'special_char']
|
||||||
args_values = [lower, upper, digit, special_char]
|
args_values = [lower, upper, digit, special_char]
|
||||||
args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]
|
args_string = [string.ascii_lowercase, string.ascii_uppercase, string.digits, string_punctuation]
|
||||||
|
@ -26,6 +26,7 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
self.target = target
|
self.target = target
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.key = key or self.KEY_TMPL.format(target)
|
self.key = key or self.KEY_TMPL.format(target)
|
||||||
|
self.verify_key = self.key + '_verify'
|
||||||
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
|
self.timeout = settings.VERIFY_CODE_TTL if timeout is None else timeout
|
||||||
self.other_args = kwargs
|
self.other_args = kwargs
|
||||||
|
|
||||||
@ -47,6 +48,11 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
def verify(self, code):
|
def verify(self, code):
|
||||||
|
times = cache.get(self.verify_key, 0)
|
||||||
|
if times >= 3:
|
||||||
|
self.__clear()
|
||||||
|
raise CodeExpired
|
||||||
|
cache.set(self.verify_key, times + 1, timeout=self.timeout)
|
||||||
right = cache.get(self.key)
|
right = cache.get(self.key)
|
||||||
if not right:
|
if not right:
|
||||||
raise CodeExpired
|
raise CodeExpired
|
||||||
@ -59,6 +65,7 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
|
|
||||||
def __clear(self):
|
def __clear(self):
|
||||||
cache.delete(self.key)
|
cache.delete(self.key)
|
||||||
|
cache.delete(self.verify_key)
|
||||||
|
|
||||||
def __ttl(self):
|
def __ttl(self):
|
||||||
return cache.ttl(self.key)
|
return cache.ttl(self.key)
|
||||||
|
@ -561,6 +561,9 @@ class Config(dict):
|
|||||||
|
|
||||||
# FTP 文件上传下载备份阈值,单位(M),当值小于等于0时,不备份
|
# FTP 文件上传下载备份阈值,单位(M),当值小于等于0时,不备份
|
||||||
'FTP_FILE_MAX_STORE': 100,
|
'FTP_FILE_MAX_STORE': 100,
|
||||||
|
|
||||||
|
# API 请求次数限制
|
||||||
|
'MAX_LIMIT_PER_PAGE': 100
|
||||||
}
|
}
|
||||||
|
|
||||||
old_config_map = {
|
old_config_map = {
|
||||||
|
@ -3,4 +3,4 @@ from rest_framework.pagination import LimitOffsetPagination
|
|||||||
|
|
||||||
|
|
||||||
class MaxLimitOffsetPagination(LimitOffsetPagination):
|
class MaxLimitOffsetPagination(LimitOffsetPagination):
|
||||||
max_limit = settings.MAX_LIMIT_PER_PAGE or 100
|
max_limit = settings.MAX_LIMIT_PER_PAGE
|
||||||
|
@ -16,6 +16,8 @@ def allow_access(private_file):
|
|||||||
path_base = path_list[1] if len(path_list) > 1 else None
|
path_base = path_list[1] if len(path_list) > 1 else None
|
||||||
path_perm = path_perms_map.get(path_base, None)
|
path_perm = path_perms_map.get(path_base, None)
|
||||||
|
|
||||||
|
if ".." in request_path:
|
||||||
|
return False
|
||||||
if not path_perm:
|
if not path_perm:
|
||||||
return False
|
return False
|
||||||
if path_perm == '*' or request.user.has_perms([path_perm]):
|
if path_perm == '*' or request.user.has_perms([path_perm]):
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-20 18:40+0800\n"
|
"POT-Creation-Date: 2023-07-26 19:26+0800\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -646,7 +646,7 @@ msgstr "アカウントはすでに存在しています"
|
|||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr "ID"
|
msgstr "ID"
|
||||||
|
|
||||||
#: accounts/serializers/account/account.py:427 acls/serializers/base.py:116
|
#: accounts/serializers/account/account.py:430 acls/serializers/base.py:116
|
||||||
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49
|
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49
|
||||||
#: audits/models.py:85 audits/models.py:163
|
#: audits/models.py:85 audits/models.py:163
|
||||||
#: authentication/models/connection_token.py:32
|
#: authentication/models/connection_token.py:32
|
||||||
@ -664,7 +664,7 @@ msgstr "ID"
|
|||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "ユーザー"
|
msgstr "ユーザー"
|
||||||
|
|
||||||
#: accounts/serializers/account/account.py:428
|
#: accounts/serializers/account/account.py:431
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:33
|
#: authentication/templates/authentication/_access_key_modal.html:33
|
||||||
#: terminal/notifications.py:158 terminal/notifications.py:207
|
#: terminal/notifications.py:158 terminal/notifications.py:207
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1b88bc1c5216d7cfc2b0a72d889198bcab84ddd40dd3f5a13a5662dfcf8170ee
|
oid sha256:5a68f334539c7511584c11770699829dc709c7e1b453365964a3b3852d5edf92
|
||||||
size 121846
|
size 121846
|
||||||
|
@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-07-20 18:40+0800\n"
|
"POT-Creation-Date: 2023-07-26 19:26+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
@ -642,7 +642,7 @@ msgstr "账号已存在"
|
|||||||
msgid "ID"
|
msgid "ID"
|
||||||
msgstr "ID"
|
msgstr "ID"
|
||||||
|
|
||||||
#: accounts/serializers/account/account.py:427 acls/serializers/base.py:116
|
#: accounts/serializers/account/account.py:430 acls/serializers/base.py:116
|
||||||
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49
|
#: assets/models/cmd_filter.py:24 assets/models/label.py:16 audits/models.py:49
|
||||||
#: audits/models.py:85 audits/models.py:163
|
#: audits/models.py:85 audits/models.py:163
|
||||||
#: authentication/models/connection_token.py:32
|
#: authentication/models/connection_token.py:32
|
||||||
@ -660,7 +660,7 @@ msgstr "ID"
|
|||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "用户"
|
msgstr "用户"
|
||||||
|
|
||||||
#: accounts/serializers/account/account.py:428
|
#: accounts/serializers/account/account.py:431
|
||||||
#: authentication/templates/authentication/_access_key_modal.html:33
|
#: authentication/templates/authentication/_access_key_modal.html:33
|
||||||
#: terminal/notifications.py:158 terminal/notifications.py:207
|
#: terminal/notifications.py:158 terminal/notifications.py:207
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
@ -4798,7 +4798,7 @@ msgid ""
|
|||||||
"Session, record, command will be delete if more than duration, only in "
|
"Session, record, command will be delete if more than duration, only in "
|
||||||
"database, OSS will not be affected."
|
"database, OSS will not be affected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"会话、录像,命令记录超过该时长将会被洲除(影响数据库存備,OSS 等不受影响)"
|
"会话、录像,命令记录超过该时长将会被清除(影响数据库存储,OSS 等不受影响)"
|
||||||
|
|
||||||
#: settings/serializers/cleaning.py:36
|
#: settings/serializers/cleaning.py:36
|
||||||
msgid "Activity log keep days (day)"
|
msgid "Activity log keep days (day)"
|
||||||
|
@ -270,8 +270,13 @@ class JMSInventory:
|
|||||||
name = host.pop('name')
|
name = host.pop('name')
|
||||||
name = name.replace('[', '_').replace(']', '_')
|
name = name.replace('[', '_').replace(']', '_')
|
||||||
data['all']['hosts'][name] = host
|
data['all']['hosts'][name] = host
|
||||||
if self.exclude_localhost and data['all']['hosts'].__contains__('localhost'):
|
if not self.exclude_localhost:
|
||||||
data['all']['hosts'].update({'localhost': {'ansible_host': '255.255.255.255'}})
|
data['all']['hosts'].update({
|
||||||
|
'localhost': {
|
||||||
|
'ansible_host': '127.0.0.1',
|
||||||
|
'ansible_connection': 'local'
|
||||||
|
}
|
||||||
|
})
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def write_to_file(self, path):
|
def write_to_file(self, path):
|
||||||
|
@ -3,6 +3,7 @@ import shutil
|
|||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -18,6 +19,7 @@ __all__ = ["PlaybookViewSet", "PlaybookFileBrowserAPIView"]
|
|||||||
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from django.utils._os import safe_join
|
||||||
|
|
||||||
|
|
||||||
def unzip_playbook(src, dist):
|
def unzip_playbook(src, dist):
|
||||||
@ -37,7 +39,7 @@ class PlaybookViewSet(OrgBulkModelViewSet):
|
|||||||
raise JMSException(code='playbook_has_job', detail={"msg": _("Currently playbook is being used in a job")})
|
raise JMSException(code='playbook_has_job', detail={"msg": _("Currently playbook is being used in a job")})
|
||||||
instance_id = instance.id
|
instance_id = instance.id
|
||||||
super().perform_destroy(instance)
|
super().perform_destroy(instance)
|
||||||
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__())
|
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance_id.__str__())
|
||||||
shutil.rmtree(dest_path)
|
shutil.rmtree(dest_path)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -48,8 +50,8 @@ class PlaybookViewSet(OrgBulkModelViewSet):
|
|||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
instance = serializer.save()
|
instance = serializer.save()
|
||||||
if 'multipart/form-data' in self.request.headers['Content-Type']:
|
if 'multipart/form-data' in self.request.headers['Content-Type']:
|
||||||
src_path = os.path.join(settings.MEDIA_ROOT, instance.path.name)
|
src_path = safe_join(settings.MEDIA_ROOT, instance.path.name)
|
||||||
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
||||||
try:
|
try:
|
||||||
unzip_playbook(src_path, dest_path)
|
unzip_playbook(src_path, dest_path)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
@ -60,9 +62,9 @@ class PlaybookViewSet(OrgBulkModelViewSet):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
if instance.create_method == 'blank':
|
if instance.create_method == 'blank':
|
||||||
dest_path = os.path.join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
dest_path = safe_join(settings.DATA_DIR, "ops", "playbook", instance.id.__str__())
|
||||||
os.makedirs(dest_path)
|
os.makedirs(dest_path)
|
||||||
with open(os.path.join(dest_path, 'main.yml'), 'w') as f:
|
with open(safe_join(dest_path, 'main.yml'), 'w') as f:
|
||||||
f.write('## write your playbook here')
|
f.write('## write your playbook here')
|
||||||
|
|
||||||
|
|
||||||
@ -83,12 +85,14 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
work_path = playbook.work_dir
|
work_path = playbook.work_dir
|
||||||
file_key = request.query_params.get('key', '')
|
file_key = request.query_params.get('key', '')
|
||||||
if file_key:
|
if file_key:
|
||||||
file_path = os.path.join(work_path, file_key)
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
try:
|
try:
|
||||||
|
file_path = safe_join(work_path, file_key)
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
content = _('Unsupported file content')
|
content = _('Unsupported file content')
|
||||||
|
except SuspiciousFileOperation:
|
||||||
|
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
|
||||||
return Response({'content': content})
|
return Response({'content': content})
|
||||||
else:
|
else:
|
||||||
expand_key = request.query_params.get('expand', '')
|
expand_key = request.query_params.get('expand', '')
|
||||||
@ -105,7 +109,8 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
parent_key = ''
|
parent_key = ''
|
||||||
if os.path.dirname(parent_key) == 'root':
|
if os.path.dirname(parent_key) == 'root':
|
||||||
parent_key = os.path.basename(parent_key)
|
parent_key = os.path.basename(parent_key)
|
||||||
full_path = os.path.join(work_path, parent_key)
|
|
||||||
|
full_path = safe_join(work_path, parent_key)
|
||||||
|
|
||||||
is_directory = request.data.get('is_directory', False)
|
is_directory = request.data.get('is_directory', False)
|
||||||
content = request.data.get('content', '')
|
content = request.data.get('content', '')
|
||||||
@ -117,13 +122,14 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
p = 'new_file.yml'
|
p = 'new_file.yml'
|
||||||
else:
|
else:
|
||||||
p = 'new_dir'
|
p = 'new_dir'
|
||||||
np = os.path.join(full_path, p)
|
np = safe_join(full_path, p)
|
||||||
n = 0
|
n = 0
|
||||||
while os.path.exists(np):
|
while os.path.exists(np):
|
||||||
n += 1
|
n += 1
|
||||||
np = os.path.join(full_path, '{}({})'.format(p, n))
|
np = safe_join(full_path, '{}({})'.format(p, n))
|
||||||
return np
|
return np
|
||||||
|
|
||||||
|
try:
|
||||||
if is_directory:
|
if is_directory:
|
||||||
new_file_path = find_new_name(name)
|
new_file_path = find_new_name(name)
|
||||||
os.makedirs(new_file_path)
|
os.makedirs(new_file_path)
|
||||||
@ -131,13 +137,15 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
new_file_path = find_new_name(name, True)
|
new_file_path = find_new_name(name, True)
|
||||||
with open(new_file_path, 'w') as f:
|
with open(new_file_path, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
except SuspiciousFileOperation:
|
||||||
|
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
|
||||||
|
|
||||||
relative_path = os.path.relpath(os.path.dirname(new_file_path), work_path)
|
relative_path = os.path.relpath(os.path.dirname(new_file_path), work_path)
|
||||||
new_node = {
|
new_node = {
|
||||||
"name": os.path.basename(new_file_path),
|
"name": os.path.basename(new_file_path),
|
||||||
"title": os.path.basename(new_file_path),
|
"title": os.path.basename(new_file_path),
|
||||||
"id": os.path.join(relative_path, os.path.basename(new_file_path))
|
"id": safe_join(relative_path, os.path.basename(new_file_path))
|
||||||
if not os.path.join(relative_path, os.path.basename(new_file_path)).startswith('.')
|
if not safe_join(relative_path, os.path.basename(new_file_path)).startswith('.')
|
||||||
else os.path.basename(new_file_path),
|
else os.path.basename(new_file_path),
|
||||||
"isParent": is_directory,
|
"isParent": is_directory,
|
||||||
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
||||||
@ -156,7 +164,7 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
new_name = request.data.get('new_name', '')
|
new_name = request.data.get('new_name', '')
|
||||||
|
|
||||||
if file_key in self.protected_files and new_name:
|
if file_key in self.protected_files and new_name:
|
||||||
return Response({'msg': '{} can not be rename'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST)
|
raise JMSException(code='this_file_can_not_be_rename', detail={"msg": _("This file can not be rename")})
|
||||||
|
|
||||||
if os.path.dirname(file_key) == 'root':
|
if os.path.dirname(file_key) == 'root':
|
||||||
file_key = os.path.basename(file_key)
|
file_key = os.path.basename(file_key)
|
||||||
@ -166,16 +174,20 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
|
|
||||||
if not file_key or file_key == 'root':
|
if not file_key or file_key == 'root':
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
file_path = os.path.join(work_path, file_key)
|
file_path = safe_join(work_path, file_key)
|
||||||
|
|
||||||
# rename
|
# rename
|
||||||
if new_name:
|
if new_name:
|
||||||
new_file_path = os.path.join(os.path.dirname(file_path), new_name)
|
try:
|
||||||
|
new_file_path = safe_join(os.path.dirname(file_path), new_name)
|
||||||
if new_file_path == file_path:
|
if new_file_path == file_path:
|
||||||
return Response(status=status.HTTP_200_OK)
|
return Response(status=status.HTTP_200_OK)
|
||||||
if os.path.exists(new_file_path):
|
if os.path.exists(new_file_path):
|
||||||
return Response({'msg': '{} already exists'.format(new_name)}, status=status.HTTP_400_BAD_REQUEST)
|
raise JMSException(code='file_already exists', detail={"msg": _("File already exists")})
|
||||||
os.rename(file_path, new_file_path)
|
os.rename(file_path, new_file_path)
|
||||||
|
except SuspiciousFileOperation:
|
||||||
|
raise JMSException(code='invalid_file_path', detail={"msg": _("Invalid file path")})
|
||||||
|
|
||||||
# edit content
|
# edit content
|
||||||
else:
|
else:
|
||||||
if not is_directory:
|
if not is_directory:
|
||||||
@ -189,10 +201,11 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
work_path = playbook.work_dir
|
work_path = playbook.work_dir
|
||||||
file_key = request.query_params.get('key', '')
|
file_key = request.query_params.get('key', '')
|
||||||
if not file_key:
|
if not file_key:
|
||||||
return Response({'msg': 'key is required'}, status=status.HTTP_400_BAD_REQUEST)
|
raise JMSException(code='file_key_is_required', detail={"msg": _("File key is required")})
|
||||||
|
|
||||||
if file_key in self.protected_files:
|
if file_key in self.protected_files:
|
||||||
return Response({'msg': ' {} can not be delete'.format(file_key)}, status=status.HTTP_400_BAD_REQUEST)
|
raise JMSException(code='This file can not be delete', detail={"msg": _("This file can not be delete")})
|
||||||
file_path = os.path.join(work_path, file_key)
|
file_path = safe_join(work_path, file_key)
|
||||||
if os.path.isdir(file_path):
|
if os.path.isdir(file_path):
|
||||||
shutil.rmtree(file_path)
|
shutil.rmtree(file_path)
|
||||||
else:
|
else:
|
||||||
@ -216,11 +229,12 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
|
|
||||||
relative_path = os.path.relpath(path, root_path)
|
relative_path = os.path.relpath(path, root_path)
|
||||||
for d in dirs:
|
for d in dirs:
|
||||||
|
dir_id = os.path.relpath(safe_join(path, d), root_path)
|
||||||
|
|
||||||
node = {
|
node = {
|
||||||
"name": d,
|
"name": d,
|
||||||
"title": d,
|
"title": d,
|
||||||
"id": os.path.join(relative_path, d) if not os.path.join(relative_path, d).startswith(
|
"id": dir_id,
|
||||||
'.') else d,
|
|
||||||
"isParent": True,
|
"isParent": True,
|
||||||
"open": True,
|
"open": True,
|
||||||
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
||||||
@ -230,12 +244,12 @@ class PlaybookFileBrowserAPIView(APIView):
|
|||||||
node['open'] = True
|
node['open'] = True
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
for f in files:
|
for f in files:
|
||||||
|
file_id = os.path.relpath(safe_join(path, f), root_path)
|
||||||
node = {
|
node = {
|
||||||
"name": f,
|
"name": f,
|
||||||
"title": f,
|
"title": f,
|
||||||
"iconSkin": 'file',
|
"iconSkin": 'file',
|
||||||
"id": os.path.join(relative_path, f) if not os.path.join(relative_path, f).startswith(
|
"id": file_id,
|
||||||
'.') else f,
|
|
||||||
"isParent": False,
|
"isParent": False,
|
||||||
"open": False,
|
"open": False,
|
||||||
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
"pId": relative_path if not relative_path.startswith('.') else 'root',
|
||||||
|
@ -43,8 +43,8 @@ def get_parent_keys(key, include_self=True):
|
|||||||
|
|
||||||
class JMSPermedInventory(JMSInventory):
|
class JMSPermedInventory(JMSInventory):
|
||||||
def __init__(self, assets, account_policy='privileged_first',
|
def __init__(self, assets, account_policy='privileged_first',
|
||||||
account_prefer='root,Administrator', host_callback=None, exclude_localhost=False, user=None):
|
account_prefer='root,Administrator', host_callback=None, user=None):
|
||||||
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost)
|
super().__init__(assets, account_policy, account_prefer, host_callback, exclude_localhost=True)
|
||||||
self.user = user
|
self.user = user
|
||||||
self.assets_accounts_mapper = self.get_assets_accounts_mapper()
|
self.assets_accounts_mapper = self.get_assets_accounts_mapper()
|
||||||
|
|
||||||
|
@ -11,10 +11,15 @@ from ops.exception import PlaybookNoValidEntry
|
|||||||
from orgs.mixins.models import JMSOrgBaseModel
|
from orgs.mixins.models import JMSOrgBaseModel
|
||||||
|
|
||||||
dangerous_keywords = (
|
dangerous_keywords = (
|
||||||
|
'hosts:localhost',
|
||||||
|
'hosts:127.0.0.1',
|
||||||
|
'hosts:::1',
|
||||||
'delegate_to:localhost',
|
'delegate_to:localhost',
|
||||||
'delegate_to:127.0.0.1',
|
'delegate_to:127.0.0.1',
|
||||||
|
'delegate_to:::1',
|
||||||
'local_action',
|
'local_action',
|
||||||
'connection:local',
|
'connection:local',
|
||||||
|
'ansible_connection'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -48,7 +53,14 @@ class Playbook(JMSOrgBaseModel):
|
|||||||
with open(file, 'r') as f:
|
with open(file, 'r') as f:
|
||||||
for line_num, line in enumerate(f):
|
for line_num, line in enumerate(f):
|
||||||
for keyword in dangerous_keywords:
|
for keyword in dangerous_keywords:
|
||||||
if keyword in line.replace(' ', ''):
|
clear_line = line.replace(' ', '')\
|
||||||
|
.replace('\n', '')\
|
||||||
|
.replace('\r', '')\
|
||||||
|
.replace('\t', '') \
|
||||||
|
.replace('\'', '') \
|
||||||
|
.replace('\"', '')\
|
||||||
|
.replace('\v', '')
|
||||||
|
if keyword in clear_line:
|
||||||
result.append((line_num, keyword))
|
result.append((line_num, keyword))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
flash_message: false,
|
flash_message: false,
|
||||||
success(data) {
|
success(data) {
|
||||||
const dateStart = new Date(data.date_start).toLocaleString();
|
const dateStart = data.date_start ? new Date(data.date_start).toLocaleString() : '';
|
||||||
$('.task-id').html(data.id);
|
$('.task-id').html(data.id);
|
||||||
$('.task-type').html(data.task_name);
|
$('.task-type').html(data.task_name);
|
||||||
$('.date-start').html(dateStart);
|
$('.date-start').html(dateStart);
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['IsSessionAssignee']
|
__all__ = ['IsSessionAssignee']
|
||||||
|
|
||||||
|
|
||||||
class IsSessionAssignee(permissions.BasePermission):
|
class IsSessionAssignee(permissions.IsAuthenticated):
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
return False
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
try:
|
try:
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
class IsAssignee(permissions.BasePermission):
|
class IsAssignee(permissions.IsAuthenticated):
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return obj.has_current_assignee(request.user)
|
return obj.has_current_assignee(request.user)
|
||||||
|
|
||||||
|
|
||||||
class IsApplicant(permissions.BasePermission):
|
class IsApplicant(permissions.IsAuthenticated):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
return obj.applicant == request.user
|
return obj.applicant == request.user
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#
|
#
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
@ -562,8 +560,7 @@ class TokenMixin:
|
|||||||
return self.access_keys.first()
|
return self.access_keys.first()
|
||||||
|
|
||||||
def generate_reset_token(self):
|
def generate_reset_token(self):
|
||||||
letter = string.ascii_letters + string.digits
|
token = random_string(50)
|
||||||
token = ''.join([random.choice(letter) for _ in range(50)])
|
|
||||||
self.set_cache(token)
|
self.set_cache(token)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
from rbac.builtin import BuiltinRole
|
|
||||||
from .utils import is_auth_password_time_valid
|
from .utils import is_auth_password_time_valid
|
||||||
|
|
||||||
|
|
||||||
@ -11,7 +10,7 @@ class IsAuthPasswdTimeValid(permissions.IsAuthenticated):
|
|||||||
and is_auth_password_time_valid(request.session)
|
and is_auth_password_time_valid(request.session)
|
||||||
|
|
||||||
|
|
||||||
class UserObjectPermission(permissions.BasePermission):
|
class UserObjectPermission(permissions.IsAuthenticated):
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
def has_object_permission(self, request, view, obj):
|
||||||
if view.action not in ['update', 'partial_update', 'destroy']:
|
if view.action not in ['update', 'partial_update', 'destroy']:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
aiofiles==22.1.0
|
aiofiles==22.1.0
|
||||||
amqp==5.0.9
|
amqp==5.0.9
|
||||||
git+https://github.com/jumpserver/ansible@master#egg=ansible-core
|
ansible-core@https://github.com/jumpserver/ansible/releases/download/v2.14.1.2/ansible-2.14.1.2.zip
|
||||||
ansible==7.1.0
|
ansible==7.1.0
|
||||||
ansible-runner==2.2.1
|
ansible-runner==2.2.1
|
||||||
asn1crypto==0.24.0
|
asn1crypto==0.24.0
|
||||||
@ -35,7 +35,7 @@ itsdangerous==1.1.0
|
|||||||
pyotp==2.6.0
|
pyotp==2.6.0
|
||||||
PyNaCl==1.5.0
|
PyNaCl==1.5.0
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
PyYAML==6.0
|
PyYAML==6.0.1
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
jms-storage==0.0.47
|
jms-storage==0.0.47
|
||||||
simplejson==3.17.6
|
simplejson==3.17.6
|
||||||
@ -118,7 +118,6 @@ kubernetes==21.7.0
|
|||||||
# DB requirements
|
# DB requirements
|
||||||
mysqlclient==2.1.0
|
mysqlclient==2.1.0
|
||||||
PyMySQL==1.0.2
|
PyMySQL==1.0.2
|
||||||
pymssql==2.2.5
|
|
||||||
django-mysql==3.9.0
|
django-mysql==3.9.0
|
||||||
django-redis==5.2.0
|
django-redis==5.2.0
|
||||||
python-redis-lock==3.7.0
|
python-redis-lock==3.7.0
|
||||||
|
@ -18,9 +18,9 @@ huaweicloud-sdk-python==1.0.21
|
|||||||
# huaweicloud-sdk-python need keystoneauth1<=3.4.0
|
# huaweicloud-sdk-python need keystoneauth1<=3.4.0
|
||||||
keystoneauth1==3.4.0
|
keystoneauth1==3.4.0
|
||||||
# DB requirements
|
# DB requirements
|
||||||
oracledb==1.0.1
|
# oracledb==1.3.2
|
||||||
psycopg2-binary==2.9.1
|
psycopg2-binary==2.9.1
|
||||||
pymssql==2.2.5
|
pymssql==2.2.8
|
||||||
IPy==1.1
|
IPy==1.1
|
||||||
psycopg2==2.9.4
|
psycopg2==2.9.4
|
||||||
ucloud-sdk-python3==0.11.47
|
ucloud-sdk-python3==0.11.47
|
||||||
|
Loading…
Reference in New Issue
Block a user