[feature] allow multiple sessions per user

This commit is contained in:
Rushabh Mehta 2016-08-10 15:36:32 +05:30
parent e25d9dd9ec
commit f91977f7da
10 changed files with 1568 additions and 1478 deletions

View file

@ -166,7 +166,7 @@ class LoginManager:
def clear_active_sessions(self):
"""Clear other sessions of the current user if `deny_multiple_sessions` is not set"""
if not (frappe.conf.get("deny_multiple_sessions") or cint(frappe.db.get_system_setting('deny_multiple_sessions'))):
if not (cint(frappe.conf.get("deny_multiple_sessions")) or cint(frappe.db.get_system_setting('deny_multiple_sessions'))):
return
if frappe.session.user != "Guest":

View file

@ -0,0 +1 @@
- Ability to add multiple sessions to users. Edit user record and edit "Simultaneous Sessions"

View file

@ -102,6 +102,59 @@ class TestUser(unittest.TestCase):
# Clear the user limit
clear_limit('users')
def test_user_limit_for_site_with_simultaneous_sessions(self):
from frappe.core.doctype.user.user import get_total_users
clear_limit('users')
# make sure this user counts
user = frappe.get_doc('User', 'test@example.com')
user.add_roles('Website Manager')
user.save()
update_limits({'users': get_total_users()})
user.simultaneous_sessions = user.simultaneous_sessions + 1
self.assertRaises(MaxUsersReachedError, user.save)
# Clear the user limit
clear_limit('users')
# def test_deny_multiple_sessions(self):
# clear_limit('users')
#
# # allow one session
# user = frappe.get_doc('User', 'test@example.com')
# user.simultaneous_sessions = 1
# user.new_password = 'testpassword'
# user.save()
#
# def test_request(conn):
# value = conn.get_value('User', 'first_name', {'name': 'test@example.com'})
# self.assertTrue('first_name' in value)
#
# from frappe.frappeclient import FrappeClient
# update_site_config('deny_multiple_sessions', 0)
#
# print 'conn1'
# conn1 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
# test_request(conn1)
#
# print 'conn2'
# conn2 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
# test_request(conn2)
#
# update_site_config('deny_multiple_sessions', 1)
#
# print 'conn3'
#
# conn3 = FrappeClient(get_url(), "test@example.com", "testpassword", verify=False)
# test_request(conn3)
#
# # first connection should fail
# test_request(conn1)
def test_site_expiry(self):
update_limits({'expiry': add_to_date(today(), days=-1)})
frappe.local.conf = _dict(frappe.get_site_config())

File diff suppressed because it is too large Load diff

View file

@ -58,11 +58,18 @@ class User(Document):
self.remove_all_roles_for_guest()
self.validate_username()
self.remove_disabled_roles()
self.validate_user_limit()
if self.language == "Loading...":
self.language = None
def on_update(self):
# clear new password
self.validate_user_limit()
self.share_with_self()
clear_notifications(user=self.name)
frappe.clear_cache(user=self.name)
self.send_password_notification(self.__new_password)
def check_demo(self):
if frappe.session.user == 'demo@erpnext.com':
frappe.throw('Cannot change user details in demo. Please signup for a new account at https://erpnext.com', title='Not Allowed')
@ -122,13 +129,6 @@ class User(Document):
else:
self.user_type = 'Website User'
def on_update(self):
# clear new password
self.share_with_self()
clear_notifications(user=self.name)
frappe.clear_cache(user=self.name)
self.send_password_notification(self.__new_password)
def share_with_self(self):
if self.user_type=="System User":
frappe.share.add(self.doctype, self.name, self.name, share=1,
@ -576,9 +576,11 @@ def user_query(doctype, txt, searchfield, start, page_len, filters):
key=searchfield, mcond=get_match_cond(doctype)),
tuple(list(STANDARD_USERS) + [txt, txt, txt, txt, start, page_len]))
def get_total_users(exclude_users=None):
def get_total_users():
"""Returns total no. of system users"""
return len(get_system_users(exclude_users=exclude_users))
return frappe.db.sql('''select sum(simultaneous_sessions) from `tabUser`
where enabled=1 and user_type="System User"
and name not in ({})'''.format(", ".join(["%s"]*len(STANDARD_USERS))), STANDARD_USERS)[0][0]
def get_system_users(exclude_users=None):
if not exclude_users:

View file

@ -291,6 +291,9 @@ def update_site_config(key, value, validate=True):
with open(get_site_config_path(), "w") as f:
f.write(json.dumps(site_config, indent=1, sort_keys=True))
if frappe.local.conf:
frappe.local.conf[key] = value
def get_site_config_path():
return os.path.join(frappe.local.site_path, "site_config.json")

View file

@ -149,7 +149,7 @@ def update_limits(limits_dict):
limits = get_limits()
limits.update(limits_dict)
update_site_config("limits", limits, validate=False)
frappe.conf.limits = limits
frappe.local.conf.limits = limits
def clear_limit(key):
'''Remove a limit option from site_config'''

View file

@ -57,15 +57,21 @@ def clear_sessions(user=None, keep_current=False, device=None):
if not device:
device = frappe.session.data.device or "desktop"
for sid in frappe.db.sql_list("""select sid from tabSessions where user=%s and device=%s""", (user, device)):
if keep_current and frappe.session.sid==sid:
continue
else:
delete_session(sid)
simultaneous_sessions = frappe.db.get_value('User', user, 'simultaneous_sessions') or 1
condition = ''
if keep_current:
condition = ' and sid != "{0}"'.format(frappe.session.sid)
limit = simultaneous_sessions - 1
for i, sid in enumerate(frappe.db.sql_list("""select sid from tabSessions
where user=%s and device=%s {condition}
order by lastupdate desc limit {limit}, 100""".format(condition=condition, limit=limit),
(user, device))):
delete_session(sid)
def delete_session(sid=None, user=None):
if not user:
user = hasattr(frappe.local, "session") and frappe.session.user or "Guest"
frappe.cache().hdel("session", sid)
frappe.cache().hdel("last_db_session_update", sid)
frappe.db.sql("""delete from tabSessions where sid=%s""", sid)
@ -298,7 +304,7 @@ class Session:
return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2])
def delete_session(self):
delete_session(self.sid, user=self.user)
delete_session(self.sid)
def start_as_guest(self):
"""all guests share the same 'Guest' session"""

View file

@ -12,15 +12,7 @@ class TestAPI(unittest.TestCase):
frappe.db.sql('delete from `tabToDo` where description like "Test API%"')
frappe.db.commit()
host = get_url()
if not host.startswith('http'):
host = 'http://' + host
if not host.endswith(':8000'):
host = host + ':8000'
server = FrappeClient(host, "Administrator", "admin", verify=False)
server = FrappeClient(get_url(), "Administrator", "admin", verify=False)
server.insert_many([
{"doctype": "ToDo", "description": "Test API 1"},

View file

@ -570,7 +570,7 @@ def get_url(uri=None, full_address=False):
if not host_name:
if hasattr(frappe.local, "request") and frappe.local.request and frappe.local.request.host:
protocol = 'https' == frappe.get_request_header('X-Forwarded-Proto', "") and 'https://' or 'http://'
protocol = 'https://' if 'https' == frappe.get_request_header('X-Forwarded-Proto', "") else 'http://'
host_name = protocol + frappe.local.request.host
elif frappe.local.site:
@ -589,17 +589,22 @@ def get_url(uri=None, full_address=False):
else:
host_name = frappe.db.get_value("Website Settings", "Website Settings",
"subdomain")
if host_name and "http" not in host_name:
host_name = "http://" + host_name
if not host_name:
host_name = "http://localhost"
if host_name and not (host_name.startswith("http://") or host_name.startswith("https://")):
host_name = "http://" + host_name
if not uri and full_address:
uri = frappe.get_request_header("REQUEST_URI", "")
url = urllib.basejoin(host_name, uri) if uri else host_name
# add port if not added
if frappe.conf.webserver_port and not url.rsplit(':', 1)[-1].isdigit():
url = url + ':' + str(frappe.conf.webserver_port)
return url
def get_host_name():