├── .gitignore ├── MANIFEST.in ├── README.md ├── license.txt ├── persona ├── __init__.py ├── api │ └── user.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── hooks.py ├── modules.txt ├── patches.txt ├── persona │ └── __init__.py ├── public │ └── js │ │ └── user.js └── templates │ ├── __init__.py │ └── pages │ └── __init__.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | persona/docs/current -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include persona *.css 8 | recursive-include persona *.csv 9 | recursive-include persona *.html 10 | recursive-include persona *.ico 11 | recursive-include persona *.js 12 | recursive-include persona *.json 13 | recursive-include persona *.md 14 | recursive-include persona *.png 15 | recursive-include persona *.py 16 | recursive-include persona *.svg 17 | recursive-include persona *.txt 18 | recursive-exclude persona *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Persona - ERPNext custom app 2 | 3 | ###Announcement 13 August 2024 4 | This app will not be maintained anymore, since ERPNext v15 already has a similar feature. Thanks supporting this project. 5 | 6 | 7 | Persona is a custom ERPNext app that Allows administrator or a system manager to impersonate another user. Inspired by [Administrator should be able to impersonate any user #17615](https://github.com/frappe/erpnext/issues/17615). 8 | 9 | 1. Only "Administrator" or "System Manager" roles can impersonate another user. 10 | 2. Adds a button "Impersonate" to User form. 11 | 3. Will generate 2 entries in activity log. 12 | 1. One will show that the Administrator/System Manager who is impersonating a user. 13 | 2. A second entry shows that the impersonated user logging in. 14 | 4. As a safeguard, we don't allow a system manager to impersonate another system manager. 15 | 16 | 17 | ### Installation 18 | 19 | ``` 20 | bench get-app https://github.com/iptelephony/persona 21 | bench --site sitename install-app persona 22 | bench migrate 23 | bench restart 24 | bench clear-cache 25 | ``` 26 | 27 | #### License 28 | 29 | MIT 30 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /persona/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '0.0.1' 3 | 4 | -------------------------------------------------------------------------------- /persona/api/user.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe.sessions import clear 3 | from frappe.core.doctype.activity_log.activity_log import add_authentication_log 4 | from frappe.utils import get_fullname 5 | import json 6 | 7 | def role_check(user, target): 8 | if user == target: # Don't allow impersonating self 9 | return False 10 | if user == "Administrator" or "System Manager" in frappe.get_roles(): 11 | is_privileged_user = target == "Administrator" or "System Manager" in frappe.get_roles(target) 12 | return not is_privileged_user 13 | return False 14 | 15 | 16 | 17 | @frappe.whitelist() 18 | def impersonate(user): 19 | # if frappe.session.user == "Administrator" or "System Manager" in frappe.get_roles(): 20 | if role_check(frappe.session.user, user): 21 | clear() 22 | message = "{a} impersonates {u}".format(a=get_fullname(frappe.session.user), u=user, operation="Impersonation") 23 | add_authentication_log(message, frappe.session.user) 24 | frappe.local.login_manager.login_as(user) 25 | # Create 2 entries in authentication log 26 | add_authentication_log("{u} logged in".format(u=user), user) 27 | return {"status": "Success", "message": frappe._("Impersonated ") + str(user)} 28 | 29 | return {"status": "Fail", "message": frappe._("Not allowed to impersonate ") + str(user)} 30 | 31 | -------------------------------------------------------------------------------- /persona/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iptelephony/persona/afaf93062062b16b752d79a2b72a38bf700c7e0a/persona/config/__init__.py -------------------------------------------------------------------------------- /persona/config/desktop.py: -------------------------------------------------------------------------------- 1 | from frappe import _ 2 | 3 | def get_data(): 4 | return [ 5 | { 6 | "module_name": "Persona", 7 | "color": "grey", 8 | "icon": "octicon octicon-file-directory", 9 | "type": "module", 10 | "label": _("Persona") 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /persona/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/persona" 6 | # docs_base_url = "https://[org_name].github.io/persona" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Persona" 12 | -------------------------------------------------------------------------------- /persona/hooks.py: -------------------------------------------------------------------------------- 1 | from . import __version__ as app_version 2 | 3 | app_name = "persona" 4 | app_title = "Persona" 5 | app_publisher = "Jireh Group" 6 | app_description = "Allows administrator to impersonate another user." 7 | app_icon = "octicon octicon-file-directory" 8 | app_color = "grey" 9 | app_email = "leo at jirehgroup dot asia" 10 | app_license = "MIT" 11 | 12 | # Includes in 13 | # ------------------ 14 | 15 | # include js, css files in header of desk.html 16 | # app_include_css = "/assets/persona/css/persona.css" 17 | # app_include_js = "/assets/persona/js/persona.js" 18 | 19 | # include js, css files in header of web template 20 | # web_include_css = "/assets/persona/css/persona.css" 21 | # web_include_js = "/assets/persona/js/persona.js" 22 | 23 | # include custom scss in every website theme (without file extension ".scss") 24 | # website_theme_scss = "persona/public/scss/website" 25 | 26 | # include js, css files in header of web form 27 | # webform_include_js = {"doctype": "public/js/doctype.js"} 28 | # webform_include_css = {"doctype": "public/css/doctype.css"} 29 | 30 | # include js in page 31 | # page_js = {"page" : "public/js/file.js"} 32 | 33 | # include js in doctype views 34 | # doctype_js = {"doctype" : "public/js/doctype.js"} 35 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 36 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 37 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 38 | doctype_js = {"User" : "public/js/user.js"} 39 | 40 | # Home Pages 41 | # ---------- 42 | 43 | # application home page (will override Website Settings) 44 | # home_page = "login" 45 | 46 | # website user home page (by Role) 47 | # role_home_page = { 48 | # "Role": "home_page" 49 | # } 50 | 51 | # Generators 52 | # ---------- 53 | 54 | # automatically create page for each record of this doctype 55 | # website_generators = ["Web Page"] 56 | 57 | # Installation 58 | # ------------ 59 | 60 | # before_install = "persona.install.before_install" 61 | # after_install = "persona.install.after_install" 62 | 63 | # Desk Notifications 64 | # ------------------ 65 | # See frappe.core.notifications.get_notification_config 66 | 67 | # notification_config = "persona.notifications.get_notification_config" 68 | 69 | # Permissions 70 | # ----------- 71 | # Permissions evaluated in scripted ways 72 | 73 | # permission_query_conditions = { 74 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 75 | # } 76 | # 77 | # has_permission = { 78 | # "Event": "frappe.desk.doctype.event.event.has_permission", 79 | # } 80 | 81 | # DocType Class 82 | # --------------- 83 | # Override standard doctype classes 84 | 85 | # override_doctype_class = { 86 | # "ToDo": "custom_app.overrides.CustomToDo" 87 | # } 88 | 89 | # Document Events 90 | # --------------- 91 | # Hook on document methods and events 92 | 93 | # doc_events = { 94 | # "*": { 95 | # "on_update": "method", 96 | # "on_cancel": "method", 97 | # "on_trash": "method" 98 | # } 99 | # } 100 | 101 | # Scheduled Tasks 102 | # --------------- 103 | 104 | # scheduler_events = { 105 | # "all": [ 106 | # "persona.tasks.all" 107 | # ], 108 | # "daily": [ 109 | # "persona.tasks.daily" 110 | # ], 111 | # "hourly": [ 112 | # "persona.tasks.hourly" 113 | # ], 114 | # "weekly": [ 115 | # "persona.tasks.weekly" 116 | # ] 117 | # "monthly": [ 118 | # "persona.tasks.monthly" 119 | # ] 120 | # } 121 | 122 | # Testing 123 | # ------- 124 | 125 | # before_tests = "persona.install.before_tests" 126 | 127 | # Overriding Methods 128 | # ------------------------------ 129 | # 130 | # override_whitelisted_methods = { 131 | # "frappe.desk.doctype.event.event.get_events": "persona.event.get_events" 132 | # } 133 | # 134 | # each overriding function accepts a `data` argument; 135 | # generated from the base implementation of the doctype dashboard, 136 | # along with any modifications made in other Frappe apps 137 | # override_doctype_dashboards = { 138 | # "Task": "persona.task.get_dashboard_data" 139 | # } 140 | 141 | # exempt linked doctypes from being automatically cancelled 142 | # 143 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 144 | 145 | 146 | # User Data Protection 147 | # -------------------- 148 | 149 | user_data_fields = [ 150 | { 151 | "doctype": "{doctype_1}", 152 | "filter_by": "{filter_by}", 153 | "redact_fields": ["{field_1}", "{field_2}"], 154 | "partial": 1, 155 | }, 156 | { 157 | "doctype": "{doctype_2}", 158 | "filter_by": "{filter_by}", 159 | "partial": 1, 160 | }, 161 | { 162 | "doctype": "{doctype_3}", 163 | "strict": False, 164 | }, 165 | { 166 | "doctype": "{doctype_4}" 167 | } 168 | ] 169 | 170 | # Authentication and authorization 171 | # -------------------------------- 172 | 173 | # auth_hooks = [ 174 | # "persona.auth.validate" 175 | # ] 176 | 177 | -------------------------------------------------------------------------------- /persona/modules.txt: -------------------------------------------------------------------------------- 1 | Persona -------------------------------------------------------------------------------- /persona/patches.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iptelephony/persona/afaf93062062b16b752d79a2b72a38bf700c7e0a/persona/patches.txt -------------------------------------------------------------------------------- /persona/persona/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iptelephony/persona/afaf93062062b16b752d79a2b72a38bf700c7e0a/persona/persona/__init__.py -------------------------------------------------------------------------------- /persona/public/js/user.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on('User', { 2 | refresh: function(frm, cdt, cdn) { 3 | 4 | 5 | function canImpersonate(user, target) { 6 | if((frappe.session.user === 'Administrator' || frappe.user.has_role("System Manager")) && frappe.session.user !== frm.doc.name) { 7 | return true; 8 | } 9 | 10 | return false; 11 | } 12 | 13 | if (canImpersonate(frappe.session.user, frm.doc.name)) { 14 | frm.add_custom_button(__("Impersonate"), function () { 15 | 16 | frappe.call({ 17 | method: 'persona.api.user.impersonate', 18 | args: { 19 | user: frm.doc.name, 20 | 21 | }, 22 | callback: function (r) { 23 | if (r.message.status === 'Success') { 24 | frappe.show_alert({message: r.message.message, indicator: 'green'}); 25 | location.reload(true); 26 | } else { 27 | frappe.throw(r.message.message); 28 | } 29 | } 30 | }) 31 | }); 32 | } 33 | 34 | }, 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /persona/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iptelephony/persona/afaf93062062b16b752d79a2b72a38bf700c7e0a/persona/templates/__init__.py -------------------------------------------------------------------------------- /persona/templates/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iptelephony/persona/afaf93062062b16b752d79a2b72a38bf700c7e0a/persona/templates/pages/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # frappe -- https://github.com/frappe/frappe is installed via 'bench init' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("requirements.txt") as f: 4 | install_requires = f.read().strip().split("\n") 5 | 6 | # get version from __version__ variable in persona/__init__.py 7 | from persona import __version__ as version 8 | 9 | setup( 10 | name="persona", 11 | version=version, 12 | description="Allows administrator to impersonate another user.", 13 | author="Jireh Group", 14 | author_email="leo at jirehgroup dot asia", 15 | packages=find_packages(), 16 | zip_safe=False, 17 | include_package_data=True, 18 | install_requires=install_requires 19 | ) 20 | --------------------------------------------------------------------------------