You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

417 rivejä
12 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. """
  5. Boot session from cache or build
  6. Session bootstraps info needed by common client side activities including
  7. permission, homepage, default variables, system defaults etc
  8. """
  9. import frappe, json
  10. from frappe import _
  11. import frappe.utils
  12. from frappe.utils import cint, cstr, get_assets_json
  13. import frappe.model.meta
  14. import frappe.defaults
  15. import frappe.translate
  16. import redis
  17. from six.moves.urllib.parse import unquote
  18. from six import text_type
  19. from frappe.cache_manager import clear_user_cache
  20. @frappe.whitelist(allow_guest=True)
  21. def clear(user=None):
  22. frappe.local.session_obj.update(force=True)
  23. frappe.local.db.commit()
  24. clear_user_cache(frappe.session.user)
  25. frappe.response['message'] = _("Cache Cleared")
  26. def clear_sessions(user=None, keep_current=False, device=None, force=False):
  27. '''Clear other sessions of the current user. Called at login / logout
  28. :param user: user name (default: current user)
  29. :param keep_current: keep current session (default: false)
  30. :param device: delete sessions of this device (default: desktop, mobile)
  31. :param force: triggered by the user (default false)
  32. '''
  33. reason = "Logged In From Another Session"
  34. if force:
  35. reason = "Force Logged out by the user"
  36. for sid in get_sessions_to_clear(user, keep_current, device):
  37. delete_session(sid, reason=reason)
  38. def get_sessions_to_clear(user=None, keep_current=False, device=None):
  39. '''Returns sessions of the current user. Called at login / logout
  40. :param user: user name (default: current user)
  41. :param keep_current: keep current session (default: false)
  42. :param device: delete sessions of this device (default: desktop, mobile)
  43. '''
  44. if not user:
  45. user = frappe.session.user
  46. if not device:
  47. device = ("desktop", "mobile")
  48. if not isinstance(device, (tuple, list)):
  49. device = (device,)
  50. offset = 0
  51. if user == frappe.session.user:
  52. simultaneous_sessions = frappe.db.get_value('User', user, 'simultaneous_sessions') or 1
  53. offset = simultaneous_sessions - 1
  54. condition = ''
  55. if keep_current:
  56. condition = ' AND sid != {0}'.format(frappe.db.escape(frappe.session.sid))
  57. return frappe.db.sql_list("""
  58. SELECT `sid` FROM `tabSessions`
  59. WHERE user=%(user)s
  60. AND device in %(device)s
  61. {condition}
  62. ORDER BY `lastupdate` DESC
  63. LIMIT 100 OFFSET {offset}""".format(condition=condition, offset=offset),
  64. {"user": user, "device": device})
  65. def delete_session(sid=None, user=None, reason="Session Expired"):
  66. from frappe.core.doctype.activity_log.feed import logout_feed
  67. frappe.cache().hdel("session", sid)
  68. frappe.cache().hdel("last_db_session_update", sid)
  69. if sid and not user:
  70. user_details = frappe.db.sql("""select user from tabSessions where sid=%s""", sid, as_dict=True)
  71. if user_details: user = user_details[0].get("user")
  72. logout_feed(user, reason)
  73. frappe.db.sql("""delete from tabSessions where sid=%s""", sid)
  74. frappe.db.commit()
  75. def clear_all_sessions(reason=None):
  76. """This effectively logs out all users"""
  77. frappe.only_for("Administrator")
  78. if not reason: reason = "Deleted All Active Session"
  79. for sid in frappe.db.sql_list("select sid from `tabSessions`"):
  80. delete_session(sid, reason=reason)
  81. def get_expired_sessions():
  82. '''Returns list of expired sessions'''
  83. expired = []
  84. for device in ("desktop", "mobile"):
  85. expired += frappe.db.sql_list("""SELECT `sid`
  86. FROM `tabSessions`
  87. WHERE (NOW() - `lastupdate`) > %s
  88. AND device = %s""", (get_expiry_period_for_query(device), device))
  89. return expired
  90. def clear_expired_sessions():
  91. """This function is meant to be called from scheduler"""
  92. for sid in get_expired_sessions():
  93. delete_session(sid, reason="Session Expired")
  94. def get():
  95. """get session boot info"""
  96. from frappe.boot import get_bootinfo, get_unseen_notes
  97. from frappe.utils.change_log import get_change_log
  98. bootinfo = None
  99. if not getattr(frappe.conf,'disable_session_cache', None):
  100. # check if cache exists
  101. bootinfo = frappe.cache().hget("bootinfo", frappe.session.user)
  102. if bootinfo:
  103. bootinfo['from_cache'] = 1
  104. bootinfo["user"]["recent"] = json.dumps(\
  105. frappe.cache().hget("user_recent", frappe.session.user))
  106. if not bootinfo:
  107. # if not create it
  108. bootinfo = get_bootinfo()
  109. frappe.cache().hset("bootinfo", frappe.session.user, bootinfo)
  110. try:
  111. frappe.cache().ping()
  112. except redis.exceptions.ConnectionError:
  113. message = _("Redis cache server not running. Please contact Administrator / Tech support")
  114. if 'messages' in bootinfo:
  115. bootinfo['messages'].append(message)
  116. else:
  117. bootinfo['messages'] = [message]
  118. # check only when clear cache is done, and don't cache this
  119. if frappe.local.request:
  120. bootinfo["change_log"] = get_change_log()
  121. bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")
  122. if not bootinfo["metadata_version"]:
  123. bootinfo["metadata_version"] = frappe.reset_metadata_version()
  124. bootinfo.notes = get_unseen_notes()
  125. bootinfo.assets_json = get_assets_json()
  126. for hook in frappe.get_hooks("extend_bootinfo"):
  127. frappe.get_attr(hook)(bootinfo=bootinfo)
  128. bootinfo["lang"] = frappe.translate.get_user_lang()
  129. bootinfo["disable_async"] = frappe.conf.disable_async
  130. bootinfo["setup_complete"] = cint(frappe.db.get_single_value('System Settings', 'setup_complete'))
  131. bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup'))
  132. return bootinfo
  133. def get_csrf_token():
  134. if not frappe.local.session.data.csrf_token:
  135. generate_csrf_token()
  136. return frappe.local.session.data.csrf_token
  137. def generate_csrf_token():
  138. frappe.local.session.data.csrf_token = frappe.generate_hash()
  139. frappe.local.session_obj.update(force=True)
  140. class Session:
  141. def __init__(self, user, resume=False, full_name=None, user_type=None):
  142. self.sid = cstr(frappe.form_dict.get('sid') or
  143. unquote(frappe.request.cookies.get('sid', 'Guest')))
  144. self.user = user
  145. self.device = frappe.form_dict.get("device") or "desktop"
  146. self.user_type = user_type
  147. self.full_name = full_name
  148. self.data = frappe._dict({'data': frappe._dict({})})
  149. self.time_diff = None
  150. # set local session
  151. frappe.local.session = self.data
  152. if resume:
  153. self.resume()
  154. else:
  155. if self.user:
  156. self.start()
  157. def start(self):
  158. """start a new session"""
  159. # generate sid
  160. if self.user=='Guest':
  161. sid = 'Guest'
  162. else:
  163. sid = frappe.generate_hash()
  164. self.data.user = self.user
  165. self.data.sid = sid
  166. self.data.data.user = self.user
  167. self.data.data.session_ip = frappe.local.request_ip
  168. if self.user != "Guest":
  169. self.data.data.update({
  170. "last_updated": frappe.utils.now(),
  171. "session_expiry": get_expiry_period(self.device),
  172. "full_name": self.full_name,
  173. "user_type": self.user_type,
  174. "device": self.device,
  175. "session_country": get_geo_ip_country(frappe.local.request_ip) if frappe.local.request_ip else None,
  176. })
  177. # insert session
  178. if self.user!="Guest":
  179. self.insert_session_record()
  180. # update user
  181. user = frappe.get_doc("User", self.data['user'])
  182. frappe.db.sql("""UPDATE `tabUser`
  183. SET
  184. last_login = %(now)s,
  185. last_ip = %(ip)s,
  186. last_active = %(now)s
  187. WHERE name=%(name)s""", {
  188. 'now': frappe.utils.now(),
  189. 'ip': frappe.local.request_ip,
  190. 'name': self.data['user']
  191. })
  192. user.run_notifications("before_change")
  193. user.run_notifications("on_update")
  194. frappe.db.commit()
  195. def insert_session_record(self):
  196. frappe.db.sql("""insert into `tabSessions`
  197. (`sessiondata`, `user`, `lastupdate`, `sid`, `status`, `device`)
  198. values (%s , %s, NOW(), %s, 'Active', %s)""",
  199. (str(self.data['data']), self.data['user'], self.data['sid'], self.device))
  200. # also add to memcache
  201. frappe.cache().hset("session", self.data.sid, self.data)
  202. def resume(self):
  203. """non-login request: load a session"""
  204. import frappe
  205. from frappe.auth import validate_ip_address
  206. data = self.get_session_record()
  207. if data:
  208. # set language
  209. self.data.update({'data': data, 'user':data.user, 'sid': self.sid})
  210. self.user = data.user
  211. validate_ip_address(self.user)
  212. self.device = data.device
  213. else:
  214. self.start_as_guest()
  215. if self.sid != "Guest":
  216. frappe.local.user_lang = frappe.translate.get_user_lang(self.data.user)
  217. frappe.local.lang = frappe.local.user_lang
  218. def get_session_record(self):
  219. """get session record, or return the standard Guest Record"""
  220. from frappe.auth import clear_cookies
  221. r = self.get_session_data()
  222. if not r:
  223. frappe.response["session_expired"] = 1
  224. clear_cookies()
  225. self.sid = "Guest"
  226. r = self.get_session_data()
  227. return r
  228. def get_session_data(self):
  229. if self.sid=="Guest":
  230. return frappe._dict({"user":"Guest"})
  231. data = self.get_session_data_from_cache()
  232. if not data:
  233. data = self.get_session_data_from_db()
  234. return data
  235. def get_session_data_from_cache(self):
  236. data = frappe.cache().hget("session", self.sid)
  237. if data:
  238. data = frappe._dict(data)
  239. session_data = data.get("data", {})
  240. # set user for correct timezone
  241. self.time_diff = frappe.utils.time_diff_in_seconds(frappe.utils.now(),
  242. session_data.get("last_updated"))
  243. expiry = get_expiry_in_seconds(session_data.get("session_expiry"))
  244. if self.time_diff > expiry:
  245. self._delete_session()
  246. data = None
  247. return data and data.data
  248. def get_session_data_from_db(self):
  249. self.device = frappe.db.sql('SELECT `device` FROM `tabSessions` WHERE `sid`=%s', self.sid)
  250. self.device = self.device and self.device[0][0] or 'desktop'
  251. rec = frappe.db.sql("""
  252. SELECT `user`, `sessiondata`
  253. FROM `tabSessions` WHERE `sid`=%s AND
  254. (NOW() - lastupdate) < %s
  255. """, (self.sid, get_expiry_period_for_query(self.device)))
  256. if rec:
  257. data = frappe._dict(frappe.safe_eval(rec and rec[0][1] or '{}'))
  258. data.user = rec[0][0]
  259. else:
  260. self._delete_session()
  261. data = None
  262. return data
  263. def _delete_session(self):
  264. delete_session(self.sid, reason="Session Expired")
  265. def start_as_guest(self):
  266. """all guests share the same 'Guest' session"""
  267. self.user = "Guest"
  268. self.start()
  269. def update(self, force=False):
  270. """extend session expiry"""
  271. if (frappe.session['user'] == "Guest" or frappe.form_dict.cmd=="logout"):
  272. return
  273. now = frappe.utils.now()
  274. self.data['data']['last_updated'] = now
  275. self.data['data']['lang'] = text_type(frappe.lang)
  276. # update session in db
  277. last_updated = frappe.cache().hget("last_db_session_update", self.sid)
  278. time_diff = frappe.utils.time_diff_in_seconds(now, last_updated) if last_updated else None
  279. # database persistence is secondary, don't update it too often
  280. updated_in_db = False
  281. if force or (time_diff==None) or (time_diff > 600):
  282. # update sessions table
  283. frappe.db.sql("""update `tabSessions` set sessiondata=%s,
  284. lastupdate=NOW() where sid=%s""" , (str(self.data['data']),
  285. self.data['sid']))
  286. # update last active in user table
  287. frappe.db.sql("""update `tabUser` set last_active=%(now)s where name=%(name)s""", {
  288. "now": now,
  289. "name": frappe.session.user
  290. })
  291. frappe.db.commit()
  292. frappe.cache().hset("last_db_session_update", self.sid, now)
  293. updated_in_db = True
  294. # set in memcache
  295. frappe.cache().hset("session", self.sid, self.data)
  296. return updated_in_db
  297. def get_expiry_period_for_query(device=None):
  298. if frappe.db.db_type == 'postgres':
  299. return get_expiry_period(device)
  300. else:
  301. return get_expiry_in_seconds(device=device)
  302. def get_expiry_in_seconds(expiry=None, device=None):
  303. if not expiry:
  304. expiry = get_expiry_period(device)
  305. parts = expiry.split(":")
  306. return (cint(parts[0]) * 3600) + (cint(parts[1]) * 60) + cint(parts[2])
  307. def get_expiry_period(device="desktop"):
  308. if device=="mobile":
  309. key = "session_expiry_mobile"
  310. default = "720:00:00"
  311. else:
  312. key = "session_expiry"
  313. default = "06:00:00"
  314. exp_sec = frappe.defaults.get_global_default(key) or default
  315. # incase seconds is missing
  316. if len(exp_sec.split(':')) == 2:
  317. exp_sec = exp_sec + ':00'
  318. return exp_sec
  319. def get_geo_from_ip(ip_addr):
  320. try:
  321. from geolite2 import geolite2
  322. with geolite2 as f:
  323. reader = f.reader()
  324. data = reader.get(ip_addr)
  325. return frappe._dict(data)
  326. except ImportError:
  327. return
  328. except ValueError:
  329. return
  330. except TypeError:
  331. return
  332. def get_geo_ip_country(ip_addr):
  333. match = get_geo_from_ip(ip_addr)
  334. if match:
  335. return match.country