sessions: added interface to show, and cleanup user auth sessions.
This commit is contained in:
parent
1ba0fb5d27
commit
fe5871e9df
8 changed files with 251 additions and 2 deletions
|
|
@ -83,8 +83,10 @@ class NavigationRegistry(object):
|
|||
NavEntry('integrations', _('Integrations'),
|
||||
'global_integrations_home', pyramid=True),
|
||||
NavEntry('system', _('System Info'), 'admin_settings_system'),
|
||||
NavEntry('session', _('User Sessions'), 'admin_settings_sessions'),
|
||||
NavEntry('open_source', _('Open Source Licenses'),
|
||||
'admin_settings_open_source', pyramid=True),
|
||||
|
||||
# TODO: marcink: we disable supervisor now until the supervisor stats
|
||||
# page is fixed in the nix configuration
|
||||
# NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
|
||||
|
|
|
|||
|
|
@ -509,6 +509,12 @@ def make_map(config):
|
|||
m.connect('admin_settings_system_update', '/settings/system/updates',
|
||||
action='settings_system_update', conditions={'method': ['GET']})
|
||||
|
||||
m.connect('admin_settings_sessions', '/settings/sessions',
|
||||
action='settings_sessions', conditions={'method': ['GET']})
|
||||
|
||||
m.connect('admin_settings_sessions_cleanup', '/settings/sessions/cleanup',
|
||||
action='settings_sessions_cleanup', conditions={'method': ['POST']})
|
||||
|
||||
m.connect('admin_settings_supervisor', '/settings/supervisor',
|
||||
action='settings_supervisor', conditions={'method': ['GET']})
|
||||
m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ from rhodecode.lib.utils2 import (
|
|||
from rhodecode.lib.compat import OrderedDict
|
||||
from rhodecode.lib.ext_json import json
|
||||
from rhodecode.lib.utils import jsonify
|
||||
from rhodecode.lib import system_info
|
||||
from rhodecode.lib import user_sessions
|
||||
|
||||
from rhodecode.model.db import RhodeCodeUi, Repository
|
||||
from rhodecode.model.forms import ApplicationSettingsForm, \
|
||||
|
|
@ -690,6 +692,52 @@ class SettingsController(BaseController):
|
|||
|
||||
return render('admin/settings/settings_system_update.mako')
|
||||
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
def settings_sessions(self):
|
||||
# url('admin_settings_sessions')
|
||||
|
||||
c.active = 'sessions'
|
||||
c.cleanup_older_days = 60
|
||||
older_than_seconds = 24 * 60 * 60 * 24 * c.cleanup_older_days
|
||||
|
||||
config = system_info.rhodecode_config().get_value()['value']['config']
|
||||
c.session_model = user_sessions.get_session_handler(
|
||||
config.get('beaker.session.type', 'memory'))(config)
|
||||
|
||||
c.session_conf = c.session_model.config
|
||||
c.session_count = c.session_model.get_count()
|
||||
c.session_expired_count = c.session_model.get_expired_count(
|
||||
older_than_seconds)
|
||||
|
||||
return render('admin/settings/settings.mako')
|
||||
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
def settings_sessions_cleanup(self):
|
||||
# url('admin_settings_sessions_update')
|
||||
|
||||
expire_days = safe_int(request.POST.get('expire_days'))
|
||||
|
||||
if expire_days is None:
|
||||
expire_days = 60
|
||||
|
||||
older_than_seconds = 24 * 60 * 60 * 24 * expire_days
|
||||
|
||||
config = system_info.rhodecode_config().get_value()['value']['config']
|
||||
session_model = user_sessions.get_session_handler(
|
||||
config.get('beaker.session.type', 'memory'))(config)
|
||||
|
||||
try:
|
||||
session_model.clean_sessions(
|
||||
older_than_seconds=older_than_seconds)
|
||||
h.flash(_('Cleaned up old sessions'), category='success')
|
||||
except user_sessions.CleanupCommand as msg:
|
||||
h.flash(msg, category='warning')
|
||||
except Exception as e:
|
||||
log.exception('Failed session cleanup')
|
||||
h.flash(_('Failed to cleanup up old sessions'), category='error')
|
||||
|
||||
return redirect(url('admin_settings_sessions'))
|
||||
|
||||
@HasPermissionAllDecorator('hg.admin')
|
||||
def settings_supervisor(self):
|
||||
c.rhodecode_ini = rhodecode.CONFIG
|
||||
|
|
|
|||
|
|
@ -64,6 +64,9 @@ class SysInfoRes(object):
|
|||
'human_value': self.human_value,
|
||||
}
|
||||
|
||||
def get_value(self):
|
||||
return self.__json__()
|
||||
|
||||
def __str__(self):
|
||||
return '<SysInfoRes({})>'.format(self.__json__())
|
||||
|
||||
|
|
|
|||
125
rhodecode/lib/user_sessions.py
Normal file
125
rhodecode/lib/user_sessions.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2017-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import datetime
|
||||
import dateutil
|
||||
from rhodecode.model.db import DbSession, Session
|
||||
|
||||
|
||||
class CleanupCommand(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseAuthSessions(object):
|
||||
SESSION_TYPE = None
|
||||
|
||||
def __init__(self, config):
|
||||
session_conf = {}
|
||||
for k, v in config.items():
|
||||
if k.startswith('beaker.session'):
|
||||
session_conf[k] = v
|
||||
self.config = session_conf
|
||||
|
||||
def get_count(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_expired_count(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def clean_sessions(self, older_than_seconds=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def _seconds_to_date(self, seconds):
|
||||
return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
|
||||
seconds=seconds)
|
||||
|
||||
|
||||
class DbAuthSessions(BaseAuthSessions):
|
||||
SESSION_TYPE = 'ext:database'
|
||||
|
||||
def get_count(self):
|
||||
return DbSession.query().count()
|
||||
|
||||
def get_expired_count(self, older_than_seconds=None):
|
||||
expiry_date = self._seconds_to_date(older_than_seconds)
|
||||
return DbSession.query().filter(DbSession.accessed < expiry_date).count()
|
||||
|
||||
def clean_sessions(self, older_than_seconds=None):
|
||||
expiry_date = self._seconds_to_date(older_than_seconds)
|
||||
DbSession.query().filter(DbSession.accessed < expiry_date).delete()
|
||||
Session().commit()
|
||||
|
||||
|
||||
class FileAuthSessions(BaseAuthSessions):
|
||||
SESSION_TYPE = 'file sessions'
|
||||
|
||||
def get_count(self):
|
||||
return 'NOT AVAILABLE'
|
||||
|
||||
def get_expired_count(self):
|
||||
return self.get_count()
|
||||
|
||||
def clean_sessions(self, older_than_seconds=None):
|
||||
data_dir = self.config.get('beaker.session.data_dir')
|
||||
raise CleanupCommand(
|
||||
'Please execute this command: '
|
||||
'`find . -mtime +60 -exec rm {{}} \;` inside {} directory'.format(
|
||||
data_dir))
|
||||
|
||||
|
||||
class MemcachedAuthSessions(BaseAuthSessions):
|
||||
SESSION_TYPE = 'ext:memcached'
|
||||
|
||||
def get_count(self):
|
||||
return 'NOT AVAILABLE'
|
||||
|
||||
def get_expired_count(self):
|
||||
return self.get_count()
|
||||
|
||||
def clean_sessions(self, older_than_seconds=None):
|
||||
raise CleanupCommand('Cleanup for this session type not yet available')
|
||||
|
||||
|
||||
class MemoryAuthSessions(BaseAuthSessions):
|
||||
SESSION_TYPE = 'memory'
|
||||
|
||||
def get_count(self):
|
||||
return 'NOT AVAILABLE'
|
||||
|
||||
def get_expired_count(self):
|
||||
return self.get_count()
|
||||
|
||||
def clean_sessions(self, older_than_seconds=None):
|
||||
raise CleanupCommand('Cleanup for this session type not yet available')
|
||||
|
||||
|
||||
def get_session_handler(session_type):
|
||||
types = {
|
||||
'file': FileAuthSessions,
|
||||
'ext:memcached': MemcachedAuthSessions,
|
||||
'ext:database': DbAuthSessions,
|
||||
'memory': MemoryAuthSessions
|
||||
}
|
||||
|
||||
try:
|
||||
return types[session_type]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
'This type {} is not supported'.format(session_type))
|
||||
|
|
@ -3812,7 +3812,12 @@ class DbSession(Base, BaseModel):
|
|||
{'extend_existing': True, 'mysql_engine': 'InnoDB',
|
||||
'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<DB:DbSession({})>'.format(self.id)
|
||||
|
||||
id = Column('id', Integer())
|
||||
namespace = Column('namespace', String(255), primary_key=True)
|
||||
accessed = Column('accessed', DateTime, nullable=False)
|
||||
created = Column('created', DateTime, nullable=False)
|
||||
data = Column('data', PickleType, nullable=False)
|
||||
data = Column('data', PickleType, nullable=False)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
(_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''),
|
||||
]
|
||||
%>
|
||||
<dl class="dl-horizontal">
|
||||
<dl class="dl-horizontal settings">
|
||||
%for dt, dd, tt in elems:
|
||||
<dt >${dt}:</dt>
|
||||
<dd title="${tt}">${dd}</dd>
|
||||
|
|
|
|||
60
rhodecode/templates/admin/settings/settings_sessions.mako
Normal file
60
rhodecode/templates/admin/settings/settings_sessions.mako
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('User Sessions Configuration')}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<%
|
||||
elems = [
|
||||
(_('Session type'), c.session_model.SESSION_TYPE, ''),
|
||||
(_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
|
||||
|
||||
(_('Total sessions'), c.session_count, ''),
|
||||
(_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),
|
||||
|
||||
]
|
||||
%>
|
||||
<dl class="dl-horizontal settings">
|
||||
%for dt, dd, tt in elems:
|
||||
<dt>${dt}:</dt>
|
||||
<dd title="${tt}">${dd}</dd>
|
||||
%endfor
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('Cleanup Old Sessions')}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
${h.secure_form(h.url('admin_settings_sessions_cleanup'), method='post')}
|
||||
|
||||
<div style="margin: 0 0 20px 0" class="fake-space">
|
||||
${_('Cleanup all sessions that were not active during choosen time frame')} <br/>
|
||||
${_('Picking All will log-out all users in the system, and each user will be required to log in again.')}
|
||||
</div>
|
||||
<select id="expire_days" name="expire_days">
|
||||
% for n in [60, 90, 30, 7, 0]:
|
||||
<option value="${n}">${'{} days'.format(n) if n != 0 else 'All'}</option>
|
||||
% endfor
|
||||
</select>
|
||||
<button class="btn btn-small" type="submit"
|
||||
onclick="return confirm('${_('Confirm to cleanup user sessions')}');">
|
||||
${_('Cleanup sessions')}
|
||||
</button>
|
||||
${h.end_form()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#expire_days').select2({
|
||||
containerCssClass: 'drop-menu',
|
||||
dropdownCssClass: 'drop-menu-dropdown',
|
||||
dropdownAutoWidth: true,
|
||||
minimumResultsForSearch: -1
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue