Merge pull request #4282 from jumpserver/dev_master

merge: Merge to master from branch dev
pull/4280/head
BaiJiangJie 2020-07-09 14:51:03 +08:00 committed by GitHub
commit f588093cd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 240 additions and 37 deletions

View File

@ -2,7 +2,7 @@
##### 使用版本 ##### 使用版本
[请提供你使用的Jumpserver版本 1.x.x 注: 0.3.x不再提供支持] [请提供你使用的JumpServer版本 如 2.0.1 注: 1.4及以下版本不再提供支持]
##### 问题复现步骤 ##### 问题复现步骤
1. [步骤1] 1. [步骤1]

44
.github/release-config.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name-template: 'v$RESOLVED_VERSION'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🌱 新功能 Features'
labels:
- 'feature'
- 'enhancement'
- 'feat'
- '新功能'
- title: '🚀 性能优化 Optimization'
labels:
- 'perf'
- 'opt'
- 'refactor'
- 'Optimization'
- '优化'
- title: '🐛 Bug修复 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- title: '🧰 其它 Maintenance'
labels:
- 'chore'
- 'docs'
exclude-labels:
- 'no'
- '无需处理'
- 'wontfix'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## 版本变化 Whats Changed
$CHANGES

46
.github/workflows/release-drafter.yml vendored Normal file
View File

@ -0,0 +1,46 @@
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
name: Create Release And Upload assets
jobs:
create-realese:
name: Create Release
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Get version
id: get_version
run: |
TAG=$(basename ${GITHUB_REF})
VERSION=${TAG/v/}
echo "::set-output name=TAG::$TAG"
echo "::set-output name=VERSION::$VERSION"
- name: Create Release
id: create_release
uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
config-name: release-config.yml
version: ${{ steps.get_version.outputs.TAG }}
tag: ${{ steps.get_version.outputs.TAG }}
build-and-release:
needs: create-realese
name: Build and Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build it and upload
uses: jumpserver/action-build-upload-assets@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-realese.outputs.upload_url }}

4
.gitignore vendored
View File

