Merge branch 'lina' of github.com:jumpserver/jumpserver into lina

pull/4019/head
ibuler 5 years ago
commit d6debde566

@ -10,7 +10,7 @@ RUN yum -y install epel-release && \
echo -e "[mysql]\nname=mysql\nbaseurl=https://mirrors.tuna.tsinghua.edu.cn/mysql/yum/mysql57-community-el6/\ngpgcheck=0\nenabled=1" > /etc/yum.repos.d/mysql.repo
RUN cd /tmp/requirements && yum -y install $(cat rpm_requirements.txt)
RUN cd /tmp/requirements && pip install --upgrade pip setuptools && pip install wheel && \
pip install -i https://mirrors.aliyun.com/pypi/simple/ -r requirements.txt || pip install -r requirements.txt
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt || pip install -r requirements.txt
RUN mkdir -p /root/.ssh/ && echo -e "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null" > /root/.ssh/config
COPY . /opt/jumpserver

@ -1,6 +1,5 @@
# ~*~ coding: utf-8 ~*~
#
import time
from treelib import Tree
from treelib.exceptions import NodeIDAbsentError
from collections import defaultdict
@ -77,21 +76,9 @@ class TreeService(Tree):
ancestor_ids.pop(0)
return ancestor_ids
def ancestors(self, nid, with_self=False, deep=False, with_assets=True):
# now = time.time()
# print("Start get ancestor_ids")
def ancestors(self, nid, with_self=False, deep=False):
ancestor_ids = self.ancestors_ids(nid, with_self=with_self)
# now2 = time.time()
# interval = (now2 - now) * 1000
# print("Start get ancestor_ids using: {}".format(interval))
ancestors = [self.get_node(i, deep=deep) for i in ancestor_ids]
# interval = (time.time() - now2) * 1000
# print("Get ancestors done: {}".format(interval))
if with_assets:
return ancestors
for n in ancestors:
n.data['assets'] = set()
n.data['all_assets'] = None
return ancestors
def get_node_full_tag(self, nid):
@ -116,6 +103,7 @@ class TreeService(Tree):
node = super().get_node(nid)
if deep:
node = self.copy_node(node)
node.data = {}
return node
def parent(self, nid, deep=False):

@ -136,8 +136,8 @@ def on_user_auth_success(sender, user, request, **kwargs):
@receiver(post_auth_failed)
def on_user_auth_failed(sender, username, request, reason, **kwargs):
def on_user_auth_failed(sender, username, request, reason='', **kwargs):
logger.debug('User login failed: {}'.format(username))
data = generate_data(username, request)
data.update({'reason': reason, 'status': False})
data.update({'reason': reason[:128], 'status': False})
write_login_log(**data)

@ -0,0 +1,4 @@
"""
使用下面的工程进行jumpserver oidc 认证
https://github.com/BaiJiangJie/jumpserver-django-oidc-rp
"""

@ -1,7 +0,0 @@
# -*- coding: utf-8 -*-
#
from .backends import *
from .middleware import *
from .utils import *
from .decorator import *

@ -1,82 +0,0 @@
# coding:utf-8
#
from django.contrib.auth import get_user_model
from django.conf import settings
from common.utils import get_logger
from .utils import new_client
from .models import OIDT_ACCESS_TOKEN
UserModel = get_user_model()
logger = get_logger(__file__)
client = new_client()
__all__ = [
'OpenIDAuthorizationCodeBackend', 'OpenIDAuthorizationPasswordBackend',
]
class BaseOpenIDAuthorizationBackend(object):
@staticmethod
def user_can_authenticate(user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_valid = getattr(user, 'is_valid', None)
return is_valid or is_valid is None
def get_user(self, user_id):
try:
user = UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
class OpenIDAuthorizationCodeBackend(BaseOpenIDAuthorizationBackend):
def authenticate(self, request, **kwargs):
logger.info('Authentication OpenID code backend')
code = kwargs.get('code')
redirect_uri = kwargs.get('redirect_uri')
if not code or not redirect_uri:
logger.info('Authenticate failed: No code or No redirect uri')
return None
try:
oidt_profile = client.update_or_create_from_code(
code=code, redirect_uri=redirect_uri
)
except Exception as e:
logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
return None
else:
# Check openid user single logout or not with access_token
request.session[OIDT_ACCESS_TOKEN] = oidt_profile.access_token
user = oidt_profile.user
logger.info('Authenticate success: user -> {}'.format(user))
return user if self.user_can_authenticate(user) else None
class OpenIDAuthorizationPasswordBackend(BaseOpenIDAuthorizationBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
logger.info('Authentication OpenID password backend')
if not username:
logger.info('Authenticate failed: Not username')
return None
try:
oidt_profile = client.update_or_create_from_password(
username=username, password=password
)
except Exception as e:
logger.error(e, exc_info=True)
logger.info('Authenticate failed: get oidt_profile: {}'.format(e))
return None
else:
user = oidt_profile.user
logger.info('Authenticate success: user -> {}'.format(user))
return user if self.user_can_authenticate(user) else None

@ -1,57 +0,0 @@
# coding: utf-8
#
import warnings
import contextlib
import requests
from urllib3.exceptions import InsecureRequestWarning
from django.conf import settings
__all__ = [
'ssl_verification',
]
old_merge_environment_settings = requests.Session.merge_environment_settings
@contextlib.contextmanager
def no_ssl_verification():
"""
https://stackoverflow.com/questions/15445981/
how-do-i-disable-the-security-certificate-check-in-python-requests
"""
opened_adapters = set()
def merge_environment_settings(self, url, proxies, stream, verify, cert):
# Verification happens only once per connection so we need to close
# all the opened adapters once we're done. Otherwise, the effects of
# verify=False persist beyond the end of this context manager.
opened_adapters.add(self.get_adapter(url))
_settings = old_merge_environment_settings(
self, url, proxies, stream, verify, cert
)
_settings['verify'] = False
return _settings
requests.Session.merge_environment_settings = merge_environment_settings
try:
with warnings.catch_warnings():
warnings.simplefilter('ignore', InsecureRequestWarning)
yield
finally:
requests.Session.merge_environment_settings = old_merge_environment_settings
for adapter in opened_adapters:
try:
adapter.close()
except:
pass
def ssl_verification(func):
def wrapper(*args, **kwargs):
if not settings.AUTH_OPENID_IGNORE_SSL_VERIFICATION:
return func(*args, **kwargs)
with no_ssl_verification():
return func(*args, **kwargs)
return wrapper

@ -1,41 +0,0 @@
# coding:utf-8
#
from django.conf import settings
from django.contrib.auth import logout
from django.utils.deprecation import MiddlewareMixin
from django.contrib.auth import BACKEND_SESSION_KEY
from common.utils import get_logger
from .utils import new_client
from .models import OIDT_ACCESS_TOKEN
BACKEND_OPENID_AUTH_CODE = 'OpenIDAuthorizationCodeBackend'
logger = get_logger(__file__)
__all__ = ['OpenIDAuthenticationMiddleware']
class OpenIDAuthenticationMiddleware(MiddlewareMixin):
"""
Check openid user single logout (with access_token)
"""
def process_request(self, request):
# Don't need openid auth if AUTH_OPENID is False
if not settings.AUTH_OPENID:
return
# Don't need openid auth if no shared session enabled
if not settings.AUTH_OPENID_SHARE_SESSION:
return
# Don't need check single logout if user not authenticated
if not request.user.is_authenticated:
return
elif not request.session[BACKEND_SESSION_KEY].endswith(
BACKEND_OPENID_AUTH_CODE):
return
# Check openid user single logout or not with access_token
try:
client = new_client()
client.get_userinfo(token=request.session.get(OIDT_ACCESS_TOKEN))
except Exception as e:
logout(request)
logger.error(e)

@ -1,185 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db import transaction
from django.contrib.auth import get_user_model
from keycloak.realm import KeycloakRealm
from keycloak.keycloak_openid import KeycloakOpenID
from users.utils import construct_user_email
from .signals import post_create_or_update_openid_user
from .decorator import ssl_verification
OIDT_ACCESS_TOKEN = 'oidt_access_token'
class Nonce(object):
"""
The openid-login is stored in cache as a temporary object, recording the
user's redirect_uri and next_pat
"""
def __init__(self, redirect_uri, next_path):
import uuid
self.state = uuid.uuid4()
self.redirect_uri = redirect_uri
self.next_path = next_path
class OpenIDTokenProfile(object):
def __init__(self, user, access_token, refresh_token):
"""
:param user: User object
:param access_token:
:param refresh_token:
"""
self.user = user
self.access_token = access_token
self.refresh_token = refresh_token
def __str__(self):
return "{}'s OpenID token profile".format(self.user.username)
class Client(object):
def __init__(self, server_url, realm_name, client_id, client_secret):
self.server_url = server_url
self.realm_name = realm_name
self.client_id = client_id
self.client_secret = client_secret
self._openid_client = None
self._realm = None
self._openid_connect_client = None
@property
def realm(self):
if self._realm is None:
self._realm = KeycloakRealm(
server_url=self.server_url,
realm_name=self.realm_name,
headers={}
)
return self._realm
@property
def openid_connect_client(self):
"""
:rtype: keycloak.openid_connect.KeycloakOpenidConnect
"""
if self._openid_connect_client is None:
self._openid_connect_client = self.realm.open_id_connect(
client_id=self.client_id,
client_secret=self.client_secret
)
return self._openid_connect_client
@property
def openid_client(self):
"""
:rtype: keycloak.keycloak_openid.KeycloakOpenID
"""
if self._openid_client is None:
self._openid_client = KeycloakOpenID(
server_url='%sauth/' % self.server_url,
realm_name=self.realm_name,
client_id=self.client_id,
client_secret_key=self.client_secret,
)
return self._openid_client
@ssl_verification
def get_url(self, name):
return self.openid_connect_client.get_url(name=name)
def get_url_end_session_endpoint(self):
return self.get_url(name='end_session_endpoint')
@ssl_verification
def get_authorization_url(self, redirect_uri, scope, state):
url = self.openid_connect_client.authorization_url(
redirect_uri=redirect_uri, scope=scope, state=state
)
return url
@ssl_verification
def get_userinfo(self, token):
user_info = self.openid_connect_client.userinfo(token=token)
return user_info
@ssl_verification
def authorization_code(self, code, redirect_uri):
token_response = self.openid_connect_client.authorization_code(
code=code, redirect_uri=redirect_uri
)
return token_response
@ssl_verification
def authorization_password(self, username, password):
token_response = self.openid_client.token(
username=username, password=password
)
return token_response
def update_or_create_from_code(self, code, redirect_uri):
"""
Update or create an user based on an authentication code.
Response as specified in:
https://tools.ietf.org/html/rfc6749#section-4.1.4
:param str code: authentication code
:param str redirect_uri:
:rtype: OpenIDTokenProfile
"""
token_response = self.authorization_code(code, redirect_uri)
return self._update_or_create(token_response=token_response)
def update_or_create_from_password(self, username, password):
"""
Update or create an user based on an authentication username and password.
:param str username: authentication username
:param str password: authentication password
:return: OpenIDTokenProfile
"""
token_response = self.authorization_password(username, password)
return self._update_or_create(token_response=token_response)
def _update_or_create(self, token_response):
"""
Update or create an user based on a token response.
`token_response` contains the items returned by the OpenIDConnect Token API
end-point:
- id_token
- access_token
- expires_in
- refresh_token
- refresh_expires_in
:param dict token_response:
:rtype: OpenIDTokenProfile
"""
userinfo = self.get_userinfo(token=token_response['access_token'])
with transaction.atomic():
name = userinfo.get('name', '')
username = userinfo.get('preferred_username', '')
email = userinfo.get('email', '')
email = construct_user_email(username, email)
user, created = get_user_model().objects.update_or_create(
username=username,
defaults={
'name': name, 'email': email,
'first_name': userinfo.get('given_name', ''),
'last_name': userinfo.get('family_name', ''),
}
)
oidt_profile = OpenIDTokenProfile(
user=user,
access_token=token_response['access_token'],
refresh_token=token_response['refresh_token'],
)
if user:
post_create_or_update_openid_user.send(
sender=user.__class__, user=user, created=created
)
return oidt_profile
def __str__(self):
return self.client_id

@ -1,5 +0,0 @@
from django.dispatch import Signal
post_create_or_update_openid_user = Signal(providing_args=('user',))
post_openid_login_success = Signal(providing_args=('user', 'request'))

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.OpenIDLoginView.as_view(), name='openid-login'),
path('login/complete/', views.OpenIDLoginCompleteView.as_view(),
name='openid-login-complete'),
]

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.conf import settings
from .models import Client
__all__ = ['new_client']
def new_client():
"""
:return: authentication.models.Client
"""
return Client(
server_url=settings.AUTH_OPENID_SERVER_URL,
realm_name=settings.AUTH_OPENID_REALM_NAME,
client_id=settings.AUTH_OPENID_CLIENT_ID,
client_secret=settings.AUTH_OPENID_CLIENT_SECRET
)

