[feature] allow multiple sessions per user
This commit is contained in:
parent
e25d9dd9ec
commit
f91977f7da
10 changed files with 1568 additions and 1478 deletions
|
|
@ -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":
|
||||
|
|
|
|||
1
frappe/change_log/v7/v7_0_18
Normal file
1
frappe/change_log/v7/v7_0_18
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Ability to add multiple sessions to users. Edit user record and edit "Simultaneous Sessions"
|
||||
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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'''
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue