* [minor] Authentication log for login & logout * [minor] added test cases for authentication logversion-14
@@ -15,6 +15,7 @@ from frappe.sessions import Session, clear_sessions, delete_session | |||||
from frappe.modules.patch_handler import check_session_stopped | from frappe.modules.patch_handler import check_session_stopped | ||||
from frappe.translate import get_lang_code | from frappe.translate import get_lang_code | ||||
from frappe.utils.password import check_password | from frappe.utils.password import check_password | ||||
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log | |||||
from urllib import quote | from urllib import quote | ||||
@@ -59,10 +60,6 @@ class HTTPRequest: | |||||
# check status | # check status | ||||
check_session_stopped() | check_session_stopped() | ||||
# run login triggers | |||||
if frappe.form_dict.get('cmd')=='login': | |||||
frappe.local.login_manager.run_trigger('on_session_creation') | |||||
def validate_csrf_token(self): | def validate_csrf_token(self): | ||||
if frappe.local.request and frappe.local.request.method=="POST": | if frappe.local.request and frappe.local.request.method=="POST": | ||||
if not frappe.local.session.data.csrf_token \ | if not frappe.local.session.data.csrf_token \ | ||||
@@ -103,6 +100,9 @@ class LoginManager: | |||||
if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | if frappe.local.form_dict.get('cmd')=='login' or frappe.local.request.path=="/api/method/login": | ||||
self.login() | self.login() | ||||
self.resume = False | self.resume = False | ||||
# run login triggers | |||||
self.run_trigger('on_session_creation') | |||||
else: | else: | ||||
try: | try: | ||||
self.resume = True | self.resume = True | ||||
@@ -183,7 +183,7 @@ class LoginManager: | |||||
if not (user and pwd): | if not (user and pwd): | ||||
user, pwd = frappe.form_dict.get('usr'), frappe.form_dict.get('pwd') | user, pwd = frappe.form_dict.get('usr'), frappe.form_dict.get('pwd') | ||||
if not (user and pwd): | if not (user and pwd): | ||||
self.fail('Incomplete login details') | |||||
self.fail('Incomplete login details', user=user) | |||||
self.check_if_enabled(user) | self.check_if_enabled(user) | ||||
self.user = self.check_password(user, pwd) | self.user = self.check_password(user, pwd) | ||||
@@ -192,7 +192,7 @@ class LoginManager: | |||||
"""raise exception if user not enabled""" | """raise exception if user not enabled""" | ||||
if user=='Administrator': return | if user=='Administrator': return | ||||
if not cint(frappe.db.get_value('User', user, 'enabled')): | if not cint(frappe.db.get_value('User', user, 'enabled')): | ||||
self.fail('User disabled or missing') | |||||
self.fail('User disabled or missing', user=user) | |||||
def check_password(self, user, pwd): | def check_password(self, user, pwd): | ||||
"""check password""" | """check password""" | ||||
@@ -200,10 +200,12 @@ class LoginManager: | |||||
# returns user in correct case | # returns user in correct case | ||||
return check_password(user, pwd) | return check_password(user, pwd) | ||||
except frappe.AuthenticationError: | except frappe.AuthenticationError: | ||||
self.fail('Incorrect password') | |||||
self.fail('Incorrect password', user=user) | |||||
def fail(self, message): | |||||
def fail(self, message, user="NA"): | |||||
frappe.local.response['message'] = message | frappe.local.response['message'] = message | ||||
add_authentication_log(message, user, status="Failed") | |||||
frappe.db.commit() | |||||
raise frappe.AuthenticationError | raise frappe.AuthenticationError | ||||
def run_trigger(self, event='on_login'): | def run_trigger(self, event='on_login'): | ||||
@@ -0,0 +1,8 @@ | |||||
// Copyright (c) 2016, Frappe Technologies and contributors | |||||
// For license information, please see license.txt | |||||
frappe.ui.form.on('Authentication Log', { | |||||
refresh: function(frm) { | |||||
} | |||||
}); |
@@ -0,0 +1,341 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 0, | |||||
"allow_rename": 0, | |||||
"beta": 0, | |||||
"creation": "2017-01-23 16:56:25.875531", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"document_type": "Setup", | |||||
"editable_grid": 1, | |||||
"engine": "InnoDB", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "user_details", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "User Details", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "user", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "User", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "User", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 1, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "full_name", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | |||||
"label": "Full Name", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "column_break_8", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "date", | |||||
"fieldtype": "Datetime", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"label": "Date", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 1, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "section_break_6", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "subject", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | |||||
"label": "Subject", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 1, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "column_break_2", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"in_standard_filter": 0, | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "operation", | |||||
"fieldtype": "Select", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | |||||
"label": "Operation", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "\nLogin\nLogout", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"columns": 0, | |||||
"fieldname": "status", | |||||
"fieldtype": "Select", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"ignore_xss_filter": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"in_standard_filter": 0, | |||||
"label": "Status", | |||||
"length": 0, | |||||
"no_copy": 0, | |||||
"options": "\nSuccess\nFailed", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"print_hide_if_no_value": 0, | |||||
"read_only": 0, | |||||
"remember_last_selected_value": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
} | |||||
], | |||||
"hide_heading": 0, | |||||
"hide_toolbar": 0, | |||||
"idx": 0, | |||||
"image_view": 0, | |||||
"in_create": 1, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"max_attachments": 0, | |||||
"modified": "2017-01-23 18:10:35.785334", | |||||
"modified_by": "Administrator", | |||||
"module": "Core", | |||||
"name": "Authentication Log", | |||||
"name_case": "", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 0, | |||||
"delete": 0, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"is_custom": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "All", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 0 | |||||
} | |||||
], | |||||
"quick_entry": 0, | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"sort_field": "modified", | |||||
"sort_order": "DESC", | |||||
"title_field": "subject", | |||||
"track_seen": 0 | |||||
} |
@@ -0,0 +1,22 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors | |||||
# For license information, please see license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
from frappe.utils import get_fullname, now | |||||
from frappe.model.document import Document | |||||
class AuthenticationLog(Document): | |||||
def before_insert(self): | |||||
self.full_name = get_fullname(self.user) | |||||
self.date = now() | |||||
def add_authentication_log(subject, user, operation="Login", status="Success"): | |||||
frappe.get_doc({ | |||||
"doctype": "Authentication Log", | |||||
"user": user, | |||||
"status": status, | |||||
"subject": subject, | |||||
"operation": operation, | |||||
}).insert(ignore_permissions=True) |
@@ -0,0 +1,8 @@ | |||||
frappe.listview_settings['Authentication Log'] = { | |||||
get_indicator: function(doc) { | |||||
if(doc.operation == "Login" && doc.status == "Success") | |||||
return [__(doc.status), "green"]; | |||||
else if(doc.operation == "Login" && doc.status == "Failed") | |||||
return [__(doc.status), "red"]; | |||||
} | |||||
}; |
@@ -0,0 +1,48 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import unittest | |||||
# test_records = frappe.get_test_records('Authentication Log') | |||||
class TestAuthenticationLog(unittest.TestCase): | |||||
def test_authentication_log(self): | |||||
from frappe.auth import LoginManager, CookieManager | |||||
# test user login log | |||||
frappe.local.form_dict = { 'cmd': 'login' } | |||||
frappe.form_dict = { | |||||
'sid': 'Guest', | |||||
'pwd': 'admin', | |||||
'usr': 'Administrator' | |||||
} | |||||
frappe.local.cookie_manager = CookieManager() | |||||
frappe.local.login_manager = LoginManager() | |||||
auth_log = self.get_auth_log() | |||||
self.assertEquals(auth_log.status, 'Success') | |||||
# test user logout log | |||||
frappe.local.login_manager.logout() | |||||
auth_log = self.get_auth_log(operation='Logout') | |||||
self.assertEquals(auth_log.status, 'Success') | |||||
# test invalid login | |||||
frappe.form_dict.update({ 'pwd': 'password' }) | |||||
self.assertRaises(frappe.AuthenticationError, LoginManager) | |||||
auth_log = self.get_auth_log() | |||||
self.assertEquals(auth_log.status, 'Failed') | |||||
def get_auth_log(self, operation='Login'): | |||||
names = frappe.db.sql_list("""select name from `tabAuthentication Log` | |||||
where user='Administrator' and operation='{operation}' order by | |||||
creation desc""".format(operation=operation)) | |||||
name = names[0] | |||||
auth_log = frappe.get_doc('Authentication Log', name) | |||||
return auth_log |
@@ -9,6 +9,7 @@ from frappe.model.document import Document | |||||
from frappe.utils import get_fullname | from frappe.utils import get_fullname | ||||
from frappe import _ | from frappe import _ | ||||
from frappe.core.doctype.communication.comment import add_info_comment | from frappe.core.doctype.communication.comment import add_info_comment | ||||
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log | |||||
def update_feed(doc, method=None): | def update_feed(doc, method=None): | ||||
"adds a new communication with comment_type='Updated'" | "adds a new communication with comment_type='Updated'" | ||||
@@ -53,19 +54,14 @@ def update_feed(doc, method=None): | |||||
}).insert(ignore_permissions=True) | }).insert(ignore_permissions=True) | ||||
def login_feed(login_manager): | def login_feed(login_manager): | ||||
add_info_comment(**{ | |||||
"subject": _("{0} logged in").format(get_fullname(login_manager.user)), | |||||
"full_name": get_fullname(login_manager.user) | |||||
}) | |||||
if login_manager.user != "Guest": | |||||
subject = _("{0} logged in").format(get_fullname(login_manager.user)) | |||||
add_authentication_log(subject, login_manager.user) | |||||
def logout_feed(user, reason): | def logout_feed(user, reason): | ||||
if not user: | |||||
return | |||||
add_info_comment(**{ | |||||
"subject": _("{0} logged out: <b>{1}</b>").format(get_fullname(user), reason), | |||||
"full_name": get_fullname(user), | |||||
}) | |||||
if user and user != "Guest": | |||||
subject = _("{0} logged out: {1}").format(get_fullname(user), frappe.bold(reason)) | |||||
add_authentication_log(subject, user, operation="Logout") | |||||
def get_feed_match_conditions(user=None, force=True): | def get_feed_match_conditions(user=None, force=True): | ||||
if not user: user = frappe.session.user | if not user: user = frappe.session.user | ||||
@@ -72,6 +72,14 @@ frappe.pages['activity'].on_page_load = function(wrapper) { | |||||
}, 'fa fa-th') | }, 'fa fa-th') | ||||
} | } | ||||
this.page.add_menu_item(__('Authentication Log'), function() { | |||||
frappe.route_options = { | |||||
"user": user | |||||
} | |||||
frappe.set_route('Report', "Authentication Log"); | |||||
}, 'fa fa-th') | |||||
this.page.add_menu_item(__('Show Likes'), function() { | this.page.add_menu_item(__('Show Likes'), function() { | ||||
frappe.route_options = { | frappe.route_options = { | ||||
show_likes: true | show_likes: true | ||||