@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
#
import logging
from django.conf import settings
from django.core.cache import cache
from django.views.generic.base import RedirectView
from django.contrib.auth import authenticate, login
from django.http.response import (
HttpResponseBadRequest,
HttpResponseServerError,
HttpResponseRedirect
)
from .utils import new_client
from .models import Nonce
from .signals import post_openid_login_success
logger = logging.getLogger(__name__)
client = new_client()
__all__ = ['OpenIDLoginView', 'OpenIDLoginCompleteView']
class OpenIDLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
redirect_uri = settings.BASE_SITE_URL + \
str(settings.AUTH_OPENID_LOGIN_COMPLETE_URL)
nonce = Nonce(
redirect_uri=redirect_uri,
next_path=self.request.GET.get('next')
)
cache.set(str(nonce.state), nonce, 24*3600)
self.request.session['openid_state'] = str(nonce.state)
authorization_url = client.get_authorization_url(
redirect_uri=nonce.redirect_uri,
scope='code',
state=str(nonce.state)
)
return authorization_url
class OpenIDLoginCompleteView(RedirectView):
def get(self, request, *args, **kwargs):
if 'error' in request.GET:
return HttpResponseServerError(self.request.GET['error'])
if 'code' not in self.request.GET and 'state' not in self.request.GET:
return HttpResponseBadRequest(content='Code or State is empty')
if self.request.GET['state'] != self.request.session['openid_state']:
return HttpResponseBadRequest(content='State invalid')
nonce = cache.get(self.request.GET['state'])
if not nonce:
return HttpResponseBadRequest(content='State failure')
user = authenticate(
request=self.request,
code=self.request.GET['code'],
redirect_uri=nonce.redirect_uri
)
cache.delete(str(nonce.state))
if not user:
return HttpResponseBadRequest(content='Authenticate user failed')
login(self.request, user)
post_openid_login_success.send(
sender=self.__class__, user=user, request=self.request
)
return HttpResponseRedirect(nonce.next_path or '/')

@ -93,6 +93,9 @@ class AuthFailedError(Exception):
'msg': self.msg,
}
def __str__(self):
return str(self.msg)
class CredentialError(AuthFailedNeedLogMixin, AuthFailedNeedBlockMixin, AuthFailedError):
def __init__(self, error, username, ip, request):
@ -168,7 +171,7 @@ class MFARequiredError(NeedMoreInfoError):
'error': self.error,
'msg': self.msg,
'data': {
'choices': ['otp'],
'choices': ['code'],
'url': reverse('api-auth:mfa-challenge')
}
}

@ -62,8 +62,7 @@ class AuthMixin:
password = request.POST.get('password', '')
public_key = request.POST.get('public_key', '')
user, error = check_user_valid(
username=username, password=password,
public_key=public_key
request=request, username=username, password=password, public_key=public_key
)
ip = self.get_request_ip()
if not user:

@ -1,54 +1,15 @@
from django.http.request import QueryDict
from django.conf import settings
from django.dispatch import receiver
from django.contrib.auth.signals import user_logged_out
from django_auth_ldap.backend import populate_user
from users.models import User
from .backends.openid import new_client
from .backends.openid.signals import (
post_create_or_update_openid_user, post_openid_login_success
)
from .signals import post_auth_success
from jms_oidc_rp.signals import openid_user_login_failed, openid_user_login_success
from .signals import post_auth_success, post_auth_failed
@receiver(user_logged_out)
def on_user_logged_out(sender, request, user, **kwargs):
if not settings.AUTH_OPENID:
return
if not settings.AUTH_OPENID_SHARE_SESSION:
return
query = QueryDict('', mutable=True)
query.update({
'redirect_uri': settings.BASE_SITE_URL
})
client = new_client()
openid_logout_url = "%s?%s" % (
client.get_url_end_session_endpoint(),
query.urlencode()
)
request.COOKIES['next'] = openid_logout_url
@receiver(post_create_or_update_openid_user)
def on_post_create_or_update_openid_user(sender, user=None, created=True, **kwargs):
if created and user and user.username != 'admin':
user.source = user.SOURCE_OPENID
user.save()
@receiver(post_openid_login_success)
def on_openid_login_success(sender, user=None, request=None, **kwargs):
post_auth_success.send(sender=sender, user=user, request=request)
@receiver(populate_user)
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user and user.username not in ['admin']:
exists = User.objects.filter(username=user.username).exists()
if not exists:
user.source = user.SOURCE_LDAP
user.save()
@receiver(openid_user_login_success)
def on_oidc_user_login_success(sender, request, user, **kwargs):
post_auth_success.send(sender, user=user, request=request)
@receiver(openid_user_login_failed)
def on_oidc_user_login_failed(sender, username, request, reason, **kwargs):
post_auth_failed.send(sender, username=username, request=request, reason=reason)

@ -56,9 +56,9 @@
<div class="hr-line-dashed"></div>
<p class="text-muted text-center">{% trans "More login options" %}</p>
<div>
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid:openid-login' %}'">
<button type="button" class="btn btn-default btn-sm btn-block" onclick="location.href='{% url 'authentication:openid:login' %}'">
<i class="fa fa-openid"></i>
{% trans 'Keycloak' %}
{% trans 'OpenID' %}
</button>
</div>
{% endif %}

@ -16,6 +16,6 @@ urlpatterns = [
path('logout/', views.UserLogoutView.as_view(), name='logout'),
# openid
path('openid/', include(('authentication.backends.openid.urls', 'authentication'), namespace='openid')),
path('cas/', include(('authentication.backends.cas.urls', 'authentication'), namespace='cas')),
path('openid/', include(('jms_oidc_rp.urls', 'authentication'), namespace='openid')),
]

@ -58,7 +58,7 @@ class UserLoginView(mixins.AuthMixin, FormView):
if self.request.GET.get("admin", 0):
return None
if settings.AUTH_OPENID:
redirect_url = reverse("authentication:openid:openid-login")
redirect_url = reverse(settings.AUTH_OPENID_AUTH_LOGIN_URL_NAME)
elif settings.AUTH_CAS:
redirect_url = reverse(settings.CAS_LOGIN_URL_NAME)
@ -133,7 +133,7 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
user = self.check_user_auth_if_need()
self.check_user_mfa_if_need(user)
self.check_user_login_confirm_if_need(user)
except errors.CredentialError:
except (errors.CredentialError, errors.SessionEmptyError):
return self.format_redirect_url(self.login_url)
except errors.MFARequiredError:
return self.format_redirect_url(self.login_otp_url)
@ -185,18 +185,18 @@ class UserLogoutView(TemplateView):
@staticmethod
def get_backend_logout_url():
if settings.AUTH_OPENID:
return settings.AUTH_OPENID_AUTH_LOGOUT_URL_NAME
# if settings.AUTH_CAS:
# return settings.CAS_LOGOUT_URL_NAME
return None
def get(self, request, *args, **kwargs):
auth_logout(request)
backend_logout_url = self.get_backend_logout_url()
if backend_logout_url:
return redirect(backend_logout_url)
next_uri = request.COOKIES.get("next")
if next_uri:
return redirect(next_uri)
auth_logout(request)
response = super().get(request, *args, **kwargs)
return response

@ -6,6 +6,9 @@ from django.views.generic.edit import FormView
from .. import forms, errors, mixins
from .utils import redirect_to_guard_view
from common.utils import get_logger
logger = get_logger(__name__)
__all__ = ['UserLoginOtpView']
@ -22,4 +25,7 @@ class UserLoginOtpView(mixins.AuthMixin, FormView):
except errors.MFAFailedError as e:
form.add_error('otp_code', e.msg)
return super().form_invalid(form)
except Exception as e:
logger.error(e)
return redirect_to_guard_view()

@ -0,0 +1,295 @@
from django.core.cache import cache
from django.utils import timezone
from django.utils.timesince import timesince
from django.db.models import Count, Max
from django.http.response import JsonResponse
from rest_framework.views import APIView
from collections import Counter
from users.models import User
from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
from common.permissions import IsOrgAdmin
from common.utils import lazyproperty
__all__ = ['IndexApi']
class MonthLoginMetricMixin:
@lazyproperty
def session_month(self):
month_ago = timezone.now() - timezone.timedelta(days=30)
session_month = Session.objects.filter(date_start__gt=month_ago)
return session_month
@lazyproperty
def session_month_dates(self):
dates = self.session_month.dates('date_start', 'day')
return dates
def get_month_metrics_date(self):
month_metrics_date = [d.strftime('%m-%d') for d in self.session_month_dates] or ['0']
return month_metrics_date
@staticmethod
def get_cache_key(date, tp):
date_str = date.strftime("%Y%m%d")
key = "SESSION_MONTH_{}_{}_{}".format(current_org.id, tp, date_str)
return key
def __get_data_from_cache(self, date, tp):
if date == timezone.now().date():
return None
cache_key = self.get_cache_key(date, tp)
count = cache.get(cache_key)
return count
def __set_data_to_cache(self, date, tp, count):
cache_key = self.get_cache_key(date, tp)
cache.set(cache_key, count, 3600*24*7)
@staticmethod
def get_date_start_2_end(d):
time_min = timezone.datetime.min.time()
time_max = timezone.datetime.max.time()
tz = timezone.get_current_timezone()
ds = timezone.datetime.combine(d, time_min).replace(tzinfo=tz)
de = timezone.datetime.combine(d, time_max).replace(tzinfo=tz)
return ds, de
def get_date_login_count(self, date):
tp = "LOGIN"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = Session.objects.filter(date_start__range=(ds, de)).count()
self.__set_data_to_cache(date, tp, count)
return count
def get_month_metrics_total_count_login(self):
data = []
for d in self.session_month_dates:
count = self.get_date_login_count(d)
data.append(count)
if len(data) == 0:
data = [0]
return data
def get_date_user_count(self, date):
tp = "USER"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('user', flat=True)))
self.__set_data_to_cache(date, tp, count)
return count
def get_month_metrics_total_count_active_users(self):
data = []
for d in self.session_month_dates:
count = self.get_date_user_count(d)
data.append(count)
return data
def get_date_asset_count(self, date):
tp = "ASSET"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = len(set(Session.objects.filter(date_start__range=(ds, de)).values_list('asset', flat=True)))
self.__set_data_to_cache(date, tp, count)
return count
def get_month_metrics_total_count_active_assets(self):
data = []
for d in self.session_month_dates:
count = self.get_date_asset_count(d)
data.append(count)
return data
@lazyproperty
def month_total_count_active_users(self):
count = len(set(self.session_month.values_list('user', flat=True)))
return count
@lazyproperty
def month_total_count_inactive_users(self):
total = current_org.get_org_members().count()
active = self.month_total_count_active_users
count = total - active
if count < 0:
count = 0
return count
@lazyproperty
def month_total_count_disabled_users(self):
return current_org.get_org_members().filter(is_active=False).count()
@lazyproperty
def month_total_count_active_assets(self):
return len(set(self.session_month.values_list('asset', flat=True)))
@lazyproperty
def month_total_count_inactive_assets(self):
total = Asset.objects.all().count()
active = self.month_total_count_active_assets
count = total - active
if count < 0:
count = 0
return count
@lazyproperty
def month_total_count_disabled_assets(self):
return Asset.objects.filter(is_active=False).count()
class WeekSessionMetricMixin:
session_week = None
@lazyproperty
def session_week(self):
week_ago = timezone.now() - timezone.timedelta(weeks=1)
session_week = Session.objects.filter(date_start__gt=week_ago)
return session_week
def get_week_login_times_top5_users(self):
users = self.session_week.values_list('user', flat=True)
users = [
{'user': user, 'total': total}
for user, total in Counter(users).most_common(5)
]
return users
def get_week_total_count_login_users(self):
return len(set(self.session_week.values_list('user', flat=True)))
def get_week_total_count_login_times(self):
return self.session_week.count()
def get_week_login_times_top10_assets(self):
assets = self.session_week.values("asset")\
.annotate(total=Count("asset"))\
.annotate(last=Max("date_start")).order_by("-total")[:10]
for asset in assets:
asset['last'] = str(asset['last'])
return list(assets)
def get_week_login_times_top10_users(self):
users = self.session_week.values("user") \
.annotate(total=Count("user")) \
.annotate(last=Max("date_start")).order_by("-total")[:10]
for user in users:
user['last'] = str(user['last'])
return list(users)
def get_week_login_record_top10_sessions(self):
sessions = self.session_week.order_by('-date_start')[:10]
for session in sessions:
session.avatar_url = User.get_avatar_url("")
sessions = [
{
'user': session.user,
'asset': session.asset,
'is_finished': session.is_finished,
'date_start': str(session.date_start),
'timesince': timesince(session.date_start)
}
for session in sessions
]
return sessions
class TotalCountMixin:
@staticmethod
def get_total_count_users():
return current_org.get_org_members().count()
@staticmethod
def get_total_count_assets():
return Asset.objects.all().count()
@staticmethod
def get_total_count_online_users():
count = len(set(Session.objects.filter(is_finished=False).values_list('user', flat=True)))
return count
@staticmethod
def get_total_count_online_sessions():
return Session.objects.filter(is_finished=False).count()
class IndexApi(TotalCountMixin, WeekSessionMetricMixin, MonthLoginMetricMixin, APIView):
permission_classes = (IsOrgAdmin,)
http_method_names = ['get']
def get(self, request, *args, **kwargs):
data = {}
query_params = self.request.query_params
_all = query_params.get('all')
if _all or query_params.get('total_count'):
data.update({
'total_count_assets': self.get_total_count_assets(),
'total_count_users': self.get_total_count_users(),
'total_count_online_users': self.get_total_count_online_users(),
'total_count_online_sessions': self.get_total_count_online_sessions(),
})
if _all or query_params.get('month_metrics'):
data.update({
'month_metrics_date': self.get_month_metrics_date(),
'month_metrics_total_count_login': self.get_month_metrics_total_count_login(),
'month_metrics_total_count_active_users': self.get_month_metrics_total_count_active_users(),
'month_metrics_total_count_active_assets': self.get_month_metrics_total_count_active_assets(),
})
if _all or query_params.get('month_total_count_users'):
data.update({
'month_total_count_active_users': self.month_total_count_active_users,
'month_total_count_inactive_users': self.month_total_count_inactive_users,
'month_total_count_disabled_users': self.month_total_count_disabled_users,
})
if _all or query_params.get('month_total_count_assets'):
data.update({
'month_total_count_active_assets': self.month_total_count_active_assets,
'month_total_count_inactive_assets': self.month_total_count_inactive_assets,
'month_total_count_disabled_assets': self.month_total_count_disabled_assets,
})
if _all or query_params.get('week_total_count'):
data.update({
'week_total_count_login_users': self.get_week_total_count_login_users(),
'week_total_count_login_times': self.get_week_total_count_login_times(),
})
if _all or query_params.get('week_login_times_top5_users'):
data.update({
'week_login_times_top5_users': self.get_week_login_times_top5_users(),
})
if _all or query_params.get('week_login_times_top10_assets'):
data.update({
'week_login_times_top10_assets': self.get_week_login_times_top10_assets(),
})
if _all or query_params.get('week_login_times_top10_users'):
data.update({
'week_login_times_top10_users': self.get_week_login_times_top10_users(),
})
if _all or query_params.get('week_login_record_top10_sessions'):
data.update({
'week_login_record_top10_sessions': self.get_week_login_record_top10_sessions()
})
return JsonResponse(data, status=200)

