mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-17 17:42:37 +00:00
Compare commits
128 Commits
pr@dev@top
...
v3.10.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f485b40bb | ||
|
|
b87554f9db | ||
|
|
3c255f9fa6 | ||
|
|
a6b5437f6a | ||
|
|
71c690ef9e | ||
|
|
bee07db900 | ||
|
|
115eb7c15a | ||
|
|
88810263cd | ||
|
|
3d95bc4656 | ||
|
|
69de08bb5d | ||
|
|
5c234fdd0c | ||
|
|
be5baa5a3f | ||
|
|
2f1a65f120 | ||
|
|
e6d02eaf4c | ||
|
|
6d6dec2752 | ||
|
|
c6c067c44b | ||
|
|
84ec1b047a | ||
|
|
e6dca2ec14 | ||
|
|
8793003d18 | ||
|
|
29fd6e0ae4 | ||
|
|
90587a83cc | ||
|
|
dfa0198742 | ||
|
|
9e857b54ed | ||
|
|
c34358509b | ||
|
|
a7c46109d9 | ||
|
|
48fa6172bd | ||
|
|
89aa87fd6b | ||
|
|
79d230755e | ||
|
|
99082f261e | ||
|
|
7e2100b435 | ||
|
|
185d4e9563 | ||
|
|
ecaa84790c | ||
|
|
30210dc0b9 | ||
|
|
ff699f4ee2 | ||
|
|
48239b0c63 | ||
|
|
f4f74909a8 | ||
|
|
cab1e0bf52 | ||
|
|
bf195c1599 | ||
|
|
7f5f7e81b8 | ||
|
|
99affad9b9 | ||
|
|
34eea024f8 | ||
|
|
1d1e4b90ed | ||
|
|
f5d40a787e | ||
|
|
77d8083c00 | ||
|
|
180303ccb4 | ||
|
|
9cd1619990 | ||
|
|
7d0a901522 | ||
|
|
5e9fabff1b | ||
|
|
1d36934111 | ||
|
|
25603e4758 | ||
|
|
3ae164d7e0 | ||
|
|
3ad64e142e | ||
|
|
0ff1413780 | ||
|
|
f5b64bed4e | ||
|
|
a559415b65 | ||
|
|
2e7bd076f4 | ||
|
|
11f6fe0bf9 | ||
|
|
ae94648e80 | ||
|
|
94e08f3d96 | ||
|
|
8bedef92f0 | ||
|
|
e5bb28231a | ||
|
|
b5aeb24ae9 | ||
|
|
674ea7142f | ||
|
|
5ab7b99b9d | ||
|
|
9cd163c99d | ||
|
|
e72073f0cc | ||
|
|
690f525afc | ||
|
|
6686afcec1 | ||
|
|
0918f5c6f6 | ||
|
|
891e3d5609 | ||
|
|
9fad591545 | ||
|
|
1ed1c3a536 | ||
|
|
63824d3491 | ||
|
|
96eadf060c | ||
|
|
2c9128b0e7 | ||
|
|
7d6fd0f881 | ||
|
|
4e996afd5e | ||
|
|
1ed745d042 | ||
|
|
39ebbfcf10 | ||
|
|
d0ec4f798b | ||
|
|
1712a9a104 | ||
|
|
951aafcabd | ||
|
|
e46da9d741 | ||
|
|
06aaf9e3d0 | ||
|
|
5ac9fb81dc | ||
|
|
fb907e250c | ||
|
|
34ea40a14d | ||
|
|
8c560b0317 | ||
|
|
df9cc4700b | ||
|
|
6aa1227e60 | ||
|
|
296c788e28 | ||
|
|
a1ae29d35e | ||
|
|
139ffd0b47 | ||
|
|
ff6c1aef7f | ||
|
|
9b6b48a7f1 | ||
|
|
2b133a8085 | ||
|
|
81b5f1ce93 | ||
|
|
c646084c51 | ||
|
|
5e69c03cb7 | ||
|
|
e2df85bddd | ||
|
|
d710697fa9 | ||
|
|
a955fcd682 | ||
|
|
1816d52d21 | ||
|
|
d7c26cab7d | ||
|
|
dc894fdc2d | ||
|
|
742ef89bef | ||
|
|
3d4fc56592 | ||
|
|
45291aba0c | ||
|
|
495ee99e29 | ||
|
|
223eb8ad38 | ||
|
|
370e959400 | ||
|
|
b82f007787 | ||
|
|
1faeb54673 | ||
|
|
04e102cb87 | ||
|
|
81027cd561 | ||
|
|
cf727d22c0 | ||
|
|
bb6d077645 | ||
|
|
a78ccc9667 | ||
|
|
d70351e6b3 | ||
|
|
4e76207adb | ||
|
|
7a12c3737f | ||
|
|
8450c49e25 | ||
|
|
ab6d8df2f0 | ||
|
|
550115c39f | ||
|
|
9c23512d91 | ||
|
|
30054b286a | ||
|
|
22d7385891 | ||
|
|
1701bedb41 |
137
Dockerfile
Normal file
137
Dockerfile
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
FROM python:3.11-slim-bullseye AS stage-1
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
ADD . .
|
||||||
|
RUN echo > /opt/jumpserver/config.yml \
|
||||||
|
&& cd utils && bash -ixeu build.sh
|
||||||
|
|
||||||
|
FROM python:3.11-slim-bullseye as stage-2
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
ARG BUILD_DEPENDENCIES=" \
|
||||||
|
g++ \
|
||||||
|
make \
|
||||||
|
pkg-config"
|
||||||
|
|
||||||
|
ARG DEPENDENCIES=" \
|
||||||
|
freetds-dev \
|
||||||
|
libffi-dev \
|
||||||
|
libjpeg-dev \
|
||||||
|
libkrb5-dev \
|
||||||
|
libldap2-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libsasl2-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libxmlsec1-dev \
|
||||||
|
libxmlsec1-openssl \
|
||||||
|
freerdp2-dev \
|
||||||
|
libaio-dev"
|
||||||
|
|
||||||
|
ARG TOOLS=" \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
default-mysql-client \
|
||||||
|
git \
|
||||||
|
git-lfs \
|
||||||
|
unzip \
|
||||||
|
xz-utils \
|
||||||
|
wget"
|
||||||
|
|
||||||
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
||||||
|
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
|
&& echo "no" | dpkg-reconfigure dash
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
|
||||||
|
set -ex \
|
||||||
|
&& python3 -m venv /opt/py3 \
|
||||||
|
&& pip install poetry -i ${PIP_MIRROR} \
|
||||||
|
&& poetry config virtualenvs.create false \
|
||||||
|
&& . /opt/py3/bin/activate \
|
||||||
|
&& poetry install
|
||||||
|
|
||||||
|
FROM python:3.11-slim-bullseye
|
||||||
|
ARG TARGETARCH
|
||||||
|
ENV LANG=zh_CN.UTF-8 \
|
||||||
|
PATH=/opt/py3/bin:$PATH
|
||||||
|
|
||||||
|
ARG DEPENDENCIES=" \
|
||||||
|
libjpeg-dev \
|
||||||
|
libpq-dev \
|
||||||
|
libx11-dev \
|
||||||
|
freerdp2-dev \
|
||||||
|
libxmlsec1-openssl"
|
||||||
|
|
||||||
|
ARG TOOLS=" \
|
||||||
|
ca-certificates \
|
||||||
|
curl \
|
||||||
|
default-libmysqlclient-dev \
|
||||||
|
default-mysql-client \
|
||||||
|
iputils-ping \
|
||||||
|
locales \
|
||||||
|
netcat-openbsd \
|
||||||
|
nmap \
|
||||||
|
openssh-client \
|
||||||
|
patch \
|
||||||
|
sshpass \
|
||||||
|
telnet \
|
||||||
|
vim \
|
||||||
|
bubblewrap \
|
||||||
|
wget"
|
||||||
|
|
||||||
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
||||||
|
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
||||||
|
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
||||||
|
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||||
|
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
||||||
|
&& mkdir -p /root/.ssh/ \
|
||||||
|
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
||||||
|
&& echo "no" | dpkg-reconfigure dash \
|
||||||
|
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
||||||
|
&& sed -i "s@# export @export @g" ~/.bashrc \
|
||||||
|
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
||||||
|
|
||||||
|
ARG RECEPTOR_VERSION=v1.4.5
|
||||||
|
RUN set -ex \
|
||||||
|
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
|
||||||
|
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
|
||||||
|
&& chown root:root /usr/local/bin/receptor \
|
||||||
|
&& chmod 755 /usr/local/bin/receptor \
|
||||||
|
&& rm -f /opt/receptor.tar.gz
|
||||||
|
|
||||||
|
COPY --from=stage-2 /opt/py3 /opt/py3
|
||||||
|
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
||||||
|
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
|
||||||
|
|
||||||
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
ENV VERSION=$VERSION
|
||||||
|
|
||||||
|
VOLUME /opt/jumpserver/data
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim-bullseye as stage-1
|
FROM python:3.11-slim-bullseye AS stage-1
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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 registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
||||||
|
|
||||||
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
||||||
@@ -53,3 +53,5 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
|
append_privs: "{{ db_name != '' | bool }}"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
|
|
||||||
|
|||||||
@@ -39,3 +39,5 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
@@ -35,12 +35,24 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
|
- 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 or params.modify_sudo
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- 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') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
register: change_secret_result
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -57,19 +69,9 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
register: change_secret_result
|
||||||
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
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@
|
|||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -97,5 +101,7 @@
|
|||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -34,6 +40,11 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
||||||
en: 'Using Ansible module user to change account secret (DES)'
|
en: 'Using Ansible module user to change account secret (DES)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -49,6 +60,11 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -35,12 +35,24 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
|
- 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 or params.modify_sudo
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: "Change {{ account.username }} 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') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
register: change_secret_result
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -57,19 +69,9 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
register: change_secret_result
|
||||||
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
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@
|
|||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -97,5 +101,7 @@
|
|||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -36,6 +42,11 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
||||||
en: 'Using Ansible module user to change account secret (SHA512)'
|
en: 'Using Ansible module user to change account secret (SHA512)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -51,6 +62,11 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -28,4 +28,6 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change
|
||||||
|
|||||||
@@ -31,5 +31,7 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
record_id = self.record_map[asset_account_id]
|
record_id = self.record_map[asset_account_id]
|
||||||
try:
|
try:
|
||||||
recorder = ChangeSecretRecord.objects.get(id=record_id)
|
recorder = ChangeSecretRecord.objects.get(id=record_id)
|
||||||
|
new_secret = recorder.new_secret
|
||||||
except ChangeSecretRecord.DoesNotExist:
|
except ChangeSecretRecord.DoesNotExist:
|
||||||
print(f"Record {record_id} not found")
|
print(f"Record {record_id} not found")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -53,3 +53,5 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -36,7 +36,8 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
|
append_privs: "{{ db_name != '' | bool }}"
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: result is succeeded
|
when: result is succeeded
|
||||||
register: change_info
|
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.postgresql.postgresql_ping:
|
community.postgresql.postgresql_ping:
|
||||||
@@ -40,8 +39,5 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
when:
|
|
||||||
- result is succeeded
|
|
||||||
- change_info is succeeded
|
|
||||||
register: result
|
register: result
|
||||||
failed_when: not result.is_available
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -35,12 +35,24 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
|
- 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 or params.modify_sudo
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- 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') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
register: change_secret_result
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -57,19 +69,9 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
register: change_secret_result
|
||||||
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
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@
|
|||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -97,6 +101,8 @@
|
|||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -34,6 +40,11 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
|
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
|
||||||
en: 'Using Ansible module user to push account (DES)'
|
en: 'Using Ansible module user to push account (DES)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -49,6 +60,11 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -35,12 +35,24 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
|
- 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 or params.modify_sudo
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: "Change {{ account.username }} 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') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
register: change_secret_result
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -57,19 +69,9 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
|
register: change_secret_result
|
||||||
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
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@
|
|||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -97,6 +101,8 @@
|
|||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
||||||
when: account.secret_type == "ssh_key"
|
when:
|
||||||
|
- account.secret_type == "ssh_key"
|
||||||
|
- check_conn_after_change or change_secret_result.failed | default(false)
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
|
- name: modify_sudo
|
||||||
|
type: bool
|
||||||
|
label: "{{ 'Modify sudo label' | trans }}"
|
||||||
|
default: False
|
||||||
|
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -36,6 +42,11 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
|
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
|
||||||
en: 'Using Ansible module user to push account (sha512)'
|
en: 'Using Ansible module user to push account (sha512)'
|
||||||
|
|
||||||
|
Modify params sudo help text:
|
||||||
|
zh: '如果用户存在,可以修改sudo权限'
|
||||||
|
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
||||||
|
en: 'If the user exists, sudo permissions can be modified'
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -51,6 +62,11 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
|
Modify sudo label:
|
||||||
|
zh: '修改 sudo 权限'
|
||||||
|
ja: 'sudo 権限を変更'
|
||||||
|
en: 'Modify sudo'
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -28,4 +28,6 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change
|
||||||
|
|||||||
@@ -31,5 +31,7 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
when: account.secret_type == "password"
|
when:
|
||||||
|
- account.secret_type == "password"
|
||||||
|
- check_conn_after_change
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -12,11 +12,13 @@
|
|||||||
path: "{{ user_home_dir.stdout }}"
|
path: "{{ user_home_dir.stdout }}"
|
||||||
register: home_dir
|
register: home_dir
|
||||||
when: user_home_dir.stdout != ""
|
when: user_home_dir.stdout != ""
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Rename user home directory if it exists"
|
- name: "Rename user home directory if it exists"
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
|
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
|
||||||
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
|
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "Remove account"
|
- name: "Remove account"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
|
|||||||
@@ -16,3 +16,5 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
|
ansible_timeout: 30
|
||||||
when: not account.become.ansible_become
|
when: not account.become.ansible_become
|
||||||
|
|
||||||
- name: Verify account connectivity(Switch)
|
- name: Verify account connectivity(Switch)
|
||||||
@@ -20,4 +21,5 @@
|
|||||||
ansible_become_method: "{{ account.become.ansible_become_method }}"
|
ansible_become_method: "{{ account.become.ansible_become_method }}"
|
||||||
ansible_become_user: "{{ account.become.ansible_become_user }}"
|
ansible_become_user: "{{ account.become.ansible_become_user }}"
|
||||||
ansible_become_password: "{{ account.become.ansible_become_password }}"
|
ansible_become_password: "{{ account.become.ansible_become_password }}"
|
||||||
|
ansible_timeout: 30
|
||||||
when: account.become.ansible_become
|
when: account.become.ansible_become
|
||||||
|
|||||||
@@ -9,3 +9,4 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
|
ansible_timeout: 30
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||||
)
|
)
|
||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||||
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
|
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'],
|
||||||
|
verbose_name=_("historical Account"))
|
||||||
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||||
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
||||||
|
|
||||||
@@ -119,7 +120,8 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
return auth
|
return auth
|
||||||
|
|
||||||
auth.update(self.make_account_ansible_vars(su_from))
|
auth.update(self.make_account_ansible_vars(su_from))
|
||||||
become_method = platform.su_method if platform.su_method else 'sudo'
|
|
||||||
|
become_method = platform.ansible_become_method
|
||||||
password = su_from.secret if become_method == 'sudo' else self.secret
|
password = su_from.secret if become_method == 'sudo' else self.secret
|
||||||
auth['ansible_become'] = True
|
auth['ansible_become'] = True
|
||||||
auth['ansible_become_method'] = become_method
|
auth['ansible_become_method'] = become_method
|
||||||
|
|||||||
@@ -79,18 +79,28 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_template_attr_for_account(template):
|
def get_template_attr_for_account(template):
|
||||||
# Set initial data from template
|
|
||||||
field_names = [
|
field_names = [
|
||||||
'name', 'username', 'secret',
|
'name', 'username',
|
||||||
'secret_type', 'privileged', 'is_active'
|
'secret_type', 'secret',
|
||||||
|
'privileged', 'is_active'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
field_map = {
|
||||||
|
'push_params': 'params',
|
||||||
|
'auto_push': 'push_now'
|
||||||
|
}
|
||||||
|
|
||||||
|
field_names.extend(field_map.keys())
|
||||||
|
|
||||||
attrs = {}
|
attrs = {}
|
||||||
for name in field_names:
|
for name in field_names:
|
||||||
value = getattr(template, name, None)
|
value = getattr(template, name, None)
|
||||||
if value is None:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
attrs[name] = value
|
|
||||||
|
attr_name = field_map.get(name, name)
|
||||||
|
attrs[attr_name] = value
|
||||||
|
|
||||||
attrs['secret'] = template.get_secret()
|
attrs['secret'] = template.get_secret()
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@@ -173,6 +183,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
params = validated_data.pop('params', None)
|
params = validated_data.pop('params', None)
|
||||||
self.clean_auth_fields(validated_data)
|
self.clean_auth_fields(validated_data)
|
||||||
instance, stat = self.do_create(validated_data)
|
instance, stat = self.do_create(validated_data)
|
||||||
|
if instance.source == Source.LOCAL:
|
||||||
self.push_account_if_need(instance, push_now, params, stat)
|
self.push_account_if_need(instance, push_now, params, stat)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
@@ -225,7 +236,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||||||
fields = BaseAccountSerializer.Meta.fields + [
|
fields = BaseAccountSerializer.Meta.fields + [
|
||||||
'su_from', 'asset', 'version',
|
'su_from', 'asset', 'version',
|
||||||
'source', 'source_id', 'connectivity',
|
'source', 'source_id', 'connectivity',
|
||||||
] + list(set(AccountCreateUpdateSerializerMixin.Meta.fields) - {'params'})
|
] + AccountCreateUpdateSerializerMixin.Meta.fields
|
||||||
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
|
||||||
'connectivity'
|
'connectivity'
|
||||||
]
|
]
|
||||||
@@ -275,8 +286,8 @@ class AssetAccountBulkSerializer(
|
|||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
||||||
'privileged', 'is_active', 'comment', 'template',
|
'privileged', 'is_active', 'comment', 'template',
|
||||||
'on_invalid', 'push_now', 'assets', 'su_from_username',
|
'on_invalid', 'push_now', 'params', 'assets',
|
||||||
'source', 'source_id',
|
'su_from_username', 'source', 'source_id',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': False},
|
'name': {'required': False},
|
||||||
@@ -414,16 +425,23 @@ class AssetAccountBulkSerializer(
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def push_accounts_if_need(results, push_now):
|
def push_accounts_if_need(results, push_now, params):
|
||||||
if not push_now:
|
if not push_now:
|
||||||
return
|
return
|
||||||
accounts = [str(v['instance']) for v in results if v.get('instance')]
|
|
||||||
push_accounts_to_assets_task.delay(accounts)
|
account_ids = [v['instance'] for v in results if v.get('instance')]
|
||||||
|
accounts = Account.objects.filter(id__in=account_ids, source=Source.LOCAL)
|
||||||
|
if not accounts.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
account_ids = [str(_id) for _id in accounts.values_list('id', flat=True)]
|
||||||
|
push_accounts_to_assets_task.delay(account_ids, params)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
params = validated_data.pop('params', None)
|
||||||
push_now = validated_data.pop('push_now', False)
|
push_now = validated_data.pop('push_now', False)
|
||||||
results = self.perform_bulk_create(validated_data)
|
results = self.perform_bulk_create(validated_data)
|
||||||
self.push_accounts_if_need(results, push_now)
|
self.push_accounts_if_need(results, push_now, params)
|
||||||
for res in results:
|
for res in results:
|
||||||
res['asset'] = str(res['asset'])
|
res['asset'] = str(res['asset'])
|
||||||
return results
|
return results
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.dispatch import receiver
|
|||||||
from django.utils.translation import gettext_noop
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
from accounts.backends import vault_client
|
from accounts.backends import vault_client
|
||||||
|
from accounts.const import Source
|
||||||
from audits.const import ActivityChoices
|
from audits.const import ActivityChoices
|
||||||
from audits.signal_handlers import create_activities
|
from audits.signal_handlers import create_activities
|
||||||
from common.decorators import merge_delay_run
|
from common.decorators import merge_delay_run
|
||||||
@@ -32,7 +33,7 @@ def push_accounts_if_need(accounts=()):
|
|||||||
template_accounts = defaultdict(list)
|
template_accounts = defaultdict(list)
|
||||||
for ac in accounts:
|
for ac in accounts:
|
||||||
# 再强调一次吧
|
# 再强调一次吧
|
||||||
if ac.source != 'template':
|
if ac.source != Source.TEMPLATE:
|
||||||
continue
|
continue
|
||||||
template_accounts[ac.source_id].append(ac)
|
template_accounts[ac.source_id].append(ac)
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ def create_accounts_activities(account, action='create'):
|
|||||||
|
|
||||||
@receiver(post_save, sender=Account)
|
@receiver(post_save, sender=Account)
|
||||||
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
||||||
if not created or instance.source != 'template':
|
if not created:
|
||||||
return
|
return
|
||||||
push_accounts_if_need.delay(accounts=(instance,))
|
push_accounts_if_need.delay(accounts=(instance,))
|
||||||
create_accounts_activities(instance, action='create')
|
create_accounts_activities(instance, action='create')
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import copy
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
|
from accounts.const import SecretType, DEFAULT_PASSWORD_RULES
|
||||||
from common.utils import ssh_key_gen, random_string
|
from common.utils import (
|
||||||
from common.utils import validate_ssh_private_key, parse_ssh_private_key_str
|
validate_ssh_private_key, parse_ssh_private_key_str, ssh_key_gen,
|
||||||
|
random_string
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SecretGenerator:
|
class SecretGenerator:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
#
|
#
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
@@ -8,7 +10,7 @@ from common.utils.ip import is_ip_address, is_ip_network, is_ip_segment
|
|||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text']
|
__all__ = ['RuleSerializer', 'ip_group_child_validator', 'ip_group_help_text', 'address_validator']
|
||||||
|
|
||||||
|
|
||||||
def ip_group_child_validator(ip_group_child):
|
def ip_group_child_validator(ip_group_child):
|
||||||
@@ -21,6 +23,19 @@ def ip_group_child_validator(ip_group_child):
|
|||||||
raise serializers.ValidationError(error)
|
raise serializers.ValidationError(error)
|
||||||
|
|
||||||
|
|
||||||
|
def address_validator(value):
|
||||||
|
parsed = urlparse(value)
|
||||||
|
is_basic_url = parsed.scheme in ('http', 'https') and parsed.netloc
|
||||||
|
is_valid = value == '*' \
|
||||||
|
or is_ip_address(value) \
|
||||||
|
or is_ip_network(value) \
|
||||||
|
or is_ip_segment(value) \
|
||||||
|
or is_basic_url
|
||||||
|
if not is_valid:
|
||||||
|
error = _('address invalid: `{}`').format(value)
|
||||||
|
raise serializers.ValidationError(error)
|
||||||
|
|
||||||
|
|
||||||
ip_group_help_text = _(
|
ip_group_help_text = _(
|
||||||
'With * indicating a match all. '
|
'With * indicating a match all. '
|
||||||
'Such as: '
|
'Such as: '
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ class AssetsTaskCreateApi(AssetsTaskMixin, generics.CreateAPIView):
|
|||||||
def check_permissions(self, request):
|
def check_permissions(self, request):
|
||||||
action_perm_require = {
|
action_perm_require = {
|
||||||
"refresh": "assets.refresh_assethardwareinfo",
|
"refresh": "assets.refresh_assethardwareinfo",
|
||||||
|
"test": "assets.test_assetconnectivity",
|
||||||
}
|
}
|
||||||
_action = request.data.get("action")
|
_action = request.data.get("action")
|
||||||
perm_required = action_perm_require.get(_action)
|
perm_required = action_perm_require.get(_action)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class NodeChildrenApi(generics.ListCreateAPIView):
|
|||||||
self.instance = self.get_object()
|
self.instance = self.get_object()
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
with NodeAddChildrenLock(self.instance):
|
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
_id = data.get("id")
|
_id = data.get("id")
|
||||||
value = data.get("value")
|
value = data.get("value")
|
||||||
@@ -49,6 +48,7 @@ class NodeChildrenApi(generics.ListCreateAPIView):
|
|||||||
raise JMSException(_('The same level node name cannot be the same'))
|
raise JMSException(_('The same level node name cannot be the same'))
|
||||||
else:
|
else:
|
||||||
value = self.instance.get_next_child_preset_name()
|
value = self.instance.get_next_child_preset_name()
|
||||||
|
with NodeAddChildrenLock(self.instance):
|
||||||
node = self.instance.create_child(value=value, _id=_id)
|
node = self.instance.create_child(value=value, _id=_id)
|
||||||
# 避免查询 full value
|
# 避免查询 full value
|
||||||
node._full_value = node.value
|
node._full_value = node.value
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from django.utils.translation import gettext as _
|
|||||||
from sshtunnel import SSHTunnelForwarder
|
from sshtunnel import SSHTunnelForwarder
|
||||||
|
|
||||||
from assets.automations.methods import platform_automation_methods
|
from assets.automations.methods import platform_automation_methods
|
||||||
|
from common.db.utils import safe_db_connection
|
||||||
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
|
from common.utils import get_logger, lazyproperty, is_openssh_format_key, ssh_pubkey_gen
|
||||||
from ops.ansible import JMSInventory, DefaultCallback, SuperPlaybookRunner
|
from ops.ansible import JMSInventory, DefaultCallback, SuperPlaybookRunner
|
||||||
from ops.ansible.interface import interface
|
from ops.ansible.interface import interface
|
||||||
@@ -113,11 +114,7 @@ class BasePlaybookManager:
|
|||||||
if not data:
|
if not data:
|
||||||
data = automation_params.get(method_id, {})
|
data = automation_params.get(method_id, {})
|
||||||
params = serializer(data).data
|
params = serializer(data).data
|
||||||
return {
|
return params
|
||||||
field_name: automation_params.get(field_name, '')
|
|
||||||
if not params[field_name] else params[field_name]
|
|
||||||
for field_name in params
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform_automation_methods(self):
|
def platform_automation_methods(self):
|
||||||
@@ -191,6 +188,7 @@ class BasePlaybookManager:
|
|||||||
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
|
host['error'] = _('{} disabled'.format(self.__class__.method_type()))
|
||||||
return host
|
return host
|
||||||
|
|
||||||
|
host['check_conn_after_change'] = settings.CHECK_CONN_AFTER_CHANGE
|
||||||
host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
|
host = self.convert_cert_to_file(host, kwargs.get('path_dir'))
|
||||||
host['params'] = self.get_params(automation, method_type)
|
host['params'] = self.get_params(automation, method_type)
|
||||||
return host
|
return host
|
||||||
@@ -342,6 +340,7 @@ class BasePlaybookManager:
|
|||||||
try:
|
try:
|
||||||
kwargs.update({"clean_workspace": False})
|
kwargs.update({"clean_workspace": False})
|
||||||
cb = runner.run(**kwargs)
|
cb = runner.run(**kwargs)
|
||||||
|
with safe_db_connection():
|
||||||
self.on_runner_success(runner, cb)
|
self.on_runner_success(runner, cb)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.on_runner_failed(runner, e)
|
self.on_runner_failed(runner, e)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from collections import Counter
|
||||||
|
|
||||||
__all__ = ['FormatAssetInfo']
|
__all__ = ['FormatAssetInfo']
|
||||||
|
|
||||||
|
|
||||||
@@ -7,13 +9,28 @@ class FormatAssetInfo:
|
|||||||
self.tp = tp
|
self.tp = tp
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def posix_format(info):
|
def get_cpu_model_count(cpus):
|
||||||
for cpu_model in info.get('cpu_model', []):
|
try:
|
||||||
if cpu_model.endswith('GHz') or cpu_model.startswith("Intel"):
|
if len(cpus) % 3 == 0:
|
||||||
break
|
step = 3
|
||||||
|
models = [cpus[i + 2] for i in range(0, len(cpus), step)]
|
||||||
|
elif len(cpus) % 2 == 0:
|
||||||
|
step = 2
|
||||||
|
models = [cpus[i + 1] for i in range(0, len(cpus), step)]
|
||||||
else:
|
else:
|
||||||
cpu_model = ''
|
raise ValueError("CPU list format not recognized")
|
||||||
info['cpu_model'] = cpu_model[:48]
|
|
||||||
|
model_counts = Counter(models)
|
||||||
|
result = ', '.join([f"{model} x{count}" for model, count in model_counts.items()])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing CPU model list: {e}")
|
||||||
|
result = ''
|
||||||
|
return result
|
||||||
|
|
||||||
|
def posix_format(self, info):
|
||||||
|
cpus = self.get_cpu_model_count(info.get('cpu_model', []))
|
||||||
|
|
||||||
|
info['cpu_model'] = cpus
|
||||||
info['cpu_count'] = info.get('cpu_count', 0)
|
info['cpu_count'] = info.get('cpu_count', 0)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|||||||
@@ -16,3 +16,5 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_timeout: 30
|
||||||
tasks:
|
tasks:
|
||||||
- name: Posix ping
|
- name: Posix ping
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
- hosts: windows
|
- hosts: windows
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
|
vars:
|
||||||
|
ansible_timeout: 30
|
||||||
tasks:
|
tasks:
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ from .automation import *
|
|||||||
from .base import *
|
from .base import *
|
||||||
from .category import *
|
from .category import *
|
||||||
from .host import *
|
from .host import *
|
||||||
|
from .platform import *
|
||||||
from .protocol import *
|
from .protocol import *
|
||||||
from .types import *
|
from .types import *
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class DatabaseTypes(BaseType):
|
|||||||
'verify_account_enabled': True,
|
'verify_account_enabled': True,
|
||||||
'change_secret_enabled': True,
|
'change_secret_enabled': True,
|
||||||
'push_account_enabled': True,
|
'push_account_enabled': True,
|
||||||
|
'remove_account_enabled': True,
|
||||||
},
|
},
|
||||||
cls.REDIS: {
|
cls.REDIS: {
|
||||||
'ansible_enabled': False,
|
'ansible_enabled': False,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class HostTypes(BaseType):
|
|||||||
'charset': 'utf-8', # default
|
'charset': 'utf-8', # default
|
||||||
'domain_enabled': True,
|
'domain_enabled': True,
|
||||||
'su_enabled': True,
|
'su_enabled': True,
|
||||||
'su_methods': ['sudo', 'su'],
|
'su_methods': ['sudo', 'su', 'only_sudo', 'only_su'],
|
||||||
},
|
},
|
||||||
cls.WINDOWS: {
|
cls.WINDOWS: {
|
||||||
'su_enabled': False,
|
'su_enabled': False,
|
||||||
@@ -53,7 +53,8 @@ class HostTypes(BaseType):
|
|||||||
'gather_accounts_enabled': True,
|
'gather_accounts_enabled': True,
|
||||||
'verify_account_enabled': True,
|
'verify_account_enabled': True,
|
||||||
'change_secret_enabled': True,
|
'change_secret_enabled': True,
|
||||||
'push_account_enabled': True
|
'push_account_enabled': True,
|
||||||
|
'remove_account_enabled': True,
|
||||||
},
|
},
|
||||||
cls.WINDOWS: {
|
cls.WINDOWS: {
|
||||||
'ansible_config': {
|
'ansible_config': {
|
||||||
|
|||||||
11
apps/assets/const/platform.py
Normal file
11
apps/assets/const/platform.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.db.models import TextChoices
|
||||||
|
|
||||||
|
|
||||||
|
class SuMethodChoices(TextChoices):
|
||||||
|
sudo = "sudo", "sudo su -"
|
||||||
|
su = "su", "su - "
|
||||||
|
only_sudo = "only_sudo", "sudo su"
|
||||||
|
only_su = "only_su", "su"
|
||||||
|
enable = "enable", "enable"
|
||||||
|
super = "super", "super 15"
|
||||||
|
super_level = "super_level", "super level 15"
|
||||||
@@ -208,6 +208,12 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
|||||||
'default': 'admin',
|
'default': 'admin',
|
||||||
'label': _('Auth source'),
|
'label': _('Auth source'),
|
||||||
'help_text': _('The database to authenticate against')
|
'help_text': _('The database to authenticate against')
|
||||||
|
},
|
||||||
|
'connection_options': {
|
||||||
|
'type': 'str',
|
||||||
|
'default': '',
|
||||||
|
'label': _('Connection options'),
|
||||||
|
'help_text': _('The connection specific options eg. retryWrites=false&retryReads=false')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -289,23 +295,17 @@ class Protocol(ChoicesMixin, models.TextChoices):
|
|||||||
'setting': {
|
'setting': {
|
||||||
'api_mode': {
|
'api_mode': {
|
||||||
'type': 'choice',
|
'type': 'choice',
|
||||||
'default': 'gpt-3.5-turbo',
|
'default': 'gpt-4o-mini',
|
||||||
'label': _('API mode'),
|
'label': _('API mode'),
|
||||||
'choices': [
|
'choices': [
|
||||||
('gpt-3.5-turbo', 'GPT-3.5 Turbo'),
|
('gpt-4o-mini', 'GPT-4o-mini'),
|
||||||
('gpt-3.5-turbo-1106', 'GPT-3.5 Turbo 1106'),
|
('gpt-4o', 'GPT-4o'),
|
||||||
|
('gpt-4-turbo', 'GPT-4 Turbo'),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if settings.XPACK_LICENSE_IS_VALID:
|
|
||||||
choices = protocols[cls.chatgpt]['setting']['api_mode']['choices']
|
|
||||||
choices.extend([
|
|
||||||
('gpt-4', 'GPT-4'),
|
|
||||||
('gpt-4-turbo', 'GPT-4 Turbo'),
|
|
||||||
('gpt-4o', 'GPT-4o'),
|
|
||||||
])
|
|
||||||
return protocols
|
return protocols
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
# Generated by Django 3.2.16 on 2022-12-30 08:08
|
# Generated by Django 3.2.16 on 2022-12-30 08:08
|
||||||
|
|
||||||
import common.db.fields
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.db.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
@@ -53,7 +55,7 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Automation task execution',
|
'verbose_name': 'Automation task execution',
|
||||||
'ordering': ('-date_start',),
|
'ordering': ('org_id', '-date_start',),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
|
|||||||
|
|
||||||
def get_labels(self):
|
def get_labels(self):
|
||||||
from labels.models import Label, LabeledResource
|
from labels.models import Label, LabeledResource
|
||||||
res_type = ContentType.objects.get_for_model(self.__class__)
|
res_type = ContentType.objects.get_for_model(self.__class__.label_model())
|
||||||
label_ids = LabeledResource.objects.filter(res_type=res_type, res_id=self.id) \
|
label_ids = LabeledResource.objects.filter(res_type=res_type, res_id=self.id) \
|
||||||
.values_list('label_id', flat=True)
|
.values_list('label_id', flat=True)
|
||||||
return Label.objects.filter(id__in=label_ids)
|
return Label.objects.filter(id__in=label_ids)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from assets.const import AllTypes, Category, Protocol
|
from assets.const import AllTypes, Category, Protocol, SuMethodChoices
|
||||||
from common.db.fields import JsonDictTextField
|
from common.db.fields import JsonDictTextField
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
|
|
||||||
@@ -127,6 +127,17 @@ class Platform(LabeledMixin, JMSBaseModel):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ansible_become_method(self):
|
||||||
|
su_method = self.su_method or SuMethodChoices.sudo
|
||||||
|
if su_method in [SuMethodChoices.sudo, SuMethodChoices.only_sudo]:
|
||||||
|
method = SuMethodChoices.sudo
|
||||||
|
elif su_method in [SuMethodChoices.su, SuMethodChoices.only_su]:
|
||||||
|
method = SuMethodChoices.su
|
||||||
|
else:
|
||||||
|
method = su_method
|
||||||
|
return method
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|||||||
@@ -323,7 +323,9 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
|
|||||||
template_id = data.get('template', None)
|
template_id = data.get('template', None)
|
||||||
if template_id:
|
if template_id:
|
||||||
template = AccountTemplate.objects.get(id=template_id)
|
template = AccountTemplate.objects.get(id=template_id)
|
||||||
if template and template.su_from:
|
template.push_params = data.pop('push_params', {})
|
||||||
|
data['params'] = template.push_params
|
||||||
|
if template.su_from:
|
||||||
su_from_name_username_secret_type_map[template.name] = (
|
su_from_name_username_secret_type_map[template.name] = (
|
||||||
template.su_from.username, template.su_from.secret_type
|
template.su_from.username, template.su_from.secret_type
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class DatabaseSerializer(AssetSerializer):
|
|||||||
elif self.context.get('request'):
|
elif self.context.get('request'):
|
||||||
platform_id = self.context['request'].query_params.get('platform')
|
platform_id = self.context['request'].query_params.get('platform')
|
||||||
|
|
||||||
if not platform and platform_id:
|
if not platform and platform_id and str(platform_id).isdigit():
|
||||||
platform = Platform.objects.filter(id=platform_id).first()
|
platform = Platform.objects.filter(id=platform_id).first()
|
||||||
return platform
|
return platform
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from common.serializers import (
|
|||||||
)
|
)
|
||||||
from common.serializers.fields import LabeledChoiceField
|
from common.serializers.fields import LabeledChoiceField
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from ..const import Category, AllTypes, Protocol
|
from ..const import Category, AllTypes, Protocol, SuMethodChoices
|
||||||
from ..models import Platform, PlatformProtocol, PlatformAutomation
|
from ..models import Platform, PlatformProtocol, PlatformAutomation
|
||||||
|
|
||||||
__all__ = ["PlatformSerializer", "PlatformOpsMethodSerializer", "PlatformProtocolSerializer"]
|
__all__ = ["PlatformSerializer", "PlatformOpsMethodSerializer", "PlatformProtocolSerializer"]
|
||||||
@@ -27,6 +27,7 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
|||||||
"change_secret_enabled", "change_secret_method", "change_secret_params",
|
"change_secret_enabled", "change_secret_method", "change_secret_params",
|
||||||
"verify_account_enabled", "verify_account_method", "verify_account_params",
|
"verify_account_enabled", "verify_account_method", "verify_account_params",
|
||||||
"gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params",
|
"gather_accounts_enabled", "gather_accounts_method", "gather_accounts_params",
|
||||||
|
"remove_account_enabled", "remove_account_method", "remove_account_params",
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
# 启用资产探测
|
# 启用资产探测
|
||||||
@@ -42,6 +43,8 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
|
|||||||
"push_account_method": {"label": _("Push account method")},
|
"push_account_method": {"label": _("Push account method")},
|
||||||
"gather_accounts_enabled": {"label": _("Gather accounts enabled")},
|
"gather_accounts_enabled": {"label": _("Gather accounts enabled")},
|
||||||
"gather_accounts_method": {"label": _("Gather accounts method")},
|
"gather_accounts_method": {"label": _("Gather accounts method")},
|
||||||
|
"remove_account_method": {"label": _("Remove account method")},
|
||||||
|
"remove_account_enabled": {"label": _("Remove account enabled")},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -124,13 +127,6 @@ class PlatformCustomField(serializers.Serializer):
|
|||||||
|
|
||||||
|
|
||||||
class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
|
class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
|
||||||
SU_METHOD_CHOICES = [
|
|
||||||
("sudo", "sudo su -"),
|
|
||||||
("su", "su - "),
|
|
||||||
("enable", "enable"),
|
|
||||||
("super", "super 15"),
|
|
||||||
("super_level", "super level 15")
|
|
||||||
]
|
|
||||||
id = serializers.IntegerField(
|
id = serializers.IntegerField(
|
||||||
label='ID', required=False,
|
label='ID', required=False,
|
||||||
validators=[UniqueValidator(queryset=Platform.objects.all())]
|
validators=[UniqueValidator(queryset=Platform.objects.all())]
|
||||||
@@ -141,8 +137,8 @@ class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
|
|||||||
protocols = PlatformProtocolSerializer(label=_("Protocols"), many=True, required=False)
|
protocols = PlatformProtocolSerializer(label=_("Protocols"), many=True, required=False)
|
||||||
automation = PlatformAutomationSerializer(label=_("Automation"), required=False, default=dict)
|
automation = PlatformAutomationSerializer(label=_("Automation"), required=False, default=dict)
|
||||||
su_method = LabeledChoiceField(
|
su_method = LabeledChoiceField(
|
||||||
choices=SU_METHOD_CHOICES, label=_("Su method"),
|
choices=SuMethodChoices.choices, label=_("Su method"),
|
||||||
required=False, default="sudo", allow_null=True
|
required=False, default=SuMethodChoices.sudo, allow_null=True
|
||||||
)
|
)
|
||||||
custom_fields = PlatformCustomField(label=_("Custom fields"), many=True, required=False)
|
custom_fields = PlatformCustomField(label=_("Custom fields"), many=True, required=False)
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from orgs.utils import current_org, tmp_to_root_org
|
|||||||
from rbac.permissions import RBACPermission
|
from rbac.permissions import RBACPermission
|
||||||
from terminal.models import default_storage
|
from terminal.models import default_storage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from .backends import TYPE_ENGINE_MAPPING
|
from .backends import get_operate_log_storage
|
||||||
from .const import ActivityChoices
|
from .const import ActivityChoices
|
||||||
from .filters import UserSessionFilterSet, OperateLogFilterSet
|
from .filters import UserSessionFilterSet, OperateLogFilterSet
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -146,7 +146,9 @@ class MyLoginLogViewSet(UserLoginCommonMixin, OrgReadonlyModelViewSet):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
qs = qs.filter(username=self.request.user.username)
|
username = self.request.user.username
|
||||||
|
q = Q(username=username) | Q(username__icontains=f'({username})')
|
||||||
|
qs = qs.filter(q)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
@@ -187,9 +189,13 @@ class ResourceActivityAPIView(generics.ListAPIView):
|
|||||||
'id', 'datetime', 'r_detail', 'r_detail_id',
|
'id', 'datetime', 'r_detail', 'r_detail_id',
|
||||||
'r_user', 'r_action', 'r_type'
|
'r_user', 'r_action', 'r_type'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
org_q = Q()
|
||||||
|
if not current_org.is_root():
|
||||||
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
org_q = Q(org_id=Organization.SYSTEM_ID) | Q(org_id=current_org.id)
|
||||||
if resource_id:
|
if resource_id:
|
||||||
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
org_q |= Q(org_id='') | Q(org_id=Organization.ROOT_ID)
|
||||||
|
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
qs1 = self.get_operate_log_qs(fields, limit, org_q, resource_id=resource_id)
|
qs1 = self.get_operate_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||||
qs2 = self.get_activity_log_qs(fields, limit, org_q, resource_id=resource_id)
|
qs2 = self.get_activity_log_qs(fields, limit, org_q, resource_id=resource_id)
|
||||||
@@ -222,12 +228,10 @@ class OperateLogViewSet(OrgReadonlyModelViewSet):
|
|||||||
if self.is_action_detail:
|
if self.is_action_detail:
|
||||||
with tmp_to_root_org():
|
with tmp_to_root_org():
|
||||||
qs |= OperateLog.objects.filter(org_id=Organization.SYSTEM_ID)
|
qs |= OperateLog.objects.filter(org_id=Organization.SYSTEM_ID)
|
||||||
es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
|
||||||
if es_config:
|
storage = get_operate_log_storage()
|
||||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['es'])
|
if storage.get_type() == 'es':
|
||||||
store = engine_mod.OperateLogStore(es_config)
|
qs = ESQuerySet(storage)
|
||||||
if store.ping(timeout=2):
|
|
||||||
qs = ESQuerySet(store)
|
|
||||||
qs.model = OperateLog
|
qs.model = OperateLog
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,62 @@
|
|||||||
from importlib import import_module
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from common.utils import get_logger
|
||||||
|
from .base import BaseOperateStorage
|
||||||
|
from .es import OperateLogStore as ESOperateLogStore
|
||||||
|
from .db import OperateLogStore as DBOperateLogStore
|
||||||
|
|
||||||
|
|
||||||
TYPE_ENGINE_MAPPING = {
|
logger = get_logger(__file__)
|
||||||
'db': 'audits.backends.db',
|
|
||||||
'es': 'audits.backends.es',
|
_global_op_log_storage: None | ESOperateLogStore | DBOperateLogStore = None
|
||||||
|
op_log_type_mapping = {
|
||||||
|
'server': DBOperateLogStore, 'es': ESOperateLogStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_operate_log_storage(default=False):
|
def _send_es_unavailable_alarm_msg():
|
||||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['db'])
|
from terminal.notifications import StorageConnectivityMessage
|
||||||
es_config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
from terminal.const import CommandStorageType
|
||||||
if not default and es_config:
|
|
||||||
engine_mod = import_module(TYPE_ENGINE_MAPPING['es'])
|
key = 'OPERATE_LOG_ES_UNAVAILABLE_KEY'
|
||||||
storage = engine_mod.OperateLogStore(es_config)
|
if cache.get(key):
|
||||||
return storage
|
return
|
||||||
|
|
||||||
|
cache.set(key, 1, 60)
|
||||||
|
errors = [{
|
||||||
|
'msg': _("Connect failed"), 'name': f"{_('Operate log')}",
|
||||||
|
'type': CommandStorageType.es.label
|
||||||
|
}]
|
||||||
|
StorageConnectivityMessage(errors).publish_async()
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_log_storage():
|
||||||
|
global _global_op_log_storage
|
||||||
|
_global_op_log_storage = None
|
||||||
|
|
||||||
|
if settings.OPERATE_LOG_ELASTICSEARCH_CONFIG.get('HOSTS'):
|
||||||
|
try:
|
||||||
|
config = settings.OPERATE_LOG_ELASTICSEARCH_CONFIG
|
||||||
|
log_storage = op_log_type_mapping['es'](config)
|
||||||
|
_global_op_log_storage = log_storage
|
||||||
|
except Exception as e:
|
||||||
|
_send_es_unavailable_alarm_msg()
|
||||||
|
logger.warning('Invalid logs storage type: es, error: %s' % str(e))
|
||||||
|
|
||||||
|
if not _global_op_log_storage:
|
||||||
|
_global_op_log_storage = op_log_type_mapping['server']()
|
||||||
|
|
||||||
|
|
||||||
|
def get_operate_log_storage():
|
||||||
|
if _global_op_log_storage is None:
|
||||||
|
refresh_log_storage()
|
||||||
|
|
||||||
|
log_storage = _global_op_log_storage
|
||||||
|
if not log_storage.ping(timeout=3):
|
||||||
|
if log_storage.get_type() == 'es':
|
||||||
|
_send_es_unavailable_alarm_msg()
|
||||||
|
logger.warning('Switch default operate log storage.')
|
||||||
|
log_storage = op_log_type_mapping['server']()
|
||||||
|
return log_storage
|
||||||
|
|||||||
15
apps/audits/backends/base.py
Normal file
15
apps/audits/backends/base.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from perms.const import ActionChoices
|
||||||
|
|
||||||
|
|
||||||
|
class BaseOperateStorage(object):
|
||||||
|
@staticmethod
|
||||||
|
def get_type():
|
||||||
|
return 'base'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_special_handler(resource_type):
|
||||||
|
# 根据资源类型,处理特殊字段
|
||||||
|
resource_map = {
|
||||||
|
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
||||||
|
}
|
||||||
|
return resource_map.get(resource_type, lambda k, v: v)
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from audits.models import OperateLog
|
from audits.models import OperateLog
|
||||||
from perms.const import ActionChoices
|
from .base import BaseOperateStorage
|
||||||
|
|
||||||
|
|
||||||
class OperateLogStore(object):
|
class OperateLogStore(BaseOperateStorage):
|
||||||
# 用不可见字符分割前后数据,节省存储-> diff: {'key': 'before\0after'}
|
# 用不可见字符分割前后数据,节省存储-> diff: {'key': 'before\0after'}
|
||||||
SEP = '\0'
|
SEP = '\0'
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, *args, **kwargs):
|
||||||
self.model = OperateLog
|
self.model = OperateLog
|
||||||
self.max_length = 2048
|
self.max_length = 2048
|
||||||
self.max_length_tip_msg = _(
|
self.max_length_tip_msg = _(
|
||||||
@@ -17,9 +17,13 @@ class OperateLogStore(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ping(timeout=None):
|
def ping(*args, **kwargs):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_type():
|
||||||
|
return 'db'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_before_after_to_diff(cls, before, after):
|
def convert_before_after_to_diff(cls, before, after):
|
||||||
if not isinstance(before, dict):
|
if not isinstance(before, dict):
|
||||||
@@ -46,18 +50,13 @@ class OperateLogStore(object):
|
|||||||
before[k], after[k] = before_value, after_value
|
before[k], after[k] = before_value, after_value
|
||||||
return before, after
|
return before, after
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_special_handler(resource_type):
|
|
||||||
# 根据资源类型,处理特殊字段
|
|
||||||
resource_map = {
|
|
||||||
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
|
|
||||||
}
|
|
||||||
return resource_map.get(resource_type, lambda k, v: _(v))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_diff_friendly(cls, op_log):
|
def convert_diff_friendly(cls, op_log):
|
||||||
diff_list = list()
|
diff_list = list()
|
||||||
handler = cls._get_special_handler(op_log.resource_type)
|
handler = cls._get_special_handler(op_log.resource_type)
|
||||||
|
# 标记翻译字符串
|
||||||
|
labels = _("labels")
|
||||||
|
operate_log_id = _("operate_log_id")
|
||||||
for k, v in op_log.diff.items():
|
for k, v in op_log.diff.items():
|
||||||
before, after = v.split(cls.SEP, 1)
|
before, after = v.split(cls.SEP, 1)
|
||||||
diff_list.append({
|
diff_list.append({
|
||||||
|
|||||||
@@ -2,16 +2,17 @@
|
|||||||
#
|
#
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from common.utils.timezone import local_now_display
|
from common.utils.timezone import local_now_display
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from common.utils.encode import Singleton
|
|
||||||
from common.plugins.es import ES
|
from common.plugins.es import ES
|
||||||
|
from .base import BaseOperateStorage
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class OperateLogStore(ES, metaclass=Singleton):
|
class OperateLogStore(BaseOperateStorage, ES):
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
properties = {
|
properties = {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -48,7 +49,26 @@ class OperateLogStore(ES, metaclass=Singleton):
|
|||||||
self.pre_use_check()
|
self.pre_use_check()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make_data(data):
|
def get_type():
|
||||||
|
return 'es'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def convert_diff_friendly(cls, op_log):
|
||||||
|
diff_list = []
|
||||||
|
handler = cls._get_special_handler(op_log.get('resource_type'))
|
||||||
|
before = op_log.get('before') or {}
|
||||||
|
after = op_log.get('after') or {}
|
||||||
|
keys = set(before.keys()) | set(after.keys())
|
||||||
|
for key in keys:
|
||||||
|
before_v, after_v = before.get(key), after.get(key)
|
||||||
|
diff_list.append({
|
||||||
|
'field': _(key),
|
||||||
|
'before': handler(key, before_v) if before_v else _('empty'),
|
||||||
|
'after': handler(key, after_v) if after_v else _('empty'),
|
||||||
|
})
|
||||||
|
return diff_list
|
||||||
|
|
||||||
|
def make_data(self, data):
|
||||||
op_id = data.get('id', str(uuid.uuid4()))
|
op_id = data.get('id', str(uuid.uuid4()))
|
||||||
datetime_param = data.get('datetime', local_now_display())
|
datetime_param = data.get('datetime', local_now_display())
|
||||||
data = {
|
data = {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from common.local import encrypted_field_set
|
from common.local import encrypted_field_set
|
||||||
from common.utils import get_request_ip, get_logger
|
from common.utils import get_request_ip, get_logger
|
||||||
from common.utils.encode import Singleton
|
|
||||||
from common.utils.timezone import as_current_tz
|
from common.utils.timezone import as_current_tz
|
||||||
from jumpserver.utils import current_request
|
from jumpserver.utils import current_request
|
||||||
from orgs.models import Organization
|
from orgs.models import Organization
|
||||||
@@ -21,17 +20,9 @@ from .backends import get_operate_log_storage
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OperatorLogHandler(metaclass=Singleton):
|
class OperatorLogHandler(object):
|
||||||
CACHE_KEY = 'OPERATOR_LOG_CACHE_KEY'
|
CACHE_KEY = 'OPERATOR_LOG_CACHE_KEY'
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.log_client = self.get_storage_client()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_storage_client():
|
|
||||||
client = get_operate_log_storage()
|
|
||||||
return client
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _consistent_type_to_str(value1, value2):
|
def _consistent_type_to_str(value1, value2):
|
||||||
if isinstance(value1, datetime):
|
if isinstance(value1, datetime):
|
||||||
@@ -164,13 +155,8 @@ class OperatorLogHandler(metaclass=Singleton):
|
|||||||
'remote_addr': remote_addr, 'before': before, 'after': after,
|
'remote_addr': remote_addr, 'before': before, 'after': after,
|
||||||
}
|
}
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
if self.log_client.ping(timeout=1):
|
|
||||||
client = self.log_client
|
|
||||||
else:
|
|
||||||
logger.info('Switch default operate log storage save.')
|
|
||||||
client = get_operate_log_storage(default=True)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
client = get_operate_log_storage()
|
||||||
client.save(**data)
|
client.save(**data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = 'An error occurred saving OperateLog.' \
|
error_msg = 'An error occurred saving OperateLog.' \
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='operatelog',
|
model_name='operatelog',
|
||||||
name='action',
|
name='action',
|
||||||
field=models.CharField(choices=[('view', 'View'), ('update', 'Update'), ('delete', 'Delete'), ('create', 'Create'), ('download', 'Download'), ('connect', 'Connect'), ('login', 'Login'), ('change_password', 'Change password'), ('accept', 'Accept'), ('review', 'Review'), ('notice', 'Notifications'), ('reject', 'Reject'), ('approve', 'Approve'), ('close', 'Close')], max_length=16, verbose_name='Action'),
|
field=models.CharField(choices=[('view', 'View'), ('update', 'Update'), ('delete', 'Delete'), ('create', 'Create'), ('download', 'Download'), ('connect', 'Connect'), ('login', 'Login'), ('change_password', 'Change password'), ('accept', 'Accept'), ('review', 'Review'), ('notice', 'Notifications'), ('reject', 'Reject'), ('approve', 'Approve'), ('close', 'Close'), ('finished', 'Finished')], max_length=16, verbose_name='Action'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='userloginlog',
|
model_name='userloginlog',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from audits.backends.db import OperateLogStore
|
from audits.backends import get_operate_log_storage
|
||||||
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
from common.serializers.fields import LabeledChoiceField, ObjectRelatedField
|
||||||
from common.utils import reverse, i18n_trans
|
from common.utils import reverse, i18n_trans
|
||||||
from common.utils.timezone import as_current_tz
|
from common.utils.timezone import as_current_tz
|
||||||
@@ -77,7 +77,7 @@ class OperateLogActionDetailSerializer(serializers.ModelSerializer):
|
|||||||
fields = ('diff',)
|
fields = ('diff',)
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
return {'diff': OperateLogStore.convert_diff_friendly(instance)}
|
return {'diff': get_operate_log_storage().convert_diff_friendly(instance)}
|
||||||
|
|
||||||
|
|
||||||
class OperateLogSerializer(BulkOrgResourceModelSerializer):
|
class OperateLogSerializer(BulkOrgResourceModelSerializer):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from audits.handler import (
|
|||||||
create_or_update_operate_log, get_instance_dict_from_cache
|
create_or_update_operate_log, get_instance_dict_from_cache
|
||||||
)
|
)
|
||||||
from audits.utils import model_to_dict_for_operate_log as model_to_dict
|
from audits.utils import model_to_dict_for_operate_log as model_to_dict
|
||||||
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, SKIP_SIGNAL
|
from common.const.signals import POST_ADD, POST_REMOVE, POST_CLEAR, OP_LOG_SKIP_SIGNAL
|
||||||
from common.signals import django_ready
|
from common.signals import django_ready
|
||||||
from jumpserver.utils import current_request
|
from jumpserver.utils import current_request
|
||||||
from ..const import MODELS_NEED_RECORD, ActionChoices
|
from ..const import MODELS_NEED_RECORD, ActionChoices
|
||||||
@@ -77,7 +77,7 @@ def signal_of_operate_log_whether_continue(
|
|||||||
condition = True
|
condition = True
|
||||||
if not instance:
|
if not instance:
|
||||||
condition = False
|
condition = False
|
||||||
if instance and getattr(instance, SKIP_SIGNAL, False):
|
if instance and getattr(instance, OP_LOG_SKIP_SIGNAL, False):
|
||||||
condition = False
|
condition = False
|
||||||
# 不记录组件的操作日志
|
# 不记录组件的操作日志
|
||||||
user = current_request.user if current_request else None
|
user = current_request.user if current_request else None
|
||||||
@@ -189,7 +189,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
|
|||||||
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
|
||||||
'FavoriteAsset', 'ChangeSecretRecord'
|
'FavoriteAsset', 'ChangeSecretRecord'
|
||||||
}
|
}
|
||||||
include_models = {'UserSession'}
|
include_models = set()
|
||||||
for i, app in enumerate(apps.get_models(), 1):
|
for i, app in enumerate(apps.get_models(), 1):
|
||||||
app_name = app._meta.app_label
|
app_name = app._meta.app_label
|
||||||
model_name = app._meta.object_name
|
model_name = app._meta.object_name
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from common.storage.ftp_file import FTPFileStorageHandler
|
|||||||
from common.utils import get_log_keep_day, get_logger
|
from common.utils import get_log_keep_day, get_logger
|
||||||
from ops.celery.decorator import register_as_period_task
|
from ops.celery.decorator import register_as_period_task
|
||||||
from ops.models import CeleryTaskExecution
|
from ops.models import CeleryTaskExecution
|
||||||
|
from orgs.utils import tmp_to_root_org
|
||||||
from terminal.backends import server_replay_storage
|
from terminal.backends import server_replay_storage
|
||||||
from terminal.models import Session, Command
|
from terminal.models import Session, Command
|
||||||
from .models import UserLoginLog, OperateLog, FTPLog, ActivityLog, PasswordChangeLog
|
from .models import UserLoginLog, OperateLog, FTPLog, ActivityLog, PasswordChangeLog
|
||||||
@@ -105,8 +106,9 @@ def clean_expired_session_period():
|
|||||||
logger.info("Clean session item done")
|
logger.info("Clean session item done")
|
||||||
batch_delete(expired_commands)
|
batch_delete(expired_commands)
|
||||||
logger.info("Clean session command done")
|
logger.info("Clean session command done")
|
||||||
command = "find %s -mtime +%s \\( -name '*.json' -o -name '*.tar' -o -name '*.gz' \\) -exec rm -f {} \\;" % (
|
file_types = "-name '*.json' -o -name '*.tar' -o -name '*.gz' -o -name '*.mp4'"
|
||||||
replay_dir, days
|
command = "find %s -mtime +%s \\( %s \\) -exec rm -f {} \\;" % (
|
||||||
|
replay_dir, days, file_types
|
||||||
)
|
)
|
||||||
subprocess.call(command, shell=True)
|
subprocess.call(command, shell=True)
|
||||||
command = "find %s -type d -empty -delete;" % replay_dir
|
command = "find %s -type d -empty -delete;" % replay_dir
|
||||||
@@ -118,6 +120,7 @@ def clean_expired_session_period():
|
|||||||
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
|
@register_as_period_task(crontab=CRONTAB_AT_AM_TWO)
|
||||||
def clean_audits_log_period():
|
def clean_audits_log_period():
|
||||||
print("Start clean audit session task log")
|
print("Start clean audit session task log")
|
||||||
|
with tmp_to_root_org():
|
||||||
clean_login_log_period()
|
clean_login_log_period()
|
||||||
clean_operation_log_period()
|
clean_operation_log_period()
|
||||||
clean_ftp_log_period()
|
clean_ftp_log_period()
|
||||||
|
|||||||
@@ -472,6 +472,8 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
|||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'create': 'authentication.add_superconnectiontoken',
|
'create': 'authentication.add_superconnectiontoken',
|
||||||
'renewal': 'authentication.add_superconnectiontoken',
|
'renewal': 'authentication.add_superconnectiontoken',
|
||||||
|
'list': 'authentication.view_superconnectiontoken',
|
||||||
|
'retrieve': 'authentication.view_superconnectiontoken',
|
||||||
'get_secret_detail': 'authentication.view_superconnectiontokensecret',
|
'get_secret_detail': 'authentication.view_superconnectiontokensecret',
|
||||||
'get_applet_info': 'authentication.view_superconnectiontoken',
|
'get_applet_info': 'authentication.view_superconnectiontoken',
|
||||||
'release_applet_account': 'authentication.view_superconnectiontoken',
|
'release_applet_account': 'authentication.view_superconnectiontoken',
|
||||||
@@ -479,7 +481,12 @@ class SuperConnectionTokenViewSet(ConnectionTokenViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return ConnectionToken.objects.all()
|
return ConnectionToken.objects.none()
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
pk = self.kwargs.get(self.lookup_field)
|
||||||
|
token = get_object_or_404(ConnectionToken, pk=pk)
|
||||||
|
return token
|
||||||
|
|
||||||
def get_user(self, serializer):
|
def get_user(self, serializer):
|
||||||
return serializer.validated_data.get('user')
|
return serializer.validated_data.get('user')
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from rest_framework.response import Response
|
|||||||
from authentication.errors import ACLError
|
from authentication.errors import ACLError
|
||||||
from common.api import JMSGenericViewSet
|
from common.api import JMSGenericViewSet
|
||||||
from common.const.http import POST, GET
|
from common.const.http import POST, GET
|
||||||
from common.permissions import OnlySuperUser
|
|
||||||
from common.serializers import EmptySerializer
|
from common.serializers import EmptySerializer
|
||||||
from common.utils import reverse, safe_next_url
|
from common.utils import reverse, safe_next_url
|
||||||
from common.utils.timezone import utc_now
|
from common.utils.timezone import utc_now
|
||||||
@@ -38,8 +37,11 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
|
|||||||
'login_url': SSOTokenSerializer,
|
'login_url': SSOTokenSerializer,
|
||||||
'login': EmptySerializer
|
'login': EmptySerializer
|
||||||
}
|
}
|
||||||
|
rbac_perms = {
|
||||||
|
'login_url': 'authentication.add_ssotoken',
|
||||||
|
}
|
||||||
|
|
||||||
@action(methods=[POST], detail=False, permission_classes=[OnlySuperUser], url_path='login-url')
|
@action(methods=[POST], detail=False, url_path='login-url')
|
||||||
def login_url(self, request, *args, **kwargs):
|
def login_url(self, request, *args, **kwargs):
|
||||||
if not settings.AUTH_SSO:
|
if not settings.AUTH_SSO:
|
||||||
raise SSOAuthClosed()
|
raise SSOAuthClosed()
|
||||||
@@ -103,11 +105,9 @@ class SSOViewSet(AuthMixin, JMSGenericViewSet):
|
|||||||
self.request.session['auth_backend'] = settings.AUTH_BACKEND_SSO
|
self.request.session['auth_backend'] = settings.AUTH_BACKEND_SSO
|
||||||
login(self.request, user, settings.AUTH_BACKEND_SSO)
|
login(self.request, user, settings.AUTH_BACKEND_SSO)
|
||||||
self.send_auth_signal(success=True, user=user)
|
self.send_auth_signal(success=True, user=user)
|
||||||
self.mark_mfa_ok('otp', user)
|
|
||||||
|
|
||||||
LoginIpBlockUtil(ip).clean_block_if_need()
|
LoginIpBlockUtil(ip).clean_block_if_need()
|
||||||
LoginBlockUtil(username, ip).clean_failed_count()
|
LoginBlockUtil(username, ip).clean_failed_count()
|
||||||
self.clear_auth_mark()
|
|
||||||
except (ACLError, LoginConfirmBaseError): # 无需记录日志
|
except (ACLError, LoginConfirmBaseError): # 无需记录日志
|
||||||
pass
|
pass
|
||||||
except (AuthFailedError, SSOAuthKeyTTLError) as e:
|
except (AuthFailedError, SSOAuthKeyTTLError) as e:
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class SignatureAuthentication(signature.SignatureAuthentication):
|
|||||||
# example implementation:
|
# example implementation:
|
||||||
try:
|
try:
|
||||||
key = AccessKey.objects.get(id=key_id)
|
key = AccessKey.objects.get(id=key_id)
|
||||||
if not key.is_active:
|
if not key.is_valid:
|
||||||
return None, None
|
return None, None
|
||||||
user, secret = key.user, str(key.secret)
|
user, secret = key.user, str(key.secret)
|
||||||
after_authenticate_update_date(user, key)
|
after_authenticate_update_date(user, key)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import base64
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@@ -67,14 +68,6 @@ class OAuth2Backend(JMSModelBackend):
|
|||||||
response_data = response_data['data']
|
response_data = response_data['data']
|
||||||
return response_data
|
return response_data
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_query_dict(response_data, query_dict):
|
|
||||||
query_dict.update({
|
|
||||||
'uid': response_data.get('uid', ''),
|
|
||||||
'access_token': response_data.get('access_token', '')
|
|
||||||
})
|
|
||||||
return query_dict
|
|
||||||
|
|
||||||
def authenticate(self, request, code=None, **kwargs):
|
def authenticate(self, request, code=None, **kwargs):
|
||||||
log_prompt = "Process authenticate [OAuth2Backend]: {}"
|
log_prompt = "Process authenticate [OAuth2Backend]: {}"
|
||||||
logger.debug(log_prompt.format('Start'))
|
logger.debug(log_prompt.format('Start'))
|
||||||
@@ -83,29 +76,31 @@ class OAuth2Backend(JMSModelBackend):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
query_dict = {
|
query_dict = {
|
||||||
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
'grant_type': 'authorization_code', 'code': code,
|
||||||
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
|
||||||
'grant_type': 'authorization_code',
|
|
||||||
'code': code,
|
|
||||||
'redirect_uri': build_absolute_uri(
|
'redirect_uri': build_absolute_uri(
|
||||||
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
|
request, path=reverse(settings.AUTH_OAUTH2_AUTH_LOGIN_CALLBACK_URL_NAME)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT:
|
separator = '&' if '?' in settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT else '?'
|
||||||
separator = '&'
|
|
||||||
else:
|
|
||||||
separator = '?'
|
|
||||||
access_token_url = '{url}{separator}{query}'.format(
|
access_token_url = '{url}{separator}{query}'.format(
|
||||||
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT, separator=separator, query=urlencode(query_dict)
|
url=settings.AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT,
|
||||||
|
separator=separator, query=urlencode(query_dict)
|
||||||
)
|
)
|
||||||
# token_method -> get, post(post_data), post_json
|
# token_method -> get, post(post_data), post_json
|
||||||
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
|
token_method = settings.AUTH_OAUTH2_ACCESS_TOKEN_METHOD.lower()
|
||||||
logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method))
|
logger.debug(log_prompt.format('Call the access token endpoint[method: %s]' % token_method))
|
||||||
|
encoded_credentials = base64.b64encode(
|
||||||
|
f"{settings.AUTH_OAUTH2_CLIENT_ID}:{settings.AUTH_OAUTH2_CLIENT_SECRET}".encode()
|
||||||
|
).decode()
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json', 'Authorization': f'Basic {encoded_credentials}'
|
||||||
}
|
}
|
||||||
if token_method.startswith('post'):
|
if token_method.startswith('post'):
|
||||||
body_key = 'json' if token_method.endswith('json') else 'data'
|
body_key = 'json' if token_method.endswith('json') else 'data'
|
||||||
|
query_dict.update({
|
||||||
|
'client_id': settings.AUTH_OAUTH2_CLIENT_ID,
|
||||||
|
'client_secret': settings.AUTH_OAUTH2_CLIENT_SECRET,
|
||||||
|
})
|
||||||
access_token_response = requests.post(
|
access_token_response = requests.post(
|
||||||
access_token_url, headers=headers, **{body_key: query_dict}
|
access_token_url, headers=headers, **{body_key: query_dict}
|
||||||
)
|
)
|
||||||
@@ -121,22 +116,12 @@ class OAuth2Backend(JMSModelBackend):
|
|||||||
logger.error(log_prompt.format(error))
|
logger.error(log_prompt.format(error))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
query_dict = self.get_query_dict(response_data, query_dict)
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Authorization': 'Bearer {}'.format(response_data.get('access_token', ''))
|
'Authorization': 'Bearer {}'.format(response_data.get('access_token', ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(log_prompt.format('Get userinfo endpoint'))
|
logger.debug(log_prompt.format('Get userinfo endpoint'))
|
||||||
if '?' in settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT:
|
userinfo_url = settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT
|
||||||
separator = '&'
|
|
||||||
else:
|
|
||||||
separator = '?'
|
|
||||||
userinfo_url = '{url}{separator}{query}'.format(
|
|
||||||
url=settings.AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT, separator=separator,
|
|
||||||
query=urlencode(query_dict)
|
|
||||||
)
|
|
||||||
userinfo_response = requests.get(userinfo_url, headers=headers)
|
userinfo_response = requests.get(userinfo_url, headers=headers)
|
||||||
try:
|
try:
|
||||||
userinfo_response.raise_for_status()
|
userinfo_response.raise_for_status()
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
|||||||
# parameters because we won't be able to get a valid token for the user in that case.
|
# parameters because we won't be able to get a valid token for the user in that case.
|
||||||
if (state is None and settings.AUTH_OPENID_USE_STATE) or code is None:
|
if (state is None and settings.AUTH_OPENID_USE_STATE) or code is None:
|
||||||
logger.debug(log_prompt.format('Authorization code or state value is missing'))
|
logger.debug(log_prompt.format('Authorization code or state value is missing'))
|
||||||
raise SuspiciousOperation('Authorization code or state value is missing')
|
return
|
||||||
|
|
||||||
# Prepares the token payload that will be used to request an authentication token to the
|
# Prepares the token payload that will be used to request an authentication token to the
|
||||||
# token endpoint of the OIDC provider.
|
# token endpoint of the OIDC provider.
|
||||||
@@ -165,7 +165,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
|||||||
error = "Json token response error, token response " \
|
error = "Json token response error, token response " \
|
||||||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||||
logger.debug(log_prompt.format(error))
|
logger.debug(log_prompt.format(error))
|
||||||
raise ParseError(error)
|
return
|
||||||
|
|
||||||
# Validates the token.
|
# Validates the token.
|
||||||
logger.debug(log_prompt.format('Validate ID Token'))
|
logger.debug(log_prompt.format('Validate ID Token'))
|
||||||
@@ -206,7 +206,7 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
|||||||
error = "Json claims response error, claims response " \
|
error = "Json claims response error, claims response " \
|
||||||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||||
logger.debug(log_prompt.format(error))
|
logger.debug(log_prompt.format(error))
|
||||||
raise ParseError(error)
|
return
|
||||||
|
|
||||||
logger.debug(log_prompt.format('Get or create user from claims'))
|
logger.debug(log_prompt.format('Get or create user from claims'))
|
||||||
user, created = self.get_or_create_user_from_claims(request, claims)
|
user, created = self.get_or_create_user_from_claims(request, claims)
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from django.views import View
|
|
||||||
from django.contrib import auth
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.contrib import auth
|
||||||
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseServerError
|
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseServerError
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views import View
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
||||||
from onelogin.saml2.errors import OneLogin_Saml2_Error
|
from onelogin.saml2.errors import OneLogin_Saml2_Error
|
||||||
from onelogin.saml2.idp_metadata_parser import (
|
from onelogin.saml2.idp_metadata_parser import (
|
||||||
@@ -16,23 +14,29 @@ from onelogin.saml2.idp_metadata_parser import (
|
|||||||
dict_deep_merge
|
dict_deep_merge
|
||||||
)
|
)
|
||||||
|
|
||||||
from .settings import JmsSaml2Settings
|
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
from .settings import JmsSaml2Settings
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
class PrepareRequestMixin:
|
class PrepareRequestMixin:
|
||||||
@staticmethod
|
|
||||||
def is_secure():
|
@property
|
||||||
url_result = parse.urlparse(settings.SITE_URL)
|
def parsed_url(self):
|
||||||
return 'on' if url_result.scheme == 'https' else 'off'
|
return parse.urlparse(settings.SITE_URL)
|
||||||
|
|
||||||
|
def is_secure(self):
|
||||||
|
return 'on' if self.parsed_url.scheme == 'https' else 'off'
|
||||||
|
|
||||||
|
def http_host(self):
|
||||||
|
return f"{self.parsed_url.hostname}:{self.parsed_url.port}" \
|
||||||
|
if self.parsed_url.port else self.parsed_url.hostname
|
||||||
|
|
||||||
def prepare_django_request(self, request):
|
def prepare_django_request(self, request):
|
||||||
result = {
|
result = {
|
||||||
'https': self.is_secure(),
|
'https': self.is_secure(),
|
||||||
'http_host': request.META['HTTP_HOST'],
|
'http_host': self.http_host(),
|
||||||
'script_name': request.META['PATH_INFO'],
|
'script_name': request.META['PATH_INFO'],
|
||||||
'get_data': request.GET.copy(),
|
'get_data': request.GET.copy(),
|
||||||
'post_data': request.POST.copy()
|
'post_data': request.POST.copy()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import base64
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import logout as auth_logout
|
from django.contrib.auth import logout as auth_logout
|
||||||
|
from django.core.cache import cache
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect, reverse, render
|
from django.shortcuts import redirect, reverse, render
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
@@ -76,6 +77,7 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
|
|||||||
ip = get_request_ip(request)
|
ip = get_request_ip(request)
|
||||||
try:
|
try:
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.check_is_block()
|
||||||
self._check_third_party_login_acl()
|
self._check_third_party_login_acl()
|
||||||
self._check_login_acl(request.user, ip)
|
self._check_login_acl(request.user, ip)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -116,23 +118,43 @@ class ThirdPartyLoginMiddleware(mixins.AuthMixin):
|
|||||||
|
|
||||||
|
|
||||||
class SessionCookieMiddleware(MiddlewareMixin):
|
class SessionCookieMiddleware(MiddlewareMixin):
|
||||||
|
USER_LOGIN_ENCRYPTION_KEY_PAIR = 'user_login_encryption_key_pair'
|
||||||
|
|
||||||
@staticmethod
|
def set_cookie_public_key(self, request, response):
|
||||||
def set_cookie_public_key(request, response):
|
|
||||||
if request.path.startswith('/api'):
|
if request.path.startswith('/api'):
|
||||||
return
|
return
|
||||||
pub_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
|
|
||||||
public_key = request.session.get(pub_key_name)
|
session_public_key_name = settings.SESSION_RSA_PUBLIC_KEY_NAME
|
||||||
cookie_key = request.COOKIES.get(pub_key_name)
|
session_private_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
|
||||||
if public_key and public_key == cookie_key:
|
|
||||||
|
session_public_key = request.session.get(session_public_key_name)
|
||||||
|
cookie_public_key = request.COOKIES.get(session_public_key_name)
|
||||||
|
|
||||||
|
if session_public_key and session_public_key == cookie_public_key:
|
||||||
return
|
return
|
||||||
|
|
||||||
pri_key_name = settings.SESSION_RSA_PRIVATE_KEY_NAME
|
private_key, public_key = self.get_key_pair()
|
||||||
private_key, public_key = gen_key_pair()
|
|
||||||
public_key_decode = base64.b64encode(public_key.encode()).decode()
|
public_key_decode = base64.b64encode(public_key.encode()).decode()
|
||||||
request.session[pub_key_name] = public_key_decode
|
|
||||||
request.session[pri_key_name] = private_key
|
request.session[session_public_key_name] = public_key_decode
|
||||||
response.set_cookie(pub_key_name, public_key_decode)
|
request.session[session_private_key_name] = private_key
|
||||||
|
response.set_cookie(session_public_key_name, public_key_decode)
|
||||||
|
|
||||||
|
def get_key_pair(self):
|
||||||
|
key_pair = cache.get(self.USER_LOGIN_ENCRYPTION_KEY_PAIR)
|
||||||
|
if key_pair:
|
||||||
|
return key_pair['private_key'], key_pair['public_key']
|
||||||
|
|
||||||
|
private_key, public_key = gen_key_pair()
|
||||||
|
|
||||||
|
key_pair = {
|
||||||
|
'private_key': private_key,
|
||||||
|
'public_key': public_key
|
||||||
|
}
|
||||||
|
cache.set(self.USER_LOGIN_ENCRYPTION_KEY_PAIR, key_pair, None)
|
||||||
|
|
||||||
|
return private_key, public_key
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_cookie_session_prefix(request, response):
|
def set_cookie_session_prefix(request, response):
|
||||||
|
|||||||
@@ -319,20 +319,26 @@ class AuthPostCheckMixin:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_passwd_is_too_simple(cls, user: User, password):
|
def _check_passwd_is_too_simple(cls, user: User, password):
|
||||||
if user.is_superuser and password == 'admin':
|
if not user.is_auth_backend_model():
|
||||||
|
return
|
||||||
|
if user.check_passwd_too_simple(password):
|
||||||
message = _('Your password is too simple, please change it for security')
|
message = _('Your password is too simple, please change it for security')
|
||||||
url = cls.generate_reset_password_url_with_flash_msg(user, message=message)
|
url = cls.generate_reset_password_url_with_flash_msg(user, message=message)
|
||||||
raise errors.PasswordTooSimple(url)
|
raise errors.PasswordTooSimple(url)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_passwd_need_update(cls, user: User):
|
def _check_passwd_need_update(cls, user: User):
|
||||||
if user.need_update_password:
|
if not user.is_auth_backend_model():
|
||||||
|
return
|
||||||
|
if user.check_need_update_password():
|
||||||
message = _('You should to change your password before login')
|
message = _('You should to change your password before login')
|
||||||
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
||||||
raise errors.PasswordNeedUpdate(url)
|
raise errors.PasswordNeedUpdate(url)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _check_password_require_reset_or_not(cls, user: User):
|
def _check_password_require_reset_or_not(cls, user: User):
|
||||||
|
if not user.is_auth_backend_model():
|
||||||
|
return
|
||||||
if user.password_has_expired:
|
if user.password_has_expired:
|
||||||
message = _('Your password has expired, please reset before logging in')
|
message = _('Your password has expired, please reset before logging in')
|
||||||
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
url = cls.generate_reset_password_url_with_flash_msg(user, message)
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ from .connection_token import *
|
|||||||
from .private_token import *
|
from .private_token import *
|
||||||
from .sso_token import *
|
from .sso_token import *
|
||||||
from .temp_token import *
|
from .temp_token import *
|
||||||
|
from ..backends.passkey.models import *
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ class AccessKey(models.Model):
|
|||||||
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
|
date_last_used = models.DateTimeField(null=True, blank=True, verbose_name=_('Date last used'))
|
||||||
date_created = models.DateTimeField(auto_now_add=True)
|
date_created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_valid(self):
|
||||||
|
return self.is_active and self.user.is_valid
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return str(self.id)
|
return str(self.id)
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ class ConnectionToken(JMSOrgBaseModel):
|
|||||||
|
|
||||||
host_account = applet.select_host_account(self.user, self.asset)
|
host_account = applet.select_host_account(self.user, self.asset)
|
||||||
if not host_account:
|
if not host_account:
|
||||||
raise JMSException({'error': 'No host account available'})
|
raise JMSException({'error': 'No host account available, please check the applet, host and account'})
|
||||||
|
|
||||||
host, account, lock_key = bulk_get(host_account, ('host', 'account', 'lock_key'))
|
host, account, lock_key = bulk_get(host_account, ('host', 'account', 'lock_key'))
|
||||||
gateway = host.domain.select_gateway() if host.domain else None
|
gateway = host.domain.select_gateway() if host.domain else None
|
||||||
|
|||||||
@@ -8,10 +8,8 @@
|
|||||||
<p>
|
<p>
|
||||||
<b>{% trans 'Username' %}:</b> {{ username }}<br>
|
<b>{% trans 'Username' %}:</b> {{ username }}<br>
|
||||||
<b>{% trans 'Login time' %}:</b> {{ time }}<br>
|
<b>{% trans 'Login time' %}:</b> {{ time }}<br>
|
||||||
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})
|
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})<br>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
-
|
|
||||||
<p>
|
<p>
|
||||||
{% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %}
|
{% trans 'If you suspect that the login behavior is abnormal, please modify the account password in time.' %}
|
||||||
</p>
|
</p>
|
||||||
@@ -10,8 +10,7 @@
|
|||||||
{% trans 'Click here reset password' %}
|
{% trans 'Click here reset password' %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<br>
|
||||||
-
|
|
||||||
<p>
|
<p>
|
||||||
{% trans 'This link is valid for 1 hour. After it expires' %}
|
{% trans 'This link is valid for 1 hour. After it expires' %}
|
||||||
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
|
<a href="{{ forget_password_url }}?email={{ user.email }}">{% trans 'request new one' %}</a>
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
{% trans 'Your password has just been successfully updated' %}
|
{% trans 'Your password has just been successfully updated' %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br />
|
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br/>
|
||||||
<b>{% trans 'Browser' %}:</b> {{ browser }}
|
<b>{% trans 'Browser' %}:</b> {{ browser }} <br>
|
||||||
</p>
|
</p>
|
||||||
-
|
|
||||||
<p>
|
<p>
|
||||||
{% trans 'If the password update was not initiated by you, your account may have security issues' %} <br />
|
{% trans 'If the password update was not initiated by you, your account may have security issues' %} <br/>
|
||||||
{% trans 'If you have any questions, you can contact the administrator' %}
|
{% trans 'If you have any questions, you can contact the administrator' %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
{% trans 'Your public key has just been successfully updated' %}
|
{% trans 'Your public key has just been successfully updated' %}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br />
|
<b>{% trans 'IP' %}:</b> {{ ip_address }} <br>
|
||||||
<b>{% trans 'Browser' %}:</b> {{ browser }}
|
<b>{% trans 'Browser' %}:</b> {{ browser }}<br>
|
||||||
</p>
|
</p>
|
||||||
-
|
|
||||||
<p>
|
<p>
|
||||||
{% trans 'If the public key update was not initiated by you, your account may have security issues' %} <br />
|
{% trans 'If the public key update was not initiated by you, your account may have security issues' %} <br/>
|
||||||
{% trans 'If you have any questions, you can contact the administrator' %}
|
{% trans 'If you have any questions, you can contact the administrator' %}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -396,7 +396,7 @@
|
|||||||
|
|
||||||
</body>
|
</body>
|
||||||
{% include '_foot_js.html' %}
|
{% include '_foot_js.html' %}
|
||||||
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.min.js"></script>
|
<script type="text/javascript" src="/static/js/plugins/jsencrypt/jsencrypt.3.3.2.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/plugins/cryptojs/crypto-js.min.js"></script>
|
<script type="text/javascript" src="/static/js/plugins/cryptojs/crypto-js.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/plugins/buffer/buffer.min.js"></script>
|
<script type="text/javascript" src="/static/js/plugins/buffer/buffer.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .utils import gen_key_pair, rsa_decrypt, rsa_encrypt
|
from common.utils import gen_key_pair, rsa_decrypt, rsa_encrypt
|
||||||
|
|
||||||
|
|
||||||
def test_rsa_encrypt_decrypt(message='test-password-$%^&*'):
|
def test_rsa_encrypt_decrypt(message='test-password-$%^&*'):
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, IMClientMixin, View):
|
|||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def create_user_if_not_exist(self, user_id, **kwargs):
|
def create_user_if_not_exist(self, user_id, **kwargs):
|
||||||
user = None
|
user = None
|
||||||
user_attr = self.client.get_user_detail(user_id, **kwargs)
|
user_attr = self.client.get_user_detail(user_id, **kwargs)
|
||||||
@@ -122,9 +119,6 @@ class BaseBindCallbackView(FlashMessageMixin, IMClientMixin, View):
|
|||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def get_already_bound_response(self, redirect_uri):
|
def get_already_bound_response(self, redirect_uri):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@@ -151,11 +145,9 @@ class BaseBindCallbackView(FlashMessageMixin, IMClientMixin, View):
|
|||||||
setattr(user, f'{self.auth_type}_id', auth_user_id)
|
setattr(user, f'{self.auth_type}_id', auth_user_id)
|
||||||
user.save()
|
user.save()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
if e.args[0] == 1062:
|
|
||||||
msg = _('The %s is already bound to another user') % self.auth_type_label
|
msg = _('The %s is already bound to another user') % self.auth_type_label
|
||||||
response = self.get_failed_response(redirect_url, msg, msg)
|
response = self.get_failed_response(redirect_url, msg, msg)
|
||||||
return response
|
return response
|
||||||
raise e
|
|
||||||
|
|
||||||
ip = get_request_ip(request)
|
ip = get_request_ip(request)
|
||||||
OAuthBindMessage(user, ip, self.auth_type_label, auth_user_id).publish_async()
|
OAuthBindMessage(user, ip, self.auth_type_label, auth_user_id).publish_async()
|
||||||
|
|||||||
@@ -47,15 +47,7 @@ class DingTalkBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, Fla
|
|||||||
)
|
)
|
||||||
|
|
||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
state = self.request.GET.get('state')
|
return self.verify_state_with_session_key(DINGTALK_STATE_SESSION_KEY)
|
||||||
session_state = self.request.session.get(DINGTALK_STATE_SESSION_KEY)
|
|
||||||
if state != session_state:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
msg = _("The system configuration is incorrect. Please contact your administrator")
|
|
||||||
return self.get_failed_response(redirect_uri, msg, msg)
|
|
||||||
|
|
||||||
def get_already_bound_response(self, redirect_url):
|
def get_already_bound_response(self, redirect_url):
|
||||||
msg = _('DingTalk is already bound')
|
msg = _('DingTalk is already bound')
|
||||||
|
|||||||
@@ -58,15 +58,7 @@ class FeiShuQRMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMe
|
|||||||
)
|
)
|
||||||
|
|
||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
state = self.request.GET.get('state')
|
return self.verify_state_with_session_key(self.state_session_key)
|
||||||
session_state = self.request.session.get(self.state_session_key)
|
|
||||||
if state != session_state:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
msg = _("The system configuration is incorrect. Please contact your administrator")
|
|
||||||
return self.get_failed_response(redirect_uri, msg, msg)
|
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
state = random_string(16)
|
state = random_string(16)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from common.utils import FlashMessageUtil
|
from common.utils import FlashMessageUtil
|
||||||
|
|
||||||
|
|
||||||
@@ -32,3 +33,12 @@ class FlashMessageMixin:
|
|||||||
|
|
||||||
def get_failed_response(self, redirect_url, title, msg, interval=10):
|
def get_failed_response(self, redirect_url, title, msg, interval=10):
|
||||||
return self.get_response(redirect_url, title, msg, 'error', interval)
|
return self.get_response(redirect_url, title, msg, 'error', interval)
|
||||||
|
|
||||||
|
def get_verify_state_failed_response(self, redirect_uri):
|
||||||
|
msg = _(
|
||||||
|
"For your safety, automatic redirection login is not supported on the client."
|
||||||
|
" If you need to open it in the client, please log in again")
|
||||||
|
return self.get_failed_response(redirect_uri, msg, msg)
|
||||||
|
|
||||||
|
def verify_state_with_session_key(self, session_key):
|
||||||
|
return self.request.GET.get('state') == self.request.session.get(session_key)
|
||||||
|
|||||||
@@ -37,15 +37,7 @@ class SlackMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashMessa
|
|||||||
)
|
)
|
||||||
|
|
||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
state = self.request.GET.get('state')
|
return self.verify_state_with_session_key(SLACK_STATE_SESSION_KEY)
|
||||||
session_state = self.request.session.get(SLACK_STATE_SESSION_KEY)
|
|
||||||
if state != session_state:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
msg = _("The system configuration is incorrect. Please contact your administrator")
|
|
||||||
return self.get_failed_response(redirect_uri, msg, msg)
|
|
||||||
|
|
||||||
def get_qr_url(self, redirect_uri):
|
def get_qr_url(self, redirect_uri):
|
||||||
state = random_string(16)
|
state = random_string(16)
|
||||||
|
|||||||
@@ -45,15 +45,7 @@ class WeComBaseMixin(UserConfirmRequiredExceptionMixin, PermissionsMixin, FlashM
|
|||||||
)
|
)
|
||||||
|
|
||||||
def verify_state(self):
|
def verify_state(self):
|
||||||
state = self.request.GET.get('state')
|
return self.verify_state_with_session_key(WECOM_STATE_SESSION_KEY)
|
||||||
session_state = self.request.session.get(WECOM_STATE_SESSION_KEY)
|
|
||||||
if state != session_state:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_verify_state_failed_response(self, redirect_uri):
|
|
||||||
msg = _("The system configuration is incorrect. Please contact your administrator")
|
|
||||||
return self.get_failed_response(redirect_uri, msg, msg)
|
|
||||||
|
|
||||||
def get_already_bound_response(self, redirect_url):
|
def get_already_bound_response(self, redirect_url):
|
||||||
msg = _('WeCom is already bound')
|
msg = _('WeCom is already bound')
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from rest_framework.request import Request
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from common.const.http import POST
|
from common.const.http import POST
|
||||||
|
from orgs.models import Organization
|
||||||
|
from orgs.utils import current_org
|
||||||
|
|
||||||
__all__ = ['SuggestionMixin', 'RenderToJsonMixin']
|
__all__ = ['SuggestionMixin', 'RenderToJsonMixin']
|
||||||
|
|
||||||
@@ -23,7 +25,16 @@ class SuggestionMixin:
|
|||||||
|
|
||||||
@action(methods=['get'], detail=False, url_path='suggestions')
|
@action(methods=['get'], detail=False, url_path='suggestions')
|
||||||
def match(self, request, *args, **kwargs):
|
def match(self, request, *args, **kwargs):
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
queryset = self.get_queryset()
|
||||||
|
org_id = str(current_org.id)
|
||||||
|
if (
|
||||||
|
not request.user.is_superuser and
|
||||||
|
org_id != Organization.ROOT_ID and
|
||||||
|
not request.user.orgs.filter(id=org_id).exists()
|
||||||
|
):
|
||||||
|
queryset = queryset.none()
|
||||||
|
|
||||||
|
queryset = self.filter_queryset(queryset)
|
||||||
queryset = queryset[:self.suggestion_limit]
|
queryset = queryset[:self.suggestion_limit]
|
||||||
page = self.paginate_queryset(queryset)
|
page = self.paginate_queryset(queryset)
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ POST_CLEAR = 'post_clear'
|
|||||||
POST_PREFIX = 'post'
|
POST_PREFIX = 'post'
|
||||||
PRE_PREFIX = 'pre'
|
PRE_PREFIX = 'pre'
|
||||||
|
|
||||||
SKIP_SIGNAL = 'skip_signal'
|
OP_LOG_SKIP_SIGNAL = 'operate_log_skip_signal'
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from django.db.models import F, ExpressionWrapper, CASCADE
|
|||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ..const.signals import SKIP_SIGNAL
|
from ..const.signals import OP_LOG_SKIP_SIGNAL
|
||||||
|
|
||||||
|
|
||||||
class ChoicesMixin:
|
class ChoicesMixin:
|
||||||
@@ -82,7 +82,7 @@ def CASCADE_SIGNAL_SKIP(collector, field, sub_objs, using):
|
|||||||
# 级联删除时,操作日志标记不保存,以免用户混淆
|
# 级联删除时,操作日志标记不保存,以免用户混淆
|
||||||
try:
|
try:
|
||||||
for obj in sub_objs:
|
for obj in sub_objs:
|
||||||
setattr(obj, SKIP_SIGNAL, True)
|
setattr(obj, OP_LOG_SKIP_SIGNAL, True)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ known_unauth_urls = [
|
|||||||
"/api/v1/authentication/login-confirm-ticket/status/",
|
"/api/v1/authentication/login-confirm-ticket/status/",
|
||||||
"/api/v1/authentication/mfa/select/",
|
"/api/v1/authentication/mfa/select/",
|
||||||
"/api/v1/authentication/mfa/send-code/",
|
"/api/v1/authentication/mfa/send-code/",
|
||||||
"/api/v1/authentication/sso/login/"
|
"/api/v1/authentication/sso/login/",
|
||||||
|
"/api/v1/authentication/user-session/"
|
||||||
]
|
]
|
||||||
|
|
||||||
known_error_urls = [
|
known_error_urls = [
|
||||||
@@ -91,7 +92,6 @@ class Command(BaseCommand):
|
|||||||
unauth_urls = []
|
unauth_urls = []
|
||||||
error_urls = []
|
error_urls = []
|
||||||
unformat_urls = []
|
unformat_urls = []
|
||||||
|
|
||||||
for url, ourl in urls:
|
for url, ourl in urls:
|
||||||
if '(' in url or '<' in url:
|
if '(' in url or '<' in url:
|
||||||
unformat_urls.append([url, ourl])
|
unformat_urls.append([url, ourl])
|
||||||
|
|||||||
@@ -190,7 +190,8 @@ class ES(object):
|
|||||||
mappings['aliases'] = {
|
mappings['aliases'] = {
|
||||||
self.query_index: {}
|
self.query_index: {}
|
||||||
}
|
}
|
||||||
|
if self.es.indices.exists(index=self.index):
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self.es.indices.create(index=self.index, body=mappings)
|
self.es.indices.create(index=self.index, body=mappings)
|
||||||
except (RequestError, BadRequestError) as e:
|
except (RequestError, BadRequestError) as e:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
cipher_alg_id = {
|
cipher_alg_id = {
|
||||||
"sm4_ebc": 0x00000401,
|
"sm4_ebc": 0x00000401,
|
||||||
"sm4_cbc": 0x00000402,
|
"sm4_cbc": 0x00000402,
|
||||||
|
"sm4_mac": 0x00000405,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class CMPPSubmitRequestInstance(CMPPBaseRequestInstance):
|
|||||||
pk_number = struct.pack('!B', 1)
|
pk_number = struct.pack('!B', 1)
|
||||||
registered_delivery = struct.pack('!B', 0)
|
registered_delivery = struct.pack('!B', 0)
|
||||||
msg_level = struct.pack('!B', 0)
|
msg_level = struct.pack('!B', 0)
|
||||||
service_id = ((10 - len(service_id)) * '\x00' + service_id).encode('utf-8')
|
service_id = service_id.ljust(10, '\x00').encode('utf-8')
|
||||||
fee_user_type = struct.pack('!B', 2)
|
fee_user_type = struct.pack('!B', 2)
|
||||||
fee_terminal_id = ('0' * 21).encode('utf-8')
|
fee_terminal_id = ('0' * 21).encode('utf-8')
|
||||||
tp_pid = struct.pack('!B', 0)
|
tp_pid = struct.pack('!B', 0)
|
||||||
@@ -85,7 +85,7 @@ class CMPPSubmitRequestInstance(CMPPBaseRequestInstance):
|
|||||||
fee_code = '000000'.encode('utf-8')
|
fee_code = '000000'.encode('utf-8')
|
||||||
valid_time = ('\x00' * 17).encode('utf-8')
|
valid_time = ('\x00' * 17).encode('utf-8')
|
||||||
at_time = ('\x00' * 17).encode('utf-8')
|
at_time = ('\x00' * 17).encode('utf-8')
|
||||||
src_id = ((21 - len(src_id)) * '\x00' + src_id).encode('utf-8')
|
src_id = src_id.ljust(21, '\x00').encode('utf-8')
|
||||||
reserve = b'\x00' * 8
|
reserve = b'\x00' * 8
|
||||||
_msg_length = struct.pack('!B', len(msg_content) * 2)
|
_msg_length = struct.pack('!B', len(msg_content) * 2)
|
||||||
_msg_src = msg_src.encode('utf-8')
|
_msg_src = msg_src.encode('utf-8')
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ class CustomSMS(BaseSMSClient):
|
|||||||
kwargs = {'params': params}
|
kwargs = {'params': params}
|
||||||
try:
|
try:
|
||||||
response = action(url=settings.CUSTOM_SMS_URL, verify=False, **kwargs)
|
response = action(url=settings.CUSTOM_SMS_URL, verify=False, **kwargs)
|
||||||
if response.reason != 'OK':
|
response.raise_for_status()
|
||||||
raise JMSException(detail=response.text, code=response.status_code)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error('Custom sms error: {}'.format(exc))
|
logger.error('Custom sms error: {}'.format(exc))
|
||||||
raise JMSException(exc)
|
raise JMSException(exc)
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ def date_expired_default():
|
|||||||
years = 70
|
years = 70
|
||||||
return timezone.now() + timezone.timedelta(days=365 * years)
|
return timezone.now() + timezone.timedelta(days=365 * years)
|
||||||
|
|
||||||
|
def user_date_expired_default():
|
||||||
|
try:
|
||||||
|
days = int(settings.USER_DEFAULT_EXPIRED_DAYS)
|
||||||
|
except TypeError:
|
||||||
|
days = 25550
|
||||||
|
return timezone.now() + timezone.timedelta(days=days)
|
||||||
|
|
||||||
|
def asset_permission_date_expired_default():
|
||||||
|
try:
|
||||||
|
days = int(settings.ASSET_PERMISSION_DEFAULT_EXPIRED_DAYS)
|
||||||
|
except TypeError:
|
||||||
|
days = 25550
|
||||||
|
return timezone.now() + timezone.timedelta(days=days)
|
||||||
|
|
||||||
def union_queryset(*args, base_queryset=None):
|
def union_queryset(*args, base_queryset=None):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ def i18n_fmt(tpl, *args):
|
|||||||
return tpl
|
return tpl
|
||||||
|
|
||||||
args = [str(arg) for arg in args]
|
args = [str(arg) for arg in args]
|
||||||
|
args = [arg.replace(', ', ' ') for arg in args]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tpl % tuple(args)
|
tpl % tuple(args)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ from common.utils.random import random_string
|
|||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(verbose_name=_('Send email'))
|
@shared_task(verbose_name=_('Send SMS code'))
|
||||||
def send_async(sender):
|
def send_sms_async(target, code):
|
||||||
sender.gen_and_send()
|
SMS().send_verify_code(target, code)
|
||||||
|
|
||||||
|
|
||||||
class SendAndVerifyCodeUtil(object):
|
class SendAndVerifyCodeUtil(object):
|
||||||
@@ -35,7 +35,7 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
logger.warning('Send sms too frequently, delay {}'.format(ttl))
|
logger.warning('Send sms too frequently, delay {}'.format(ttl))
|
||||||
raise CodeSendTooFrequently(ttl)
|
raise CodeSendTooFrequently(ttl)
|
||||||
|
|
||||||
return send_async.apply_async(kwargs={"sender": self}, priority=100)
|
return self.gen_and_send()
|
||||||
|
|
||||||
def gen_and_send(self):
|
def gen_and_send(self):
|
||||||
try:
|
try:
|
||||||
@@ -72,13 +72,15 @@ class SendAndVerifyCodeUtil(object):
|
|||||||
return code
|
return code
|
||||||
|
|
||||||
def __send_with_sms(self):
|
def __send_with_sms(self):
|
||||||
sms = SMS()
|
send_sms_async.apply_async(args=(self.target, self.code), priority=100)
|
||||||
sms.send_verify_code(self.target, self.code)
|
|
||||||
|
|
||||||
def __send_with_email(self):
|
def __send_with_email(self):
|
||||||
subject = self.other_args.get('subject')
|
subject = self.other_args.get('subject', '')
|
||||||
message = self.other_args.get('message')
|
message = self.other_args.get('message', '')
|
||||||
send_mail_async(subject, message, [self.target], html_message=message)
|
send_mail_async.apply_async(
|
||||||
|
args=(subject, message, [self.target]),
|
||||||
|
kwargs={'html_message': message}, priority=100
|
||||||
|
)
|
||||||
|
|
||||||
def __send(self, code):
|
def __send(self, code):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class DateTimeMixin:
|
|||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def user_login_logs_on_the_system_queryset(self):
|
def user_login_logs_on_the_system_queryset(self):
|
||||||
qs = UserLoginLog.objects.all()
|
qs = UserLoginLog.objects.filter(status=LoginStatusChoices.success)
|
||||||
qs = self.get_logs_queryset_filter(qs, 'datetime')
|
qs = self.get_logs_queryset_filter(qs, 'datetime')
|
||||||
queryset = qs.filter(username__in=construct_userlogin_usernames(self.users))
|
queryset = qs.filter(username__in=construct_userlogin_usernames(self.users))
|
||||||
return queryset
|
return queryset
|
||||||
|
|||||||
@@ -229,11 +229,14 @@ class Config(dict):
|
|||||||
|
|
||||||
'TOKEN_EXPIRATION': 3600 * 24,
|
'TOKEN_EXPIRATION': 3600 * 24,
|
||||||
'DEFAULT_EXPIRED_YEARS': 70,
|
'DEFAULT_EXPIRED_YEARS': 70,
|
||||||
|
'USER_DEFAULT_EXPIRED_DAYS': 25550,
|
||||||
|
'ASSET_PERMISSION_DEFAULT_EXPIRED_DAYS': 25550,
|
||||||
'SESSION_COOKIE_DOMAIN': None,
|
'SESSION_COOKIE_DOMAIN': None,
|
||||||
'CSRF_COOKIE_DOMAIN': None,
|
'CSRF_COOKIE_DOMAIN': None,
|
||||||
'SESSION_COOKIE_NAME_PREFIX': None,
|
'SESSION_COOKIE_NAME_PREFIX': None,
|
||||||
'SESSION_COOKIE_AGE': 3600 * 24,
|
'SESSION_COOKIE_AGE': 3600 * 24,
|
||||||
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
'SESSION_EXPIRE_AT_BROWSER_CLOSE': False,
|
||||||
|
'VIEW_ASSET_ONLINE_SESSION_INFO': True,
|
||||||
'LOGIN_URL': reverse_lazy('authentication:login'),
|
'LOGIN_URL': reverse_lazy('authentication:login'),
|
||||||
|
|
||||||
'CONNECTION_TOKEN_ONETIME_EXPIRATION': 5 * 60, # 默认(new)
|
'CONNECTION_TOKEN_ONETIME_EXPIRATION': 5 * 60, # 默认(new)
|
||||||
@@ -487,6 +490,7 @@ class Config(dict):
|
|||||||
'TERMINAL_OMNIDB_ENABLED': True,
|
'TERMINAL_OMNIDB_ENABLED': True,
|
||||||
|
|
||||||
# 安全配置
|
# 安全配置
|
||||||
|
'CHECK_CONN_AFTER_CHANGE': True,
|
||||||
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
|
'SECURITY_MFA_AUTH': 0, # 0 不开启 1 全局开启 2 管理员开启
|
||||||
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY': True,
|
'SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY': True,
|
||||||
'SECURITY_COMMAND_EXECUTION': False,
|
'SECURITY_COMMAND_EXECUTION': False,
|
||||||
@@ -600,7 +604,6 @@ class Config(dict):
|
|||||||
|
|
||||||
# API 分页
|
# API 分页
|
||||||
'MAX_LIMIT_PER_PAGE': 10000,
|
'MAX_LIMIT_PER_PAGE': 10000,
|
||||||
'DEFAULT_PAGE_SIZE': None,
|
|
||||||
|
|
||||||
'LIMIT_SUPER_PRIV': False,
|
'LIMIT_SUPER_PRIV': False,
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ __all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG']
|
|||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
PROJECT_DIR = os.path.dirname(BASE_DIR)
|
||||||
VERSION = '2.0.0'
|
VERSION = 'v3.10.20'
|
||||||
CONFIG = ConfigManager.load_user_config()
|
CONFIG = ConfigManager.load_user_config()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from urllib.parse import urlparse, quote
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import MiddlewareNotUsed
|
from django.core.exceptions import MiddlewareNotUsed
|
||||||
from django.http.response import HttpResponseForbidden
|
from django.http.response import HttpResponseForbidden
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .utils import set_current_request
|
from .utils import set_current_request
|
||||||
@@ -137,3 +140,31 @@ class EndMiddleware:
|
|||||||
response = self.get_response(request)
|
response = self.get_response(request)
|
||||||
request._e_time_end = time.time()
|
request._e_time_end = time.time()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class SafeRedirectMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
if not (300 <= response.status_code < 400):
|
||||||
|
return response
|
||||||
|
if request.resolver_match and request.resolver_match.namespace.startswith('authentication'):
|
||||||
|
# 认证相关的路由跳过验证(core/auth/xxxx
|
||||||
|
return response
|
||||||
|
location = response.get('Location')
|
||||||
|
if not location:
|
||||||
|
return response
|
||||||
|
parsed = urlparse(location)
|
||||||
|
if parsed.scheme and parsed.netloc:
|
||||||
|
target_host = parsed.netloc
|
||||||
|
if target_host in [*settings.ALLOWED_HOSTS]:
|
||||||
|
return response
|
||||||
|
origin = f"{request.scheme}://{request.get_host()}"
|
||||||
|
target_origin = f"{parsed.scheme}://{target_host}"
|
||||||
|
if not target_origin.startswith(origin):
|
||||||
|
safe_redirect_url = '%s?%s' % (reverse('redirect-confirm'), f'next={quote(location)}')
|
||||||
|
return redirect(safe_redirect_url)
|
||||||
|
return response
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ current_year = datetime.datetime.now().year
|
|||||||
corporation = f'FIT2CLOUD 飞致云 © 2014-{current_year}'
|
corporation = f'FIT2CLOUD 飞致云 © 2014-{current_year}'
|
||||||
|
|
||||||
XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack')
|
XPACK_DIR = os.path.join(const.BASE_DIR, 'xpack')
|
||||||
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
|
XPACK_DISABLED = os.environ.get('XPACK_ENABLED') in ['0', 'false', 'False', 'no', 'No']
|
||||||
|
XPACK_ENABLED = False
|
||||||
|
if not XPACK_DISABLED:
|
||||||
|
XPACK_ENABLED = os.path.isdir(XPACK_DIR)
|
||||||
XPACK_TEMPLATES_DIR = []
|
XPACK_TEMPLATES_DIR = []
|
||||||
XPACK_CONTEXT_PROCESSOR = []
|
XPACK_CONTEXT_PROCESSOR = []
|
||||||
XPACK_LICENSE_IS_VALID = False
|
XPACK_LICENSE_IS_VALID = False
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ MIDDLEWARE = [
|
|||||||
'authentication.middleware.ThirdPartyLoginMiddleware',
|
'authentication.middleware.ThirdPartyLoginMiddleware',
|
||||||
'authentication.middleware.SessionCookieMiddleware',
|
'authentication.middleware.SessionCookieMiddleware',
|
||||||
'simple_history.middleware.HistoryRequestMiddleware',
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
|
'jumpserver.middleware.SafeRedirectMiddleware',
|
||||||
'jumpserver.middleware.EndMiddleware',
|
'jumpserver.middleware.EndMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -236,6 +237,7 @@ SESSION_COOKIE_NAME = '{}sessionid'.format(SESSION_COOKIE_NAME_PREFIX)
|
|||||||
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE
|
SESSION_COOKIE_AGE = CONFIG.SESSION_COOKIE_AGE
|
||||||
SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST
|
SESSION_SAVE_EVERY_REQUEST = CONFIG.SESSION_SAVE_EVERY_REQUEST
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = CONFIG.SESSION_EXPIRE_AT_BROWSER_CLOSE
|
||||||
|
VIEW_ASSET_ONLINE_SESSION_INFO = CONFIG.VIEW_ASSET_ONLINE_SESSION_INFO
|
||||||
SESSION_ENGINE = "common.sessions.{}".format(CONFIG.SESSION_ENGINE)
|
SESSION_ENGINE = "common.sessions.{}".format(CONFIG.SESSION_ENGINE)
|
||||||
|
|
||||||
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user