@ -35,4 +35,6 @@ docs/_build/
xpack xpack
logs/* logs/*
### Vagrant ### ### Vagrant ###
.vagrant/ .vagrant/
release/*
releashe

View File

@ -2,6 +2,7 @@
[![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/) [![Python3](https://img.shields.io/badge/python-3.6-green.svg?style=plastic)](https://www.python.org/)
[![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/) [![Django](https://img.shields.io/badge/django-2.2-brightgreen.svg?style=plastic)](https://www.djangoproject.com/)
[![Docker Pulls](https://img.shields.io/docker/pulls/jumpserver/jms_all.svg)](https://hub.docker.com/u/jumpserver)
JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。 JumpServer 是全球首款开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 规范的运维安全审计系统。

View File

@ -18,5 +18,5 @@ class GatheredUserViewSet(OrgModelViewSet):
permission_classes = [IsOrgAdmin] permission_classes = [IsOrgAdmin]
extra_filter_backends = [AssetRelatedByNodeFilterBackend] extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname'] filter_fields = ['asset', 'username', 'present', 'asset__ip', 'asset__hostname', 'asset_id']
search_fields = ['username', 'asset__ip', 'asset__hostname'] search_fields = ['username', 'asset__ip', 'asset__hostname']

View File

@ -28,7 +28,7 @@ class SystemUserViewSet(OrgBulkModelViewSet):
System user api set, for add,delete,update,list,retrieve resource System user api set, for add,delete,update,list,retrieve resource
""" """
model = SystemUser model = SystemUser
filter_fields = ("name", "username") filter_fields = ("name", "username", "protocol")
search_fields = filter_fields search_fields = filter_fields
serializer_class = serializers.SystemUserSerializer serializer_class = serializers.SystemUserSerializer
serializer_classes = { serializer_classes = {

View File

@ -27,6 +27,7 @@ class FTPLogViewSet(CreateModelMixin,
] ]
filter_fields = ['user', 'asset', 'system_user', 'filename'] filter_fields = ['user', 'asset', 'system_user', 'filename']
search_fields = filter_fields search_fields = filter_fields
ordering = ['-date_start']
class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet): class UserLoginLogViewSet(ListModelMixin, CommonGenericViewSet):

View File

@ -30,7 +30,7 @@ class JMSCSVRender(BaseRenderer):
@staticmethod @staticmethod
def _gen_table(data, fields): def _gen_table(data, fields):
data = data[:100] data = data[:10000]
yield ['*{}'.format(f.label) if f.required else f.label for f in fields] yield ['*{}'.format(f.label) if f.required else f.label for f in fields]
for item in data: for item in data:

View File

@ -10,3 +10,7 @@ class HttpResponseTemporaryRedirect(HttpResponse):
def __init__(self, redirect_to): def __init__(self, redirect_to):
HttpResponse.__init__(self) HttpResponse.__init__(self)
self['Location'] = iri_to_uri(redirect_to) self['Location'] = iri_to_uri(redirect_to)
def get_remote_addr(request):
return request.META.get("HTTP_X_FORWARDED_HOST") or request.META.get("REMOTE_ADDR")

View File

@ -260,7 +260,8 @@ class Config(dict):
'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False, 'WINDOWS_SKIP_ALL_MANUAL_PASSWORD': False,
'ORG_CHANGE_TO_URL': '', 'ORG_CHANGE_TO_URL': '',
'LANGUAGE_CODE': 'zh', 'LANGUAGE_CODE': 'zh',
'TIME_ZONE': 'Asia/Shanghai' 'TIME_ZONE': 'Asia/Shanghai',
'CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED': True
} }
def compatible_auth_openid_of_key(self): def compatible_auth_openid_of_key(self):

View File

@ -86,7 +86,11 @@ TASK_LOG_KEEP_DAYS = CONFIG.TASK_LOG_KEEP_DAYS
ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL ORG_CHANGE_TO_URL = CONFIG.ORG_CHANGE_TO_URL
WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD WINDOWS_SKIP_ALL_MANUAL_PASSWORD = CONFIG.WINDOWS_SKIP_ALL_MANUAL_PASSWORD
AUTH_EXPIRED_SECONDS = 60 * 5
# XPACK # XPACK
XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
LOGO_URLS = DYNAMIC.LOGO_URLS LOGO_URLS = DYNAMIC.LOGO_URLS
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED

View File

@ -1,5 +1,4 @@
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import redirect from django.shortcuts import redirect
from common.permissions import PermissionsMixin, IsValidUser from common.permissions import PermissionsMixin, IsValidUser
@ -12,17 +11,3 @@ class IndexView(PermissionsMixin, TemplateView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return redirect('/ui/') return redirect('/ui/')
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return self.handle_no_permission()
if request.user.is_common_user:
return redirect('assets:user-asset-list')
return super(IndexView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'app': _("Dashboard"),
})
return context

Binary file not shown.

View File

@ -3850,7 +3850,7 @@ msgstr "腾讯云"
#: xpack/plugins/cloud/serializers.py:56 #: xpack/plugins/cloud/serializers.py:56
msgid "History count" msgid "History count"
msgstr "用户数量" msgstr "执行次数"
#: xpack/plugins/cloud/serializers.py:57 #: xpack/plugins/cloud/serializers.py:57
msgid "Instance count" msgid "Instance count"

View File

@ -5,8 +5,8 @@ import re
import pyotp import pyotp
import base64 import base64
import logging import logging
import time
from django.http import Http404
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.cache import cache from django.core.cache import cache
@ -333,3 +333,15 @@ def get_source_choices():
if settings.AUTH_CAS: if settings.AUTH_CAS:
choices.append((User.SOURCE_CAS, choices_all[User.SOURCE_CAS])) choices.append((User.SOURCE_CAS, choices_all[User.SOURCE_CAS]))
return choices return choices
def is_auth_time_valid(session, key):
return True if session.get(key, 0) > time.time() else False
def is_auth_password_time_valid(session):
return is_auth_time_valid(session, 'auth_password_expired_at')
def is_auth_otp_time_valid(session):
return is_auth_time_valid(session, 'auth_opt_expired_at')

View File

@ -1,4 +1,5 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
import time
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -6,13 +7,17 @@ from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.contrib.auth import logout as auth_logout from django.contrib.auth import logout as auth_logout
from django.conf import settings from django.conf import settings
from django.http.response import HttpResponseForbidden
from common.utils import get_logger from authentication.mixins import AuthMixin
from users.models import User
from common.utils import get_logger, get_object_or_none
from common.permissions import IsValidUser from common.permissions import IsValidUser
from ... import forms from ... import forms
from .password import UserVerifyPasswordView from .password import UserVerifyPasswordView
from ...utils import ( from ...utils import (
generate_otp_uri, check_otp_code, get_user_or_pre_auth_user, generate_otp_uri, check_otp_code, get_user_or_pre_auth_user,
is_auth_password_time_valid, is_auth_otp_time_valid
) )
__all__ = [ __all__ = [
@ -46,11 +51,50 @@ class UserOtpEnableInstallAppView(TemplateView):
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class UserOtpEnableBindView(TemplateView, FormView): class UserOtpEnableBindView(AuthMixin, TemplateView, FormView):
template_name = 'users/user_otp_enable_bind.html' template_name = 'users/user_otp_enable_bind.html'
form_class = forms.UserCheckOtpCodeForm form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('authentication:user-otp-settings-success') success_url = reverse_lazy('authentication:user-otp-settings-success')
def get(self, request, *args, **kwargs):
if self._check_can_bind():
return super().get(request, *args, **kwargs)
return HttpResponseForbidden()
def post(self, request, *args, **kwargs):
if self._check_can_bind():
return super().post(request, *args, **kwargs)
return HttpResponseForbidden()
def _check_authenticated_user_can_bind(self):
user = self.request.user
session = self.request.session
if not user.mfa_enabled:
return is_auth_password_time_valid(session)
if not user.otp_secret_key:
return is_auth_password_time_valid(session)
return is_auth_otp_time_valid(session)
def _check_unauthenticated_user_can_bind(self):
session_user = None
if not self.request.session.is_empty():
user_id = self.request.session.get('user_id')
session_user = get_object_or_none(User, pk=user_id)
if session_user:
if all((is_auth_password_time_valid(self.request.session), session_user.mfa_enabled, not session_user.otp_secret_key)):
return True
return False
def _check_can_bind(self):
if self.request.user.is_authenticated:
return self._check_authenticated_user_can_bind()
else:
return self._check_unauthenticated_user_can_bind()
def form_valid(self, form): def form_valid(self, form):
otp_code = form.cleaned_data.get('otp_code') otp_code = form.cleaned_data.get('otp_code')
otp_secret_key = self.request.session.get('otp_secret_key', '') otp_secret_key = self.request.session.get('otp_secret_key', '')
@ -116,6 +160,7 @@ class UserOtpUpdateView(FormView):
valid = user.check_mfa(otp_code) valid = user.check_mfa(otp_code)
if valid: if valid:
self.request.session['auth_opt_expired_at'] = time.time() + settings.AUTH_EXPIRED_SECONDS
return super().form_valid(form) return super().form_valid(form)
else: else:
error = _('MFA code invalid, or ntp sync server time') error = _('MFA code invalid, or ntp sync server time')

View File

@ -1,8 +1,10 @@
# ~*~ coding: utf-8 ~*~ # ~*~ coding: utf-8 ~*~
import time
from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.edit import UpdateView, FormView from django.views.generic.edit import UpdateView, FormView
from django.contrib.auth import logout as auth_logout from django.contrib.auth import logout as auth_logout
@ -76,6 +78,7 @@ class UserVerifyPasswordView(FormView):
user.save() user.save()
self.request.session['user_id'] = str(user.id) self.request.session['user_id'] = str(user.id)
self.request.session['auth_password'] = 1 self.request.session['auth_password'] = 1
self.request.session['auth_password_expired_at'] = time.time() + settings.AUTH_EXPIRED_SECONDS
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def get_success_url(self): def get_success_url(self):

View File

@ -1,11 +0,0 @@
#!/bin/bash
#
version=$1
if [ -z "$version" ];then
echo "Usage: sh build version"
exit
fi
docker build -t jumpserver/jumpserver:$version .

28
utils/build.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
#
# 该build基于registry.fit2cloud.com/public/python:3
utils_dir=$(pwd)
project_dir=$(dirname "$utils_dir")
release_dir=${project_dir}/release
# 安装依赖包
command -v git || yum -y install git
# 打包
cd "${project_dir}" || exit 3
rm -rf "${release_dir:?}/*"
to_dir="${release_dir}/jumpserver"
mkdir -p "${to_dir}"
git archive --format tar HEAD | tar x -C "${to_dir}"
if [[ $(uname) == 'Darwin' ]];then
alias sedi="sed -i ''"
else
alias sedi='sed -i'
fi
# 修改版本号文件
if [[ -n ${VERSION} ]]; then
sedi "s@VERSION = .*@VERSION = \"${VERSION}\"@g" "${to_dir}/apps/jumpserver/const.py"
fi

12
utils/build_docker.sh Normal file
View File

@ -0,0 +1,12 @@
#!/bin/bash
#
utils_dir=$(dirname "$0")
project_dir=$(dirname "${utils_dir}")
version=$1
if [ -z "$version" ]; then
echo "Usage: sh build version"
exit
fi
cd "${project_dir}" && docker build -t "jumpserver/jumpserver:${version}" .

26
utils/example_api.py Normal file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env python
import requests
# 私有token页面上目前不允许创建只能后台生成见 https://docs.jumpserver.org/zh/master/dev/rest_api/
private_token = '10659d70a223235b8f76d45a3023eca1147488d7'
def do_request(url, data=None, method='get', params=None, org_id=''):
authorization = 'Token {}'.format(private_token)
headers = {'Authorization': authorization, 'Content-Type': 'application/json'}
if org_id:
headers['X-JMS-ORG'] = org_id
resp = requests.request(method=method, url=url, data=data, params=params, headers=headers)
return resp
def get_assets_list():
url = 'http://localhost:8080/api/v1/assets/assets/?limit=10'
resp = do_request(url)
print(resp.status_code)
print(resp.json())
print(resp)
if __name__ == '__main__':
get_assets_list()