@ -8,6 +8,7 @@
3. 程序需要, 用户需要更改的写到本config中
"""
import os
import re
import sys
import types
import errno
@ -15,9 +16,12 @@ import json
import yaml
from importlib import import_module
from django.urls import reverse_lazy
from urllib.parse import urljoin, urlparse
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(BASE_DIR)
XPACK_DIR = os.path.join(BASE_DIR, 'xpack')
HAS_XPACK = os.path.isdir(XPACK_DIR)
def import_string(dotted_path):
@ -36,6 +40,38 @@ def import_string(dotted_path):
) from err
def is_absolute_uri(uri):
""" 判断一个uri是否是绝对地址 """
if not isinstance(uri, str):
return False
result = re.match(r'^http[s]?://.*', uri)
if result is None:
return False
return True
def build_absolute_uri(base, uri):
""" 构建绝对uri地址 """
if uri is None:
return base
if isinstance(uri, int):
uri = str(uri)
if not isinstance(uri, str):
return base
if is_absolute_uri(uri):
return uri
parsed_base = urlparse(base)
url = "{}://{}".format(parsed_base.scheme, parsed_base.netloc)
path = '{}/{}/'.format(parsed_base.path.strip('/'), uri.strip('/'))
return urljoin(url, path)
class DoesNotExist(Exception):
pass
@ -134,14 +170,32 @@ class Config(dict):
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
# OpenID 配置参数
# OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8)
'AUTH_OPENID': False,
'AUTH_OPENID_CLIENT_ID': 'client-id',
'AUTH_OPENID_CLIENT_SECRET': 'client-secret',
'AUTH_OPENID_SHARE_SESSION': True,
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
# OpenID 新配置参数 (version >= 1.5.8)
'AUTH_OPENID_PROVIDER_ENDPOINT': 'https://op-example.com/',
'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT': 'https://op-example.com/authorize',
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT': 'https://op-example.com/token',
'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT': 'https://op-example.com/jwks',
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT': 'https://op-example.com/userinfo',
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT': 'https://op-example.com/logout',
'AUTH_OPENID_PROVIDER_SIGNATURE_ALG': 'HS256',
'AUTH_OPENID_PROVIDER_SIGNATURE_KEY': None,
'AUTH_OPENID_SCOPES': 'openid profile email',
'AUTH_OPENID_ID_TOKEN_MAX_AGE': 60,
'AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS': True,
'AUTH_OPENID_USE_STATE': True,
'AUTH_OPENID_USE_NONCE': True,
'AUTH_OPENID_ALWAYS_UPDATE_USER': True,
# OpenID 旧配置参数 (version <= 1.5.8 (discarded))
'BASE_SITE_URL': 'http://localhost:8080',
'AUTH_OPENID_SERVER_URL': 'http://openid',
'AUTH_OPENID_REALM_NAME': 'jumpserver',
'AUTH_OPENID_CLIENT_ID': 'jumpserver',
'AUTH_OPENID_CLIENT_SECRET': '',
'AUTH_OPENID_IGNORE_SSL_VERIFICATION': True,
'AUTH_OPENID_SHARE_SESSION': True,
'AUTH_OPENID_REALM_NAME': None,
'AUTH_RADIUS': False,
'RADIUS_SERVER': 'localhost',
@ -190,7 +244,7 @@ class Config(dict):
'TASK_LOG_KEEP_DAYS': 10,
'ASSETS_PERM_CACHE_TIME': 3600 * 24,
'SECURITY_MFA_VERIFY_TTL': 3600,
'ASSETS_PERM_CACHE_ENABLE': False,
'ASSETS_PERM_CACHE_ENABLE': HAS_XPACK,
'SYSLOG_ADDR': '', # '192.168.0.1:514'
'SYSLOG_FACILITY': 'user',
'SYSLOG_SOCKTYPE': 2,
@ -207,6 +261,88 @@ class Config(dict):
'TIME_ZONE': 'Asia/Shanghai'
}
def compatible_auth_openid_of_key(self):
"""
兼容OpenID旧配置 ( version <= 1.5.8)
因为旧配置只支持OpenID协议的Keycloak实现,
所以只需要根据旧配置和Keycloak的Endpoint说明文档
构造出新配置中标准OpenID协议中所需的Endpoint即可
(Keycloak说明文档参考: https://www.keycloak.org/docs/latest/securing_apps/)
"""
if not self.AUTH_OPENID:
return
realm_name = self.AUTH_OPENID_REALM_NAME
if realm_name is None:
return
compatible_keycloak_config = [
(
'AUTH_OPENID_PROVIDER_ENDPOINT',
self.AUTH_OPENID_SERVER_URL
),
(
'AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT',
'/realms/{}/protocol/openid-connect/auth'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT',
'/realms/{}/protocol/openid-connect/token'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_JWKS_ENDPOINT',
'/realms/{}/protocol/openid-connect/certs'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT',
'/realms/{}/protocol/openid-connect/userinfo'.format(realm_name)
),
(
'AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT',
'/realms/{}/protocol/openid-connect/logout'.format(realm_name)
)
]
for key, value in compatible_keycloak_config:
self[key] = value
def compatible_auth_openid_of_value(self):
"""
兼容值的绝对路径相对路径
(key AUTH_OPENID_PROVIDER_*_ENDPOINT 的配置)
"""
if not self.AUTH_OPENID:
return
base = self.AUTH_OPENID_PROVIDER_ENDPOINT
config = list(self.items())
for key, value in config:
result = re.match(r'^AUTH_OPENID_PROVIDER_.*_ENDPOINT$', key)
if result is None:
continue
if value is None:
# None 在 url 中有特殊含义 (比如对于: end_session_endpoint)
continue
value = build_absolute_uri(base, value)
self[key] = value
def compatible(self):
"""
对配置做兼容处理
1. `key`的兼容 (例如版本升级)
2. `value`做兼容 (例如Truetrue1 => True)
处理顺序要保持先对key做处理, 再对value做处理,
因为处理value的时候只根据最新版本支持的key进行
"""
parts = ['key', 'value']
targets = ['auth_openid']
for part in parts:
for target in targets:
method_name = 'compatible_{}_of_{}'.format(target, part)
method = getattr(self, method_name, None)
if method is not None:
method()
def convert_type(self, k, v):
default_value = self.defaults.get(k)
if default_value is None:
@ -283,9 +419,6 @@ class DynamicConfig:
return lambda: self.get(item)
def LOGIN_URL(self):
auth_openid = self.get('AUTH_OPENID')
if auth_openid:
return reverse_lazy("authentication:openid:openid-login")
return self.get('LOGIN_URL')
def AUTHENTICATION_BACKENDS(self):
@ -298,8 +431,8 @@ class DynamicConfig:
if self.static_config.get('AUTH_CAS'):
backends.insert(0, 'authentication.backends.cas.CASBackend')
if self.static_config.get('AUTH_OPENID'):
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationPasswordBackend')
backends.insert(0, 'authentication.backends.openid.backends.OpenIDAuthorizationCodeBackend')
backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthPasswordBackend')
backends.insert(0, 'jms_oidc_rp.backends.OIDCAuthCodeBackend')
if self.static_config.get('AUTH_RADIUS'):
backends.insert(0, 'authentication.backends.radius.RadiusBackend')
return backends
@ -480,9 +613,9 @@ class ConfigManager:
manager = cls(root_path=root_path)
if manager.load_from_object():
return manager.config
config = manager.config
elif manager.load_from_yml():
return manager.config
config = manager.config
else:
msg = """
@ -492,6 +625,10 @@ class ConfigManager:
"""
raise ImportError(msg)
# 对config进行兼容处理
config.compatible()
return config
@classmethod
def get_dynamic_config(cls, config):
return DynamicConfig(config)

@ -2,7 +2,6 @@
#
import os
import ldap
from django.urls import reverse_lazy
from ..const import CONFIG, DYNAMIC, PROJECT_DIR
@ -43,19 +42,35 @@ AUTH_LDAP_SYNC_INTERVAL = CONFIG.AUTH_LDAP_SYNC_INTERVAL
AUTH_LDAP_SYNC_CRONTAB = CONFIG.AUTH_LDAP_SYNC_CRONTAB
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
# openid
# Auth OpenID settings
BASE_SITE_URL = CONFIG.BASE_SITE_URL
# ==============================================================================
# 认证 OpenID 配置参数
# 参考: https://django-oidc-rp.readthedocs.io/en/stable/settings.html
# ==============================================================================
AUTH_OPENID = CONFIG.AUTH_OPENID
AUTH_OPENID_SERVER_URL = CONFIG.AUTH_OPENID_SERVER_URL
AUTH_OPENID_REALM_NAME = CONFIG.AUTH_OPENID_REALM_NAME
AUTH_OPENID_CLIENT_ID = CONFIG.AUTH_OPENID_CLIENT_ID
AUTH_OPENID_CLIENT_SECRET = CONFIG.AUTH_OPENID_CLIENT_SECRET
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_LOGIN_URL = reverse_lazy("authentication:openid:openid-login")
AUTH_OPENID_LOGIN_COMPLETE_URL = reverse_lazy("authentication:openid:openid-login-complete")
AUTH_OPENID_PROVIDER_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_ENDPOINT
AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT
AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT
AUTH_OPENID_PROVIDER_JWKS_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_JWKS_ENDPOINT
AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT
AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT = CONFIG.AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT
AUTH_OPENID_PROVIDER_SIGNATURE_ALG = CONFIG.AUTH_OPENID_PROVIDER_SIGNATURE_ALG
AUTH_OPENID_PROVIDER_SIGNATURE_KEY = CONFIG.AUTH_OPENID_PROVIDER_SIGNATURE_KEY
AUTH_OPENID_SCOPES = CONFIG.AUTH_OPENID_SCOPES
AUTH_OPENID_ID_TOKEN_MAX_AGE = CONFIG.AUTH_OPENID_ID_TOKEN_MAX_AGE
AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS = CONFIG.AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS
AUTH_OPENID_USE_STATE = CONFIG.AUTH_OPENID_USE_STATE
AUTH_OPENID_USE_NONCE = CONFIG.AUTH_OPENID_USE_NONCE
AUTH_OPENID_SHARE_SESSION = CONFIG.AUTH_OPENID_SHARE_SESSION
AUTH_OPENID_IGNORE_SSL_VERIFICATION = CONFIG.AUTH_OPENID_IGNORE_SSL_VERIFICATION
AUTH_OPENID_ALWAYS_UPDATE_USER = CONFIG.AUTH_OPENID_ALWAYS_UPDATE_USER
AUTH_OPENID_AUTH_LOGIN_URL_NAME = 'authentication:openid:login'
AUTH_OPENID_AUTH_LOGIN_CALLBACK_URL_NAME = 'authentication:openid:login-callback'
AUTH_OPENID_AUTH_LOGOUT_URL_NAME = 'authentication:openid:logout'
# ==============================================================================
# Radius Auth
AUTH_RADIUS = CONFIG.AUTH_RADIUS

@ -48,6 +48,7 @@ INSTALLED_APPS = [
'authentication.apps.AuthenticationConfig', # authentication
'applications.apps.ApplicationsConfig',
'tickets.apps.TicketsConfig',
'jms_oidc_rp',
'rest_framework',
'rest_framework_swagger',
'drf_yasg',
@ -75,7 +76,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'authentication.backends.openid.middleware.OpenIDAuthenticationMiddleware',
'jms_oidc_rp.middleware.OIDCRefreshIDTokenMiddleware',
'django_cas_ng.middleware.CASMiddleware',
'jumpserver.middleware.TimezoneMiddleware',
'jumpserver.middleware.DemoMiddleware',
@ -103,6 +104,7 @@ TEMPLATES = [
'django.template.context_processors.media',
'jumpserver.context_processor.jumpserver_processor',
'orgs.context_processor.org_processor',
'jms_oidc_rp.context_processors.oidc',
],
},
},

@ -7,9 +7,10 @@ from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.views.i18n import JavaScriptCatalog
from . import views
from . import views, api
api_v1 = [
path('index/', api.IndexApi.as_view()),
path('users/', include('users.urls.api_urls', namespace='api-users')),
path('assets/', include('assets.urls.api_urls', namespace='api-assets')),
path('perms/', include('perms.urls.api_urls', namespace='api-perms')),

@ -18,4 +18,3 @@ def get_current_request():
current_request = LocalProxy(partial(_find, 'current_request'))

@ -1,224 +1,12 @@
from django.core.cache import cache
from django.views.generic import TemplateView
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.db.models import Count, Max
from django.shortcuts import redirect
from users.models import User
from assets.models import Asset
from terminal.models import Session
from orgs.utils import current_org
from common.permissions import PermissionsMixin, IsValidUser
from common.utils import timeit, lazyproperty
__all__ = ['IndexView']
class MonthLoginMetricMixin:
@lazyproperty
def session_month(self):
month_ago = timezone.now() - timezone.timedelta(days=30)
session_month = Session.objects.filter(date_start__gt=month_ago)
return session_month
@lazyproperty
def session_month_dates(self):
dates = self.session_month.dates('date_start', 'day')
return dates
def get_month_day_metrics(self):
month_str = [
d.strftime('%m-%d') for d in self.session_month_dates
] or ['0']
return month_str
@staticmethod
def get_cache_key(date, tp):
date_str = date.strftime("%Y%m%d")
key = "SESSION_MONTH_{}_{}".format(tp, date_str)
return key
def __get_data_from_cache(self, date, tp):
if date == timezone.now().date():
return None
cache_key = self.get_cache_key(date, tp)
count = cache.get(cache_key)
return count
def __set_data_to_cache(self, date, tp, count):
cache_key = self.get_cache_key(date, tp)
cache.set(cache_key, count, 3600*24*7)
@lazyproperty
def user_disabled_total(self):
return current_org.get_org_members().filter(is_active=False).count()
@lazyproperty
def asset_disabled_total(self):
return Asset.objects.filter(is_active=False).count()
@staticmethod
def get_date_start_2_end(d):
time_min = timezone.datetime.min.time()
time_max = timezone.datetime.max.time()
tz = timezone.get_current_timezone()
ds = timezone.datetime.combine(d, time_min).replace(tzinfo=tz)
de = timezone.datetime.combine(d, time_max).replace(tzinfo=tz)
return ds, de
def get_date_login_count(self, date):
tp = "LOGIN"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = Session.objects.filter(date_start__range=(ds, de)).count()
self.__set_data_to_cache(date, tp, count)
return count
def get_month_login_metrics(self):
data = []
for d in self.session_month_dates:
count = self.get_date_login_count(d)
data.append(count)
if len(data) == 0:
data = [0]
return data
def get_date_user_count(self, date):
tp = "USER"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = Session.objects.filter(date_start__range=(ds, de))\
.values('user').distinct().count()
self.__set_data_to_cache(date, tp, count)
return count
def get_month_active_user_metrics(self):
data = []
for d in self.session_month_dates:
count = self.get_date_user_count(d)
data.append(count)
return data
def get_date_asset_count(self, date):
tp = "ASSET"
count = self.__get_data_from_cache(date, tp)
if count is not None:
return count
ds, de = self.get_date_start_2_end(date)
count = Session.objects.filter(date_start__range=(ds, de)) \
.values('asset').distinct().count()
self.__set_data_to_cache(date, tp, count)
return count
def get_month_active_asset_metrics(self):
data = []
for d in self.session_month_dates:
count = self.get_date_asset_count(d)
data.append(count)
return data
@lazyproperty
def month_active_user_total(self):
count = self.session_month.values('user').distinct().count()
return count
@lazyproperty
def month_inactive_user_total(self):
total = current_org.get_org_members().count()
active = self.month_active_user_total
count = total - active
if count < 0:
count = 0
return count
@lazyproperty
def month_active_asset_total(self):
return self.session_month.values('asset').distinct().count()
@lazyproperty
def month_inactive_asset_total(self):
total = Asset.objects.all().count()
active = self.month_active_asset_total
count = total - active
if count < 0:
count = 0
return count
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'month_str': self.get_month_day_metrics(),
'month_total_visit_count': self.get_month_login_metrics(),
'month_user': self.get_month_active_user_metrics(),
'mouth_asset': self.get_month_active_asset_metrics(),
'month_user_active': self.month_active_user_total,
'month_user_inactive': self.month_inactive_user_total,
'month_user_disabled': self.user_disabled_total,
'month_asset_active': self.month_active_asset_total,
'month_asset_inactive': self.month_inactive_asset_total,
'month_asset_disabled': self.asset_disabled_total,
})
return context
class WeekSessionMetricMixin:
session_week = None
@lazyproperty
def session_week(self):
week_ago = timezone.now() - timezone.timedelta(weeks=1)
session_week = Session.objects.filter(date_start__gt=week_ago)
return session_week
def get_top5_user_a_week(self):
users = self.session_week.values('user') \
.annotate(total=Count('user')) \
.order_by('-total')[:5]
return users
def get_week_login_user_count(self):
return self.session_week.values('user').distinct().count()
def get_week_login_asset_count(self):
return self.session_week.count()
def get_week_top10_assets(self):
assets = self.session_week.values("asset")\
.annotate(total=Count("asset"))\
.annotate(last=Max("date_start")).order_by("-total")[:10]
return assets
def get_week_top10_users(self):
users = self.session_week.values("user") \
.annotate(total=Count("user")) \
.annotate(last=Max("date_start")).order_by("-total")[:10]
return users
def get_last10_sessions(self):
sessions = self.session_week.order_by('-date_start')[:10]
for session in sessions:
session.avatar_url = User.get_avatar_url("")
return sessions
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'user_visit_count_weekly': self.get_week_login_user_count(),
'asset_visit_count_weekly': self.get_week_login_asset_count(),
'user_visit_count_top_five': self.get_top5_user_a_week(),
'last_login_ten': self.get_last10_sessions(),
'week_asset_hot_ten': self.get_week_top10_assets(),
'week_user_hot_ten': self.get_week_top10_users(),
})
return context
class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin, TemplateView):
class IndexView(PermissionsMixin, TemplateView):
template_name = 'index.html'
permission_classes = [IsValidUser]
@ -229,31 +17,9 @@ class IndexView(PermissionsMixin, MonthLoginMetricMixin, WeekSessionMetricMixin,
return redirect('assets:user-asset-list')
return super(IndexView, self).dispatch(request, *args, **kwargs)
@staticmethod
def get_user_count():
return current_org.get_org_members().count()
@staticmethod
def get_asset_count():
return Asset.objects.all().count()
@staticmethod
def get_online_user_count():
count = Session.objects.filter(is_finished=False)\
.values_list('user', flat=True).distinct().count()
return count
@staticmethod
def get_online_session_count():
return Session.objects.filter(is_finished=False).count()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'assets_count': self.get_asset_count(),
'users_count': self.get_user_count(),
'online_user_count': self.get_online_user_count(),
'online_asset_count': self.get_online_session_count(),
'app': _("Dashboard"),
})
return context

File diff suppressed because it is too large Load Diff

@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
#
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework import status, generics
from rest_framework.views import Response
from rest_framework_bulk import BulkModelViewSet
from common.permissions import IsSuperUserOrAppUser
from .models import Organization
from .serializers import OrgSerializer, OrgReadSerializer, \
OrgMembershipUserSerializer, OrgMembershipAdminSerializer
OrgMembershipUserSerializer, OrgMembershipAdminSerializer, \
OrgAllUserSerializer
from users.models import User, UserGroup
from assets.models import Asset, Domain, AdminUser, SystemUser, Label
from perms.models import AssetPermission
@ -67,3 +69,15 @@ class OrgMembershipUsersViewSet(OrgMembershipModelViewSetMixin, BulkModelViewSet
membership_class = Organization.users.through
permission_classes = (IsSuperUserOrAppUser, )
class OrgAllUserListApi(generics.ListAPIView):
permission_classes = (IsSuperUserOrAppUser,)
serializer_class = OrgAllUserSerializer
filter_fields = ("username", "name")
search_fields = filter_fields
def get_queryset(self):
pk = self.kwargs.get("pk")
org = get_object_or_404(Organization, pk=pk)
users = org.get_org_users().only(*self.serializer_class.Meta.only_fields)
return users

@ -80,3 +80,15 @@ class OrgMembershipUserSerializer(OrgMembershipSerializerMixin, ModelSerializer)
model = Organization.users.through
list_serializer_class = AdaptedBulkListSerializer
fields = '__all__'
class OrgAllUserSerializer(serializers.Serializer):
user = serializers.UUIDField(read_only=True, source='id')
user_display = serializers.SerializerMethodField()
class Meta:
only_fields = ['id', 'username', 'name']
@staticmethod
def get_user_display(obj):
return str(obj)

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
from django.urls import re_path
from django.urls import re_path, path
from rest_framework.routers import DefaultRouter
from common import api as capi
@ -24,6 +24,7 @@ old_version_urlpatterns = [
]
urlpatterns = [
path('<uuid:pk>/users/all/', api.OrgAllUserListApi.as_view(), name='org-all-users'),
]
urlpatterns += router.urls + old_version_urlpatterns

@ -1,14 +1,7 @@
from __future__ import unicode_literals
from django.conf import settings
from django.apps import AppConfig
class PermsConfig(AppConfig):
name = 'perms'
def ready(self):
from . import signals_handler
if not settings.XPACK_ENABLED:
settings.ASSETS_PERM_CACHE_ENABLE = False
return super().ready()

@ -302,7 +302,6 @@ class AssetPermissionUtil(AssetPermissionUtilCacheMixin):
continue
ancestors = self.full_tree.ancestors(
child.identifier, with_self=False, deep=True,
with_assets=False,
)
# print("Get ancestors: {}".format(len(ancestors)))
if not ancestors:

@ -11,7 +11,7 @@
<h5>{% trans 'Total users' %}</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'users:user-list' %}">{{ users_count }}</a></h1>
<h1 class="no-margins"><a href="{% url 'users:user-list' %}"><span id="total_count_users"></span></a></h1>
<small>All users</small>
</div>
</div>
@ -19,12 +19,12 @@
<div class="col-sm-3">
<div class="ibox float-e-margins">
<div class="ibox-title">
<span class="label label-info pull-right">Hosts</span>
<h5>{% trans 'Total hosts' %}</h5>
<span class="label label-info pull-right">Assets</span>
<h5>{% trans 'Total assets' %}</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'assets:asset-list' %}">{{ assets_count }}</a></h1>
<small>All hosts</small>
<h1 class="no-margins"><a href="{% url 'assets:asset-list' %}"><span id="total_count_assets"></span></a></h1>
<small>All assets</small>
</div>
</div>
</div>
@ -36,7 +36,7 @@
<h5>{% trans 'Online users' %}</h5>
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_users"></span>{{ online_user_count }}</a></h1>
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="total_count_online_users"></span></a></h1>
<small>Online users</small>
</div>
</div>
@ -50,7 +50,7 @@
</div>
<div class="ibox-content">
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="online_hosts"></span>{{ online_asset_count }}</a></h1>
<h1 class="no-margins"><a href="{% url 'terminal:session-online-list' %}"> <span id="total_count_online_sessions"></span></a></h1>
<small>Online sessions</small>
</div>
</div>
@ -58,19 +58,11 @@
</div>
<div class="row">
<div class="col-sm-2 border-bottom white-bg dashboard-header" style="margin-left:15px;height: 346px">
<small>{% trans 'In the past week, a total of ' %}<span class="text-info">{{ user_visit_count_weekly }}</span>{% trans ' users have logged in ' %}<span class="text-success">{{ asset_visit_count_weekly }}</span>{% trans ' times asset.' %}</small>
<ul class="list-group clear-list m-t">
{% for data in user_visit_count_top_five %}
<li class="list-group-item fist-item">
<span class="pull-right">
{{ data.total }}{% trans ' times/week' %}
</span>
<span class="label ">{{ forloop.counter }}</span> {{ data.user }}
</li>
{% endfor %}
<small>{% trans 'In the past week, a total of ' %}<span class="text-info" id="week_total_count_login_users"></span>{% trans ' users have logged in ' %}<span class="text-success" id="week_total_count_login_times"></span>{% trans ' times asset.' %}</small>
<ul class="list-group clear-list m-t" id="week_login_times_top5_users">
</ul>
</div>
<div class="col-sm-7" id="top10" style="margin-left: -15px;height: 346px;padding: 15px 0 15px 0;"></div>
<div class="col-sm-7" id="month_metrics_echarts" style="margin-left: -15px;height: 346px;padding: 15px 0 15px 0;"></div>
<div class="col-sm-3 white-bg" id="top1" style="margin-left: -15px;height: 346px">
<div class="statistic-box">
<h4>
@ -81,13 +73,13 @@
</p>
<div class="row text-center">
<div class="col-sm-6">
<div id="activeUser" style="width: 140px; height: 140px;">
<div id="month_total_count_users_pie" style="width: 140px; height: 140px;">
</div>
<h5>{% trans 'User' %}</h5>
</div>
<div class="col-sm-6">
<div id="activeAsset" style="width: 140px; height: 140px;"></div>
<h5>{% trans 'Host' %}</h5>
<div id="month_total_count_assets_pie" style="width: 140px; height: 140px;"></div>
<h5>{% trans 'Asset' %}</h5>
</div>
</div>
<div class="m-t">
@ -120,27 +112,7 @@
<h3><i class="fa fa-inbox"></i>{% trans 'Top 10 assets in a week'%}</h3>
<small><i class="fa fa-map-marker"></i>{% trans 'Login frequency and last login record.' %}</small>
</div>
<div class="ibox-content inspinia-timeline">
{% if week_asset_hot_ten %}
{% for data in week_asset_hot_ten %}
<div class="timeline-item">
<div class="row">
<div class="col-xs-5 date ellipsis">
<i class="fa fa-info-circle"></i>
<strong data-toggle="tooltip" title="{{ data.asset }}">{{ data.asset }}</strong>
<br/>
<small class="text-navy">{{ data.total }}{% trans ' times' %}</small>
</div>
<div class="col-xs-7 content no-top-border">
<p class="m-b-xs">{% trans 'The time last logged in' %}</p>
<p>{% trans 'At' %} {{ data.last|date:"Y-m-d H:i:s" }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-center">{% trans '(No)' %}</p>
{% endif %}
<div class="ibox-content inspinia-timeline" id="week_login_times_top10_assets">
</div>
</div>
</div>
@ -158,27 +130,7 @@
</div>
<div class="ibox-content">
<div>
<div class="feed-activity-list">
{% if last_login_ten %}
{% for login in last_login_ten %}
<div class="feed-element">
<a href="#" class="pull-left">
<img alt="image" class="img-circle" src="{% static 'img/avatar/user.png' %}">
</a>
<div class="media-body ">
{% ifequal login.is_finished 0 %}
<small class="pull-right text-navy">{{ login.date_start|timesince }} {% trans 'Before' %}</small>
{% else %}
<small class="pull-right">{{ login.date_start|timesince }} {% trans 'Before' %}</small>
{% endifequal %}
<strong>{{ login.user }}</strong> {% trans 'Login in ' %}{{ login.asset }} <br>
<small class="text-muted">{{ login.date_start }}</small>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-center">{% trans '(No)' %}</p>
{% endif %}
<div class="feed-activity-list" id="week_login_record_top10_sessions">
</div>
</div>
</div>
@ -206,27 +158,7 @@
<h3><i class="fa fa-user"></i>{% trans 'Top 10 users in a week' %}</h3>
<small><i class="fa fa-map-marker"></i>{% trans 'User login frequency and last login record.' %}</small>
</div>
<div class="ibox-content inspinia-timeline">
{% if week_user_hot_ten %}
{% for data in week_user_hot_ten %}
<div class="timeline-item">
<div class="row">
<div class="col-xs-5 date ellipsis">
<i class="fa fa-info-circle"></i>
<strong data-toggle="tooltip" title="{{ data.user }}">{{ data.user }}</strong>
<br/>
<small class="text-navy">{{ data.total }}{% trans ' times' %}</small>
</div>
<div class="col-xs-7 content no-top-border">
<p class="m-b-xs">{% trans 'The time last logged in' %}</p>
<p>{% trans 'At' %} {{ data.last|date:"Y-m-d H:i:s" }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="text-center">{% trans '(No)' %}</p>
{% endif %}
<div class="ibox-content inspinia-timeline" id="week_login_times_top10_users">
</div>
</div>
</div>
@ -238,27 +170,15 @@
{% block custom_foot_js %}
<script src="{% static 'js/plugins/echarts/echarts.js' %}"></script>
<script>
$(document).ready(function(){
$('#show').click(function(){
$('#show').css('display', 'none');
$('#more').css('display', 'block');
});
$("[data-toggle='tooltip']").tooltip();
});
require.config({
paths: {
'echarts': '/static/js/plugins/echarts/chart/',
'echarts/chart/line': '/static/js/plugins/echarts/chart/line',
'echarts/chart/pie': '/static/js/plugins/echarts/chart/pie'
}
});
function requireMonthMetricsECharts(data){
require(
[
'echarts',
'echarts/chart/line'
],
function (ec) {
var top10Chart = ec.init(document.getElementById('top10'));
var monthMetricsECharts = ec.init(document.getElementById('month_metrics_echarts'));
var option = {
title : {
text: "{% trans 'Monthly data overview' %}",
@ -284,7 +204,7 @@ $(document).ready(function(){
{
type : 'category',
boundaryGap : false,
data : {{ month_str|safe}}
data : data['month_metrics_date'],
}
],
yAxis : [
@ -298,38 +218,42 @@ $(document).ready(function(){
type:'line',
smooth: true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ month_total_visit_count|safe}}
data: data['month_metrics_total_count_login']
},
{
name: "{% trans 'Active users' %}",
type: 'line',
smooth: true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ month_user|safe }}
data: data['month_metrics_total_count_active_users']
},
{
name:"{% trans 'Active assets' %}",
type:'line',
smooth:true,
itemStyle: {normal: {areaStyle: {type: 'default'}}},
data: {{ mouth_asset|safe }}
data: data['month_metrics_total_count_active_assets']
}
]
};
top10Chart.setOption(option);
monthMetricsECharts.setOption(option);
}
);
}
function requireMonthTotalCountUsersPie(data){
require(
[
'echarts',
'echarts/chart/pie'
],
function (ec) {
var auChart = ec.init(document.getElementById('activeUser'));
var monthTotalCountUsersPie = ec.init(document.getElementById('month_total_count_users_pie'));
var option = {
tooltip : {
trigger: 'item',
formatter: "{b} <br> {c} ({d}%)"
formatter: "{b} <br> {c} <br> ({d}%)"
},
legend: {
show: false,
@ -364,6 +288,7 @@ $(document).ready(function(){
name:"{% trans 'Access to the source' %}",
type:'pie',
radius : ['50%', '70%'],
avoidLabelOverlap: false,
itemStyle : {
normal : {
label : {
@ -385,33 +310,36 @@ $(document).ready(function(){
}
},
data:[
{value:{{ month_user_active }}, name:"{% trans 'Monthly active users' %}"},
{value:{{ month_user_disabled }}, name:"{% trans 'Disable user' %}"},
{value:{{ month_user_inactive }}, name:"{% trans 'Month not logged in user' %}"}
{value:data['month_total_count_active_users'], name:"{% trans 'Monthly active users' %}"},
{value:data['month_total_count_disabled_users'], name:"{% trans 'Disable user' %}"},
{value:data['month_total_count_inactive_users'], name:"{% trans 'Month not logged in user' %}"}
]
}
]
};
auChart.setOption(option);
monthTotalCountUsersPie.setOption(option);
}
);
}
function requireMonthTotalCountAssetsPie(data){
require(
[
'echarts',
'echarts/chart/pie'
],
function (ec) {
var aaChart = ec.init(document.getElementById('activeAsset'));
var monthTotalCountAssetsPie = ec.init(document.getElementById('month_total_count_assets_pie'));
var option = {
tooltip : {
trigger: 'item',
formatter: "{b} <br> {c} ({d}%)"
formatter: "{b} <br> {c} <br> ({d}%)"
},
legend: {
show: false,
orient : 'vertical',
x : 'left',
data:["{% trans 'Month is logged into the host' %}", "{% trans 'Disable host' %}", "{% trans 'Month not logged on host' %}"]
data:["{% trans 'Month is logged into the asset' %}", "{% trans 'Disable host' %}", "{% trans 'Month not logged on host' %}"]
},
toolbox: {
show : false,
@ -461,16 +389,230 @@ $(document).ready(function(){
}
},
data:[
{value:{{ month_asset_active }}, name:"{% trans 'Month is logged into the host' %}"},
{value:{{ month_asset_disabled }}, name:"{% trans 'Disable host' %}"},
{value:{{ month_asset_inactive }}, name:"{% trans 'Month not logged on host' %}"}
{value:data['month_total_count_active_assets'], name:"{% trans 'Month is logged into the host' %}"},
{value:data['month_total_count_disabled_assets'], name:"{% trans 'Disable host' %}"},
{value:data['month_total_count_inactive_assets'], name:"{% trans 'Month not logged on host' %}"}
]
}
]
};
aaChart.setOption(option);
monthTotalCountAssetsPie.setOption(option);
}
);
}
var indexUrl = "/api/v1/index/";
function renderRequestApi(query, success, error){
var url = indexUrl + "?" + query;
if (!error){
error = function (){console.log("Request url error: " + url)}
}
requestApi({
url: url,
method: "GET",
success: success,
error: error,
flash_message: false,
})
}
function renderTotalCount(){
var success = function (data) {
$('#total_count_assets').html(data['total_count_assets']);
$('#total_count_users').html(data['total_count_users']);
$('#total_count_online_users').html(data['total_count_online_users']);
$('#total_count_online_sessions').html(data['total_count_online_sessions']);
};
renderRequestApi('total_count=1', success);
}
function renderMonthMetricsECharts(){
var success = function (data) {
requireMonthMetricsECharts(data)
};
renderRequestApi('month_metrics=1', success)
}
function renderMonthTotalCountUsersPie(){
var success = function (data) {
requireMonthTotalCountUsersPie(data)
};
renderRequestApi('month_total_count_users=1', success)
}
function renderMonthTotalCountAssetsPie(){
var success = function (data) {
requireMonthTotalCountAssetsPie(data)
};
renderRequestApi('month_total_count_assets=1', success)
}
function renderWeekTotalCount(){
var success = function (data) {
$('#week_total_count_login_users').html(data['week_total_count_login_users']);
$('#week_total_count_login_times').html(data['week_total_count_login_times'])
};
renderRequestApi('week_total_count=1', success)
}
function renderWeekLoginTimesTop5Users(){
var success = function (data){
var html = "";
var html_cell = "" +
"<li class=\"list-group-item fist-item\">" +
"<span class=\"pull-right\">" +
"{TOTAL} {% trans ' times/week' %}" +
"</span>" +
"<span class=\"label \">{INDEX}</span> {USER}" +
"</li>";
$.each(data['week_login_times_top5_users'], function(index, value){
html += html_cell.replace('{TOTAL}', value['total'])
.replace('{USER}', value['user'])
.replace('{INDEX}', index+1)
});
$('#week_login_times_top5_users').html(html)
};
renderRequestApi('week_login_times_top5_users=1', success)
}
function renderWeekLoginTimesTop10Assets(){
var success = function (data){
var html = "";
var html_cell = "" +
"<div class=\"timeline-item\">" +
"<div class=\"row\">" +
"<div class=\"col-xs-5 date ellipsis\">" +
"<i class=\"fa fa-info-circle\"></i>" +
"<strong data-toggle=\"tooltip\" title=\"{ASSET}\">{ASSET}</strong>" +
"<br/>" +
"<small class=\"text-navy\">{TOTAL}{% trans ' times' %}</small>" +
"</div>" +
"<div class=\"col-xs-7 content no-top-border\">" +
"<p class=\"m-b-xs\">{% trans 'The time last logged in' %}</p>" +
"<p>{% trans 'At' %} {DATE_LAST}</p>" +
"</div>" +
"</div>" +
"</div>";
var assets = data['week_login_times_top10_assets'];
if (assets.length !== 0){
$.each(assets, function(index, value){
html += html_cell
.replaceAll('{ASSET}', value['asset'])
.replace('{TOTAL}', value['total'])
.replace('{DATE_LAST}', toSafeLocalDateStr(value['last']))
});
}
else{
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
}
$('#week_login_times_top10_assets').html(html)
};
renderRequestApi('week_login_times_top10_assets=1', success)
}
function renderWeekLoginTimesTop10Users(){
var success = function (data){
var html = "";
var html_cell = "" +
"<div class=\"timeline-item\">" +
"<div class=\"row\">" +
"<div class=\"col-xs-5 date ellipsis\">" +
"<i class=\"fa fa-info-circle\"></i>" +
"<strong data-toggle=\"tooltip\" title=\"{USER}\">{USER}</strong>" +
"<br/>" +
"<small class=\"text-navy\">{TOTAL}{% trans ' times' %}</small>" +
"</div>" +
"<div class=\"col-xs-7 content no-top-border\">" +
"<p class=\"m-b-xs\">{% trans 'The time last logged in' %}</p>" +
"<p>{% trans 'At' %} {DATE_LAST}</p>" +
"</div>" +
"</div>" +
"</div>";
var users = data['week_login_times_top10_users'];
if (users.length !== 0){
$.each(users, function(index, value){
html += html_cell.replaceAll('{USER}', value['user'])
.replace('{TOTAL}', value['total'])
.replace('{DATE_LAST}', toSafeLocalDateStr(value['last']))
});
}
else{
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
}
$('#week_login_times_top10_users').html(html)
};
renderRequestApi('week_login_times_top10_users=1', success)
}
function renderWeekLoginRecordTop10Sessions(){
var success = function (data){
var html = "";
var html_cell = "" +
"<div class=\"feed-element\">" +
"<a href=\"#\" class=\"pull-left\">" +
"<img alt=\"image\" class=\"img-circle\" src=\"{% static 'img/avatar/user.png' %}\">" +
"</a>" +
"<div class=\"media-body \">" +
"<small class=\"pull-right {TEXT_NAVY}\">{TIMESINCE} {% trans 'Before' %}</small>" +
"<strong>{USER}</strong> {% trans 'Login in ' %}{ASSET} <br>" +
"<small class=\"text-muted\">{DATE_START}</small>" +
"</div>" +
"</div>";
var users = data['week_login_record_top10_sessions'];
if (users.length !== 0){
$.each(users, function(index, value){
console.log(value['is_finished'])
html += html_cell.replaceAll('{USER}', value['user'])
.replace('{ASSET}', value['asset'])
.replace('{DATE_START}', toSafeLocalDateStr(value['date_start']))
.replace('{TEXT_NAVY}', value['is_finished']?'':'text-navy')
.replace('{TIMESINCE}', value['timesince'])
});
}
else{
html += "<p class=\"text-center\">{% trans '(No)' %}</p>"
}
$('#week_login_record_top10_sessions').html(html)
};
renderRequestApi('week_login_record_top10_sessions=1', success)
}
function renderData(){
renderTotalCount();
renderMonthMetricsECharts();
renderMonthTotalCountUsersPie();
renderMonthTotalCountAssetsPie();
renderWeekTotalCount();
renderWeekLoginTimesTop5Users();
renderWeekLoginTimesTop10Assets();
renderWeekLoginRecordTop10Sessions();
renderWeekLoginTimesTop10Users();
}
require.config({
paths: {
'echarts': '/static/js/plugins/echarts/chart/',
'echarts/chart/line': '/static/js/plugins/echarts/chart/line',
'echarts/chart/pie': '/static/js/plugins/echarts/chart/pie'
}
});
$(document).ready(function(){
$('#show').click(function(){
$('#show').css('display', 'none');
$('#more').css('display', 'block');
});
$("[data-toggle='tooltip']").tooltip();
renderData()
});
</script>
{% endblock %}

@ -16,7 +16,7 @@ class TerminalSerializer(serializers.ModelSerializer):
fields = [
'id', 'name', 'remote_addr', 'http_port', 'ssh_port',
'comment', 'is_accepted', "is_active", 'session_online',
'is_alive'
'is_alive', 'date_created', 'command_storage', 'replay_storage'
]
@staticmethod

@ -1,9 +1,13 @@
# -*- coding: utf-8 -*-
#
from .. import utils
from users.models import User
class UserQuerysetMixin:
def get_queryset(self):
queryset = utils.get_current_org_members()
if self.request.query_params.get('all'):
queryset = User.objects.exclude(role=User.ROLE_APP)
else:
queryset = utils.get_current_org_members()
return queryset

@ -3,12 +3,17 @@
from django.dispatch import receiver
from django.db.models.signals import m2m_changed
from django_auth_ldap.backend import populate_user
from django.conf import settings
from django_cas_ng.signals import cas_user_authenticated
from jms_oidc_rp.signals import openid_create_or_update_user
from common.utils import get_logger
from .signals import post_user_create
from .models import User
logger = get_logger(__file__)
@ -37,3 +42,33 @@ def on_cas_user_authenticated(sender, user, created, **kwargs):
if created:
user.source = user.SOURCE_CAS
user.save()
@receiver(populate_user)
def on_ldap_create_user(sender, user, ldap_user, **kwargs):
if user and user.username not in ['admin']:
exists = User.objects.filter(username=user.username).exists()
if not exists:
user.source = user.SOURCE_LDAP
user.save()
@receiver(openid_create_or_update_user)
def on_openid_create_or_update_user(sender, request, user, created, name, username, email, **kwargs):
if created:
logger.debug(
"Receive OpenID user created signal: {}, "
"Set user source is: {}".format(user, User.SOURCE_OPENID)
)
user.source = User.SOURCE_OPENID
user.save()
elif not created and settings.AUTH_OPENID_ALWAYS_UPDATE_USER:
logger.debug(
"Receive OpenID user updated signal: {}, "
"Update user info: {}"
"".format(user, "name: {}|username: {}|email: {}".format(name, username, email))
)
user.name = name
user.username = username
user.email = email
user.save()

@ -83,12 +83,26 @@ class UserOtpEnableBindView(TemplateView, FormView):
return super().get_context_data(**kwargs)
class UserDisableMFAView(FormView):
template_name = 'users/user_disable_mfa.html'
class UserVerifyMFAView(FormView):
template_name = 'users/user_verify_mfa.html'
form_class = forms.UserCheckOtpCodeForm
success_url = reverse_lazy('users:user-otp-settings-success')
permission_classes = [IsValidUser]
def form_valid(self, form):
user = self.request.user
otp_code = form.cleaned_data.get('otp_code')
valid = user.check_mfa(otp_code)
if valid:
return super().form_valid(form)
else:
error = _('MFA code invalid, or ntp sync server time')
form.add_error('otp_code', error)
return super().form_invalid(form)
class UserDisableMFAView(UserVerifyMFAView):
def form_valid(self, form):
user = self.request.user
otp_code = form.cleaned_data.get('otp_code')
@ -104,7 +118,7 @@ class UserDisableMFAView(FormView):
return super().form_invalid(form)
class UserOtpUpdateView(UserDisableMFAView):
class UserOtpUpdateView(UserVerifyMFAView):
success_url = reverse_lazy('users:user-otp-enable-bind')

@ -29,7 +29,6 @@ BOOTSTRAP_TOKEN:
# 使用单文件sqlite数据库
# DB_ENGINE: sqlite3
# DB_NAME:
# MySQL or postgres setting like:
# 使用Mysql作为数据库
DB_ENGINE: mysql
@ -54,16 +53,27 @@ REDIS_PORT: 6379
# REDIS_DB_CELERY: 3
# REDIS_DB_CACHE: 4
# Use OpenID authorization
# 使用OpenID 来进行认证设置
# BASE_SITE_URL: http://localhost:8080
# AUTH_OPENID: false # True or False
# AUTH_OPENID_SERVER_URL: https://openid-auth-server.com/
# AUTH_OPENID_REALM_NAME: realm-name
# Use OpenID Authorization
# 使用 OpenID 进行认证设置
# AUTH_OPENID: False # True or False
# AUTH_OPENID_CLIENT_ID: client-id
# AUTH_OPENID_CLIENT_SECRET: client-secret
# AUTH_OPENID_IGNORE_SSL_VERIFICATION: True
# AUTH_OPENID_PROVIDER_ENDPOINT: https://op-example.com/
# AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT: https://op-example.com/authorize
# AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT: https://op-example.com/token
# AUTH_OPENID_PROVIDER_JWKS_ENDPOINT: https://op-example.com/jwks
# AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT: https://op-example.com/userinfo
# AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT: https://op-example.com/logout
# AUTH_OPENID_PROVIDER_SIGNATURE_ALG: HS256
# AUTH_OPENID_PROVIDER_SIGNATURE_KEY: None
# AUTH_OPENID_SCOPES: "openid profile email"
# AUTH_OPENID_ID_TOKEN_MAX_AGE: 60
# AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS: True
# AUTH_OPENID_USE_STATE: True
# AUTH_OPENID_USE_NONCE: True
# AUTH_OPENID_SHARE_SESSION: True
# AUTH_OPENID_IGNORE_SSL_VERIFICATION: True
# AUTH_OPENID_ALWAYS_UPDATE_USER: True
# Use Radius authorization
# 使用Radius来认证

@ -74,8 +74,6 @@ Werkzeug==0.15.3
drf-nested-routers==0.91
aliyun-python-sdk-core-v3==2.9.1
aliyun-python-sdk-ecs==4.10.1
python-keycloak==0.13.3
python-keycloak-client==0.1.3
rest_condition==1.0.3
python-ldap==3.1.0
tencentcloud-sdk-python==3.0.40
@ -98,3 +96,4 @@ ipython
huaweicloud-sdk-python==1.0.21
django-redis==4.11.0
python-redis-lock==3.5.0
jumpserver-django-oidc-rp==0.3.7.3

Loading…
Cancel
Save