##// END OF EJS Templates
sessions: added interface to show, and cleanup user auth sessions.
marcink -
r1295:5854ddda default
parent child Browse files
Show More
@@ -0,0 +1,125 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import datetime
22 import dateutil
23 from rhodecode.model.db import DbSession, Session
24
25
26 class CleanupCommand(Exception):
27 pass
28
29
30 class BaseAuthSessions(object):
31 SESSION_TYPE = None
32
33 def __init__(self, config):
34 session_conf = {}
35 for k, v in config.items():
36 if k.startswith('beaker.session'):
37 session_conf[k] = v
38 self.config = session_conf
39
40 def get_count(self):
41 raise NotImplementedError
42
43 def get_expired_count(self):
44 raise NotImplementedError
45
46 def clean_sessions(self, older_than_seconds=None):
47 raise NotImplementedError
48
49 def _seconds_to_date(self, seconds):
50 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
51 seconds=seconds)
52
53
54 class DbAuthSessions(BaseAuthSessions):
55 SESSION_TYPE = 'ext:database'
56
57 def get_count(self):
58 return DbSession.query().count()
59
60 def get_expired_count(self, older_than_seconds=None):
61 expiry_date = self._seconds_to_date(older_than_seconds)
62 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
63
64 def clean_sessions(self, older_than_seconds=None):
65 expiry_date = self._seconds_to_date(older_than_seconds)
66 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
67 Session().commit()
68
69
70 class FileAuthSessions(BaseAuthSessions):
71 SESSION_TYPE = 'file sessions'
72
73 def get_count(self):
74 return 'NOT AVAILABLE'
75
76 def get_expired_count(self):
77 return self.get_count()
78
79 def clean_sessions(self, older_than_seconds=None):
80 data_dir = self.config.get('beaker.session.data_dir')
81 raise CleanupCommand(
82 'Please execute this command: '
83 '`find . -mtime +60 -exec rm {{}} \;` inside {} directory'.format(
84 data_dir))
85
86
87 class MemcachedAuthSessions(BaseAuthSessions):
88 SESSION_TYPE = 'ext:memcached'
89
90 def get_count(self):
91 return 'NOT AVAILABLE'
92
93 def get_expired_count(self):
94 return self.get_count()
95
96 def clean_sessions(self, older_than_seconds=None):
97 raise CleanupCommand('Cleanup for this session type not yet available')
98
99
100 class MemoryAuthSessions(BaseAuthSessions):
101 SESSION_TYPE = 'memory'
102
103 def get_count(self):
104 return 'NOT AVAILABLE'
105
106 def get_expired_count(self):
107 return self.get_count()
108
109 def clean_sessions(self, older_than_seconds=None):
110 raise CleanupCommand('Cleanup for this session type not yet available')
111
112
113 def get_session_handler(session_type):
114 types = {
115 'file': FileAuthSessions,
116 'ext:memcached': MemcachedAuthSessions,
117 'ext:database': DbAuthSessions,
118 'memory': MemoryAuthSessions
119 }
120
121 try:
122 return types[session_type]
123 except KeyError:
124 raise ValueError(
125 'This type {} is not supported'.format(session_type))
@@ -0,0 +1,60 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('User Sessions Configuration')}</h3>
4 </div>
5 <div class="panel-body">
6 <%
7 elems = [
8 (_('Session type'), c.session_model.SESSION_TYPE, ''),
9 (_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
10
11 (_('Total sessions'), c.session_count, ''),
12 (_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),
13
14 ]
15 %>
16 <dl class="dl-horizontal settings">
17 %for dt, dd, tt in elems:
18 <dt>${dt}:</dt>
19 <dd title="${tt}">${dd}</dd>
20 %endfor
21 </dl>
22 </div>
23 </div>
24
25
26 <div class="panel panel-warning">
27 <div class="panel-heading">
28 <h3 class="panel-title">${_('Cleanup Old Sessions')}</h3>
29 </div>
30 <div class="panel-body">
31 ${h.secure_form(h.url('admin_settings_sessions_cleanup'), method='post')}
32
33 <div style="margin: 0 0 20px 0" class="fake-space">
34 ${_('Cleanup all sessions that were not active during choosen time frame')} <br/>
35 ${_('Picking All will log-out all users in the system, and each user will be required to log in again.')}
36 </div>
37 <select id="expire_days" name="expire_days">
38 % for n in [60, 90, 30, 7, 0]:
39 <option value="${n}">${'{} days'.format(n) if n != 0 else 'All'}</option>
40 % endfor
41 </select>
42 <button class="btn btn-small" type="submit"
43 onclick="return confirm('${_('Confirm to cleanup user sessions')}');">
44 ${_('Cleanup sessions')}
45 </button>
46 ${h.end_form()}
47 </div>
48 </div>
49
50
51 <script type="text/javascript">
52 $(document).ready(function() {
53 $('#expire_days').select2({
54 containerCssClass: 'drop-menu',
55 dropdownCssClass: 'drop-menu-dropdown',
56 dropdownAutoWidth: true,
57 minimumResultsForSearch: -1
58 });
59 });
60 </script> No newline at end of file
@@ -83,8 +83,10 b' class NavigationRegistry(object):'
83 83 NavEntry('integrations', _('Integrations'),
84 84 'global_integrations_home', pyramid=True),
85 85 NavEntry('system', _('System Info'), 'admin_settings_system'),
86 NavEntry('session', _('User Sessions'), 'admin_settings_sessions'),
86 87 NavEntry('open_source', _('Open Source Licenses'),
87 88 'admin_settings_open_source', pyramid=True),
89
88 90 # TODO: marcink: we disable supervisor now until the supervisor stats
89 91 # page is fixed in the nix configuration
90 92 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
@@ -509,6 +509,12 b' def make_map(config):'
509 509 m.connect('admin_settings_system_update', '/settings/system/updates',
510 510 action='settings_system_update', conditions={'method': ['GET']})
511 511
512 m.connect('admin_settings_sessions', '/settings/sessions',
513 action='settings_sessions', conditions={'method': ['GET']})
514
515 m.connect('admin_settings_sessions_cleanup', '/settings/sessions/cleanup',
516 action='settings_sessions_cleanup', conditions={'method': ['POST']})
517
512 518 m.connect('admin_settings_supervisor', '/settings/supervisor',
513 519 action='settings_supervisor', conditions={'method': ['GET']})
514 520 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
@@ -50,6 +50,8 b' from rhodecode.lib.utils2 import ('
50 50 from rhodecode.lib.compat import OrderedDict
51 51 from rhodecode.lib.ext_json import json
52 52 from rhodecode.lib.utils import jsonify
53 from rhodecode.lib import system_info
54 from rhodecode.lib import user_sessions
53 55
54 56 from rhodecode.model.db import RhodeCodeUi, Repository
55 57 from rhodecode.model.forms import ApplicationSettingsForm, \
@@ -691,6 +693,52 b' class SettingsController(BaseController)'
691 693 return render('admin/settings/settings_system_update.mako')
692 694
693 695 @HasPermissionAllDecorator('hg.admin')
696 def settings_sessions(self):
697 # url('admin_settings_sessions')
698
699 c.active = 'sessions'
700 c.cleanup_older_days = 60
701 older_than_seconds = 24 * 60 * 60 * 24 * c.cleanup_older_days
702
703 config = system_info.rhodecode_config().get_value()['value']['config']
704 c.session_model = user_sessions.get_session_handler(
705 config.get('beaker.session.type', 'memory'))(config)
706
707 c.session_conf = c.session_model.config
708 c.session_count = c.session_model.get_count()
709 c.session_expired_count = c.session_model.get_expired_count(
710 older_than_seconds)
711
712 return render('admin/settings/settings.mako')
713
714 @HasPermissionAllDecorator('hg.admin')
715 def settings_sessions_cleanup(self):
716 # url('admin_settings_sessions_update')
717
718 expire_days = safe_int(request.POST.get('expire_days'))
719
720 if expire_days is None:
721 expire_days = 60
722
723 older_than_seconds = 24 * 60 * 60 * 24 * expire_days
724
725 config = system_info.rhodecode_config().get_value()['value']['config']
726 session_model = user_sessions.get_session_handler(
727 config.get('beaker.session.type', 'memory'))(config)
728
729 try:
730 session_model.clean_sessions(
731 older_than_seconds=older_than_seconds)
732 h.flash(_('Cleaned up old sessions'), category='success')
733 except user_sessions.CleanupCommand as msg:
734 h.flash(msg, category='warning')
735 except Exception as e:
736 log.exception('Failed session cleanup')
737 h.flash(_('Failed to cleanup up old sessions'), category='error')
738
739 return redirect(url('admin_settings_sessions'))
740
741 @HasPermissionAllDecorator('hg.admin')
694 742 def settings_supervisor(self):
695 743 c.rhodecode_ini = rhodecode.CONFIG
696 744 c.active = 'supervisor'
@@ -64,6 +64,9 b' class SysInfoRes(object):'
64 64 'human_value': self.human_value,
65 65 }
66 66
67 def get_value(self):
68 return self.__json__()
69
67 70 def __str__(self):
68 71 return '<SysInfoRes({})>'.format(self.__json__())
69 72
@@ -3812,7 +3812,12 b' class DbSession(Base, BaseModel):'
3812 3812 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3813 3813 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3814 3814 )
3815
3816 def __repr__(self):
3817 return '<DB:DbSession({})>'.format(self.id)
3818
3819 id = Column('id', Integer())
3815 3820 namespace = Column('namespace', String(255), primary_key=True)
3816 3821 accessed = Column('accessed', DateTime, nullable=False)
3817 3822 created = Column('created', DateTime, nullable=False)
3818 data = Column('data', PickleType, nullable=False) No newline at end of file
3823 data = Column('data', PickleType, nullable=False)
@@ -20,7 +20,7 b''
20 20 (_('SMTP auth'), c.rhodecode_ini.get('smtp_auth'), ''),
21 21 ]
22 22 %>
23 <dl class="dl-horizontal">
23 <dl class="dl-horizontal settings">
24 24 %for dt, dd, tt in elems:
25 25 <dt >${dt}:</dt>
26 26 <dd title="${tt}">${dd}</dd>
General Comments 0
You need to be logged in to leave comments. Login now