refactor: improve oauthlib implementation
implement openid provider
implement PKCE
improve errors
(cherry picked from commit 96d6971ee4)
This commit is contained in:
parent
2ca9db0477
commit
dfd5651dbe
7 changed files with 673 additions and 805 deletions
|
|
@ -15,7 +15,6 @@ from frappe.utils.background_jobs import get_jobs
|
|||
from frappe.utils.data import get_url, get_link_to_form
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.integrations.oauth2 import validate_url
|
||||
|
||||
|
||||
class EventProducer(Document):
|
||||
|
|
@ -56,7 +55,7 @@ class EventProducer(Document):
|
|||
self.reload()
|
||||
|
||||
def check_url(self):
|
||||
if not validate_url(self.producer_url):
|
||||
if not frappe.utils.validate_url(self.producer_url):
|
||||
frappe.throw(_('Invalid URL'))
|
||||
|
||||
# remove '/' from the end of the url like http://test_site.com/
|
||||
|
|
|
|||
|
|
@ -1,256 +1,112 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:authorization_code",
|
||||
"beta": 0,
|
||||
"creation": "2016-08-24 14:12:13.647159",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"autoname": "field:authorization_code",
|
||||
"creation": "2016-08-24 14:12:13.647159",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"user",
|
||||
"scopes",
|
||||
"authorization_code",
|
||||
"expiration_time",
|
||||
"redirect_uri_bound_to_authorization_code",
|
||||
"validity",
|
||||
"nonce",
|
||||
"code_challenge",
|
||||
"code_challenge_method"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "OAuth Client",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"options": "OAuth Client",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Scopes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"label": "Scopes",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "authorization_code",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Authorization Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "authorization_code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Authorization Code",
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expiration time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Expiration time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "redirect_uri_bound_to_authorization_code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Redirect URI Bound To Auth Code",
|
||||
"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
|
||||
},
|
||||
"fieldname": "redirect_uri_bound_to_authorization_code",
|
||||
"fieldtype": "Data",
|
||||
"label": "Redirect URI Bound To Auth Code",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "validity",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Validity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Valid\nInvalid",
|
||||
"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
|
||||
"fieldname": "validity",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Validity",
|
||||
"options": "Valid\nInvalid",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "nonce",
|
||||
"fieldtype": "Data",
|
||||
"label": "nonce",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "code_challenge",
|
||||
"fieldtype": "Data",
|
||||
"label": "Code Challenge",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "code_challenge_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Code challenge method",
|
||||
"options": "\ns256\nplain",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-08 14:40:04.113884",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Authorization Code",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-04-26 07:23:02.980612",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Authorization Code",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,283 +1,96 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:access_token",
|
||||
"beta": 0,
|
||||
"creation": "2016-08-24 14:10:17.471264",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"autoname": "field:access_token",
|
||||
"creation": "2016-08-24 14:10:17.471264",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"user",
|
||||
"scopes",
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"expiration_time",
|
||||
"expires_in",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "OAuth Client",
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"options": "OAuth Client",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"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
|
||||
},
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Scopes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"label": "Scopes",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "access_token",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Access Token",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "access_token",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Token",
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "refresh_token",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Refresh Token",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "refresh_token",
|
||||
"fieldtype": "Data",
|
||||
"label": "Refresh Token",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expiration time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"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
|
||||
},
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Expiration time",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expires_in",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expires In",
|
||||
"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
|
||||
},
|
||||
"fieldname": "expires_in",
|
||||
"fieldtype": "Int",
|
||||
"label": "Expires In",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"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": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Active\nRevoked",
|
||||
"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
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "Active\nRevoked",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-08 14:40:04.209039",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Bearer Token",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-04-26 06:40:34.922441",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Bearer Token",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,195 +1,176 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
|
||||
import jwt
|
||||
from urllib.parse import quote, urlencode
|
||||
from oauthlib.oauth2 import FatalClientError, OAuth2Error
|
||||
from oauthlib.openid.connect.core.endpoints.pre_configured import (
|
||||
Server as WebApplicationServer,
|
||||
)
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer
|
||||
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings
|
||||
from frappe.oauth import OAuthWebRequestValidator, generate_json_error_response
|
||||
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import (
|
||||
get_oauth_settings,
|
||||
)
|
||||
|
||||
|
||||
def get_oauth_server():
|
||||
if not getattr(frappe.local, 'oauth_server', None):
|
||||
if not getattr(frappe.local, "oauth_server", None):
|
||||
oauth_validator = OAuthWebRequestValidator()
|
||||
frappe.local.oauth_server = WebApplicationServer(oauth_validator)
|
||||
|
||||
return frappe.local.oauth_server
|
||||
|
||||
|
||||
def sanitize_kwargs(param_kwargs):
|
||||
"""Remove 'data' and 'cmd' keys, if present."""
|
||||
arguments = param_kwargs
|
||||
arguments.pop('data', None)
|
||||
arguments.pop('cmd', None)
|
||||
arguments.pop("data", None)
|
||||
arguments.pop("cmd", None)
|
||||
|
||||
return arguments
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def approve(*args, **kwargs):
|
||||
r = frappe.request
|
||||
|
||||
try:
|
||||
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
|
||||
r.url,
|
||||
r.method,
|
||||
r.get_data(),
|
||||
r.headers
|
||||
(
|
||||
scopes,
|
||||
frappe.flags.oauth_credentials,
|
||||
) = get_oauth_server().validate_authorization_request(
|
||||
r.url, r.method, r.get_data(), r.headers
|
||||
)
|
||||
|
||||
headers, body, status = get_oauth_server().create_authorization_response(
|
||||
uri=frappe.flags.oauth_credentials['redirect_uri'],
|
||||
uri=frappe.flags.oauth_credentials["redirect_uri"],
|
||||
body=r.get_data(),
|
||||
headers=r.headers,
|
||||
scopes=scopes,
|
||||
credentials=frappe.flags.oauth_credentials
|
||||
credentials=frappe.flags.oauth_credentials,
|
||||
)
|
||||
uri = headers.get('Location', None)
|
||||
uri = headers.get("Location", None)
|
||||
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = uri
|
||||
return
|
||||
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
except OAuth2Error as e:
|
||||
return e
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def authorize(**kwargs):
|
||||
success_url = "/api/method/frappe.integrations.oauth2.approve?" + encode_params(sanitize_kwargs(kwargs))
|
||||
success_url = "/api/method/frappe.integrations.oauth2.approve?" + encode_params(
|
||||
sanitize_kwargs(kwargs)
|
||||
)
|
||||
failure_url = frappe.form_dict["redirect_uri"] + "?error=access_denied"
|
||||
|
||||
if frappe.session.user == 'Guest':
|
||||
#Force login, redirect to preauth again.
|
||||
if frappe.session.user == "Guest":
|
||||
# Force login, redirect to preauth again.
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = "/login?" + encode_params({'redirect-to': frappe.request.url})
|
||||
frappe.local.response["location"] = "/login?" + encode_params(
|
||||
{"redirect-to": frappe.request.url}
|
||||
)
|
||||
else:
|
||||
try:
|
||||
r = frappe.request
|
||||
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
|
||||
r.url,
|
||||
r.method,
|
||||
r.get_data(),
|
||||
r.headers
|
||||
(
|
||||
scopes,
|
||||
frappe.flags.oauth_credentials,
|
||||
) = get_oauth_server().validate_authorization_request(
|
||||
r.url, r.method, r.get_data(), r.headers
|
||||
)
|
||||
|
||||
skip_auth = frappe.db.get_value("OAuth Client", frappe.flags.oauth_credentials['client_id'], "skip_authorization")
|
||||
unrevoked_tokens = frappe.get_all("OAuth Bearer Token", filters={"status":"Active"})
|
||||
skip_auth = frappe.db.get_value(
|
||||
"OAuth Client",
|
||||
frappe.flags.oauth_credentials["client_id"],
|
||||
"skip_authorization",
|
||||
)
|
||||
unrevoked_tokens = frappe.get_all(
|
||||
"OAuth Bearer Token", filters={"status": "Active"}
|
||||
)
|
||||
|
||||
if skip_auth or (get_oauth_settings().skip_authorization == "Auto" and unrevoked_tokens):
|
||||
if skip_auth or (
|
||||
get_oauth_settings().skip_authorization == "Auto" and unrevoked_tokens
|
||||
):
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = success_url
|
||||
else:
|
||||
#Show Allow/Deny screen.
|
||||
response_html_params = frappe._dict({
|
||||
"client_id": frappe.db.get_value("OAuth Client", kwargs['client_id'], "app_name"),
|
||||
"success_url": success_url,
|
||||
"failure_url": failure_url,
|
||||
"details": scopes
|
||||
})
|
||||
resp_html = frappe.render_template("templates/includes/oauth_confirmation.html", response_html_params)
|
||||
# Show Allow/Deny screen.
|
||||
response_html_params = frappe._dict(
|
||||
{
|
||||
"client_id": frappe.db.get_value(
|
||||
"OAuth Client", kwargs["client_id"], "app_name"
|
||||
),
|
||||
"success_url": success_url,
|
||||
"failure_url": failure_url,
|
||||
"details": scopes,
|
||||
}
|
||||
)
|
||||
resp_html = frappe.render_template(
|
||||
"templates/includes/oauth_confirmation.html", response_html_params
|
||||
)
|
||||
frappe.respond_as_web_page("Confirm Access", resp_html)
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
except OAuth2Error as e:
|
||||
return e
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_token(*args, **kwargs):
|
||||
#Check whether frappe server URL is set
|
||||
frappe_server_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
|
||||
if not frappe_server_url:
|
||||
frappe.throw(_("Please set Base URL in Social Login Key for Frappe"))
|
||||
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_token_response(
|
||||
r.url,
|
||||
r.method,
|
||||
r.form,
|
||||
r.headers,
|
||||
frappe.flags.oauth_credentials
|
||||
r.url, r.method, r.form, r.headers, frappe.flags.oauth_credentials
|
||||
)
|
||||
out = frappe._dict(json.loads(body))
|
||||
if not out.error and "openid" in out.scope:
|
||||
token_user = frappe.db.get_value("OAuth Bearer Token", out.access_token, "user")
|
||||
token_client = frappe.db.get_value("OAuth Bearer Token", out.access_token, "client")
|
||||
client_secret = frappe.db.get_value("OAuth Client", token_client, "client_secret")
|
||||
if token_user in ["Guest", "Administrator"]:
|
||||
frappe.throw(_("Logged in as Guest or Administrator"))
|
||||
body = frappe._dict(json.loads(body))
|
||||
|
||||
id_token_header = {
|
||||
"typ":"jwt",
|
||||
"alg":"HS256"
|
||||
}
|
||||
id_token = {
|
||||
"aud": token_client,
|
||||
"exp": int((frappe.db.get_value("OAuth Bearer Token", out.access_token, "expiration_time") - frappe.utils.datetime.datetime(1970, 1, 1)).total_seconds()),
|
||||
"sub": frappe.db.get_value("User Social Login", {"parent":token_user, "provider": "frappe"}, "userid"),
|
||||
"iss": frappe_server_url,
|
||||
"at_hash": frappe.oauth.calculate_at_hash(out.access_token, hashlib.sha256)
|
||||
}
|
||||
if body.error:
|
||||
frappe.local.response = body
|
||||
frappe.local.response["http_status_code"] = 400
|
||||
return
|
||||
|
||||
id_token_encoded = jwt.encode(id_token, client_secret, algorithm='HS256', headers=id_token_header)
|
||||
out.update({"id_token": frappe.safe_decode(id_token_encoded)})
|
||||
frappe.local.response = body
|
||||
return
|
||||
|
||||
frappe.local.response = out
|
||||
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def revoke_token(*args, **kwargs):
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_revocation_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
http_method=r.method
|
||||
)
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_revocation_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
http_method=r.method,
|
||||
)
|
||||
except (FatalClientError, OAuth2Error):
|
||||
pass
|
||||
|
||||
# status_code must be 200
|
||||
frappe.local.response = frappe._dict({})
|
||||
frappe.local.response["http_status_code"] = status or 200
|
||||
return
|
||||
|
||||
frappe.local.response['http_status_code'] = status
|
||||
if status == 200:
|
||||
return "success"
|
||||
else:
|
||||
return "bad request"
|
||||
|
||||
@frappe.whitelist()
|
||||
def openid_profile(*args, **kwargs):
|
||||
picture = None
|
||||
first_name, last_name, avatar, name = frappe.db.get_value("User", frappe.session.user, ["first_name", "last_name", "user_image", "name"])
|
||||
frappe_userid = frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid")
|
||||
request_url = urlparse(frappe.request.url)
|
||||
base_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
|
||||
|
||||
if avatar:
|
||||
if validate_url(avatar):
|
||||
picture = avatar
|
||||
elif base_url:
|
||||
picture = base_url + '/' + avatar
|
||||
else:
|
||||
picture = request_url.scheme + "://" + request_url.netloc + avatar
|
||||
|
||||
user_profile = frappe._dict({
|
||||
"sub": frappe_userid,
|
||||
"name": " ".join(filter(None, [first_name, last_name])),
|
||||
"given_name": first_name,
|
||||
"family_name": last_name,
|
||||
"email": name,
|
||||
"picture": picture
|
||||
})
|
||||
|
||||
frappe.local.response = user_profile
|
||||
|
||||
def validate_url(url_string):
|
||||
try:
|
||||
result = urlparse(url_string)
|
||||
return result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]
|
||||
except:
|
||||
return False
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_userinfo_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
)
|
||||
body = frappe._dict(json.loads(body))
|
||||
frappe.local.response = body
|
||||
return
|
||||
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
def encode_params(params):
|
||||
"""
|
||||
|
|
|
|||
544
frappe/oauth.py
544
frappe/oauth.py
|
|
@ -1,65 +1,16 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
import frappe
|
||||
import pytz
|
||||
import jwt
|
||||
import hashlib
|
||||
import re
|
||||
import base64
|
||||
import datetime
|
||||
|
||||
from frappe import _
|
||||
from frappe.auth import LoginManager
|
||||
from http import cookies
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant
|
||||
from oauthlib.oauth2 import RequestValidator
|
||||
from oauthlib.oauth2.rfc6749.endpoints.authorization import AuthorizationEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.token import TokenEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.resource import ResourceEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.revocation import RevocationEndpoint
|
||||
from oauthlib.common import Request
|
||||
from six.moves.urllib.parse import unquote
|
||||
from oauthlib.openid import RequestValidator
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
def get_url_delimiter(separator_character=" "):
|
||||
return separator_character
|
||||
|
||||
class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a new web application server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
resource_owner_password_credentials_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={
|
||||
'code': auth_grant,
|
||||
'token': implicit_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'password': resource_owner_password_credentials_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
import frappe
|
||||
from frappe.auth import LoginManager
|
||||
|
||||
|
||||
class OAuthWebRequestValidator(RequestValidator):
|
||||
|
|
@ -67,7 +18,7 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Pre- and post-authorization.
|
||||
def validate_client_id(self, client_id, request, *args, **kwargs):
|
||||
# Simple validity check, does client exist? Not banned?
|
||||
cli_id = frappe.db.get_value("OAuth Client",{ "name":client_id })
|
||||
cli_id = frappe.db.get_value("OAuth Client", {"name": client_id})
|
||||
if cli_id:
|
||||
request.client = frappe.get_doc("OAuth Client", client_id).as_dict()
|
||||
return True
|
||||
|
|
@ -78,7 +29,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Is the client allowed to use the supplied redirect_uri? i.e. has
|
||||
# the client previously registered this EXACT redirect uri.
|
||||
|
||||
redirect_uris = frappe.db.get_value("OAuth Client", client_id, 'redirect_uris').split(get_url_delimiter())
|
||||
redirect_uris = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "redirect_uris"
|
||||
).split(get_url_delimiter())
|
||||
|
||||
if redirect_uri in redirect_uris:
|
||||
return True
|
||||
|
|
@ -89,7 +42,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# The redirect used if none has been supplied.
|
||||
# Prefer your clients to pre register a redirect uri rather than
|
||||
# supplying one on each authorization request.
|
||||
redirect_uri = frappe.db.get_value("OAuth Client", client_id, 'default_redirect_uri')
|
||||
redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "default_redirect_uri"
|
||||
)
|
||||
return redirect_uri
|
||||
|
||||
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
||||
|
|
@ -101,19 +56,23 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Scopes a client will authorize for if none are supplied in the
|
||||
# authorization request.
|
||||
scopes = get_client_scopes(client_id)
|
||||
request.scopes = scopes #Apparently this is possible.
|
||||
request.scopes = scopes # Apparently this is possible.
|
||||
return scopes
|
||||
|
||||
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
||||
# Clients should only be allowed to use one type of response type, the
|
||||
# one associated with their one allowed grant type.
|
||||
# In this case it must be "code".
|
||||
allowed_response_types = [client.response_type.lower(),
|
||||
"code token", "code id_token", "code token id_token",
|
||||
"code+token", "code+id_token", "code+token id_token"]
|
||||
|
||||
return (response_type in allowed_response_types)
|
||||
def validate_response_type(
|
||||
self, client_id, response_type, client, request, *args, **kwargs
|
||||
):
|
||||
allowed_response_types = [
|
||||
# From OAuth Client response_type field
|
||||
client.response_type.lower(),
|
||||
# OIDC
|
||||
"id_token",
|
||||
"id_token token",
|
||||
"code id_token",
|
||||
"code token id_token",
|
||||
]
|
||||
|
||||
return response_type in allowed_response_types
|
||||
|
||||
# Post-authorization
|
||||
|
||||
|
|
@ -121,38 +80,69 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
|
||||
oac = frappe.new_doc('OAuth Authorization Code')
|
||||
oac = frappe.new_doc("OAuth Authorization Code")
|
||||
oac.scopes = get_url_delimiter().join(request.scopes)
|
||||
oac.redirect_uri_bound_to_authorization_code = request.redirect_uri
|
||||
oac.client = client_id
|
||||
oac.user = unquote(cookie_dict['user_id'].value)
|
||||
oac.authorization_code = code['code']
|
||||
oac.user = unquote(cookie_dict["user_id"].value)
|
||||
oac.authorization_code = code["code"]
|
||||
|
||||
if request.nonce:
|
||||
oac.nonce = request.nonce
|
||||
|
||||
if request.code_challenge and request.code_challenge_method:
|
||||
oac.code_challenge = request.code_challenge
|
||||
oac.code_challenge_method = request.code_challenge_method.lower()
|
||||
|
||||
oac.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def authenticate_client(self, request, *args, **kwargs):
|
||||
#Get ClientID in URL
|
||||
# Get ClientID in URL
|
||||
if request.client_id:
|
||||
oc = frappe.get_doc("OAuth Client", request.client_id)
|
||||
else:
|
||||
#Extract token, instantiate OAuth Bearer Token and use clientid from there.
|
||||
# Extract token, instantiate OAuth Bearer Token and use clientid from there.
|
||||
if "refresh_token" in frappe.form_dict:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", {"refresh_token": frappe.form_dict["refresh_token"]}, 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
{"refresh_token": frappe.form_dict["refresh_token"]},
|
||||
"client",
|
||||
),
|
||||
)
|
||||
elif "token" in frappe.form_dict:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token", frappe.form_dict["token"], "client"
|
||||
),
|
||||
)
|
||||
else:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.get_request_header("Authorization").split(" ")[1], 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
frappe.get_request_header("Authorization").split(" ")[1],
|
||||
"client",
|
||||
),
|
||||
)
|
||||
try:
|
||||
request.client = request.client or oc.as_dict()
|
||||
except Exception as e:
|
||||
print("Failed body authentication: Application %s does not exist".format(cid=request.client_id))
|
||||
return generate_json_error_response(e)
|
||||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
user_id = unquote(cookie_dict.get('user_id').value) if 'user_id' in cookie_dict else "Guest"
|
||||
user_id = (
|
||||
unquote(cookie_dict.get("user_id").value)
|
||||
if "user_id" in cookie_dict
|
||||
else "Guest"
|
||||
)
|
||||
return frappe.session.user == user_id
|
||||
|
||||
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
||||
cli_id = frappe.db.get_value('OAuth Client', client_id, 'name')
|
||||
cli_id = frappe.db.get_value("OAuth Client", client_id, "name")
|
||||
if not cli_id:
|
||||
# Don't allow public (non-authenticated) clients
|
||||
return False
|
||||
|
|
@ -164,28 +154,66 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Validate the code belongs to the client. Add associated scopes,
|
||||
# state and user to request.scopes and request.user.
|
||||
|
||||
validcodes = frappe.get_all("OAuth Authorization Code", filters={"client": client_id, "validity": "Valid"})
|
||||
validcodes = frappe.get_all(
|
||||
"OAuth Authorization Code",
|
||||
filters={"client": client_id, "validity": "Valid"},
|
||||
)
|
||||
|
||||
checkcodes = []
|
||||
for vcode in validcodes:
|
||||
checkcodes.append(vcode["name"])
|
||||
|
||||
if code in checkcodes:
|
||||
request.scopes = frappe.db.get_value("OAuth Authorization Code", code, 'scopes').split(get_url_delimiter())
|
||||
request.user = frappe.db.get_value("OAuth Authorization Code", code, 'user')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
request.scopes = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "scopes"
|
||||
).split(get_url_delimiter())
|
||||
request.user = frappe.db.get_value("OAuth Authorization Code", code, "user")
|
||||
code_challenge_method = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "code_challenge_method"
|
||||
)
|
||||
code_challenge = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "code_challenge"
|
||||
)
|
||||
|
||||
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **kwargs):
|
||||
saved_redirect_uri = frappe.db.get_value('OAuth Client', client_id, 'default_redirect_uri')
|
||||
if code_challenge and not request.code_verifier:
|
||||
if frappe.db.exists("OAuth Authorization Code", code):
|
||||
frappe.delete_doc(
|
||||
"OAuth Authorization Code", code, ignore_permissions=True
|
||||
)
|
||||
frappe.db.commit()
|
||||
return False
|
||||
|
||||
if code_challenge_method == "s256":
|
||||
m = hashlib.sha256()
|
||||
m.update(bytes(request.code_verifier, "utf-8"))
|
||||
code_verifier = base64.b64encode(m.digest()).decode("utf-8")
|
||||
code_verifier = re.sub(r"\+", "-", code_verifier)
|
||||
code_verifier = re.sub(r"\/", "_", code_verifier)
|
||||
code_verifier = re.sub(r"=", "", code_verifier)
|
||||
return code_challenge == code_verifier
|
||||
|
||||
elif code_challenge_method == "plain":
|
||||
return code_challenge == request.code_verifier
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def confirm_redirect_uri(
|
||||
self, client_id, code, redirect_uri, client, *args, **kwargs
|
||||
):
|
||||
saved_redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "default_redirect_uri"
|
||||
)
|
||||
|
||||
return saved_redirect_uri == redirect_uri
|
||||
|
||||
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
||||
def validate_grant_type(
|
||||
self, client_id, grant_type, client, request, *args, **kwargs
|
||||
):
|
||||
# Clients should only be allowed to use one type of grant.
|
||||
# In this case, it must be "authorization_code" or "refresh_token"
|
||||
return (grant_type in ["authorization_code", "refresh_token", "password"])
|
||||
return grant_type in ["authorization_code", "refresh_token", "password"]
|
||||
|
||||
def save_bearer_token(self, token, request, *args, **kwargs):
|
||||
# Remember to associate it with request.scopes, request.user and
|
||||
|
|
@ -195,19 +223,29 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# access_token to now + expires_in seconds.
|
||||
|
||||
otoken = frappe.new_doc("OAuth Bearer Token")
|
||||
otoken.client = request.client['name']
|
||||
otoken.client = request.client["name"]
|
||||
try:
|
||||
otoken.user = request.user if request.user else frappe.db.get_value("OAuth Bearer Token", {"refresh_token":request.body.get("refresh_token")}, "user")
|
||||
otoken.user = (
|
||||
request.user
|
||||
if request.user
|
||||
else frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
{"refresh_token": request.body.get("refresh_token")},
|
||||
"user",
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
otoken.user = frappe.session.user
|
||||
otoken.scopes = get_url_delimiter().join(request.scopes)
|
||||
otoken.access_token = token['access_token']
|
||||
otoken.refresh_token = token.get('refresh_token')
|
||||
otoken.expires_in = token['expires_in']
|
||||
otoken.access_token = token["access_token"]
|
||||
otoken.refresh_token = token.get("refresh_token")
|
||||
otoken.expires_in = token["expires_in"]
|
||||
otoken.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
default_redirect_uri = frappe.db.get_value("OAuth Client", request.client['name'], "default_redirect_uri")
|
||||
default_redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", request.client["name"], "default_redirect_uri"
|
||||
)
|
||||
return default_redirect_uri
|
||||
|
||||
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||
|
|
@ -222,24 +260,35 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
def validate_bearer_token(self, token, scopes, request):
|
||||
# Remember to check expiration and scope membership
|
||||
otoken = frappe.get_doc("OAuth Bearer Token", token)
|
||||
token_expiration_local = otoken.expiration_time.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone()))
|
||||
token_expiration_local = otoken.expiration_time.replace(
|
||||
tzinfo=pytz.timezone(frappe.utils.get_time_zone())
|
||||
)
|
||||
token_expiration_utc = token_expiration_local.astimezone(pytz.utc)
|
||||
is_token_valid = (frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc) < token_expiration_utc) \
|
||||
and otoken.status != "Revoked"
|
||||
client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(get_url_delimiter())
|
||||
is_token_valid = (
|
||||
frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
< token_expiration_utc
|
||||
) and otoken.status != "Revoked"
|
||||
client_scopes = frappe.db.get_value(
|
||||
"OAuth Client", otoken.client, "scopes"
|
||||
).split(get_url_delimiter())
|
||||
are_scopes_valid = True
|
||||
for scp in scopes:
|
||||
are_scopes_valid = are_scopes_valid and True if scp in client_scopes else False
|
||||
are_scopes_valid = (
|
||||
are_scopes_valid and True if scp in client_scopes else False
|
||||
)
|
||||
|
||||
return is_token_valid and are_scopes_valid
|
||||
|
||||
# Token refresh request
|
||||
|
||||
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
||||
# Obtain the token associated with the given refresh_token and
|
||||
# return its scopes, these will be passed on to the refreshed
|
||||
# access token if the client did not specify a scope during the
|
||||
# request.
|
||||
obearer_token = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token})
|
||||
obearer_token = frappe.get_doc(
|
||||
"OAuth Bearer Token", {"refresh_token": refresh_token}
|
||||
)
|
||||
return obearer_token.scopes
|
||||
|
||||
def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
|
||||
|
|
@ -250,36 +299,44 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
|
||||
Method is used by:
|
||||
- Revocation Endpoint
|
||||
- Revocation Endpoint
|
||||
"""
|
||||
otoken = None
|
||||
|
||||
if token_type_hint == "access_token":
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", token, 'status', 'Revoked')
|
||||
otoken = frappe.db.set_value(
|
||||
"OAuth Bearer Token", token, "status", "Revoked"
|
||||
)
|
||||
elif token_type_hint == "refresh_token":
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", {"refresh_token": token}, 'status', 'Revoked')
|
||||
otoken = frappe.db.set_value(
|
||||
"OAuth Bearer Token", {"refresh_token": token}, "status", "Revoked"
|
||||
)
|
||||
else:
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", token, 'status', 'Revoked')
|
||||
otoken = frappe.db.set_value(
|
||||
"OAuth Bearer Token", token, "status", "Revoked"
|
||||
)
|
||||
frappe.db.commit()
|
||||
|
||||
def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs):
|
||||
# """Ensure the Bearer token is valid and authorized access to scopes.
|
||||
"""Ensure the Bearer token is valid and authorized access to scopes.
|
||||
|
||||
# OBS! The request.user attribute should be set to the resource owner
|
||||
# associated with this refresh token.
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this refresh token.
|
||||
|
||||
# :param refresh_token: Unicode refresh token
|
||||
# :param client: Client object set by you, see authenticate_client.
|
||||
# :param request: The HTTP Request (oauthlib.common.Request)
|
||||
# :rtype: True or False
|
||||
:param refresh_token: Unicode refresh token
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
# Method is used by:
|
||||
# - Authorization Code Grant (indirectly by issuing refresh tokens)
|
||||
# - Resource Owner Password Credentials Grant (also indirectly)
|
||||
# - Refresh Token Grant
|
||||
# """
|
||||
Method is used by:
|
||||
- Authorization Code Grant (indirectly by issuing refresh tokens)
|
||||
- Resource Owner Password Credentials Grant (also indirectly)
|
||||
- Refresh Token Grant
|
||||
"""
|
||||
|
||||
otoken = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"})
|
||||
otoken = frappe.get_doc(
|
||||
"OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"}
|
||||
)
|
||||
|
||||
if not otoken:
|
||||
return False
|
||||
|
|
@ -287,36 +344,84 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
return True
|
||||
|
||||
# OpenID Connect
|
||||
def get_id_token(self, token, token_handler, request):
|
||||
"""
|
||||
In the OpenID Connect workflows when an ID Token is requested this method is called.
|
||||
Subclasses should implement the construction, signing and optional encryption of the
|
||||
ID Token as described in the OpenID Connect spec.
|
||||
|
||||
In addition to the standard OAuth2 request properties, the request may also contain
|
||||
these OIDC specific properties which are useful to this method:
|
||||
def finalize_id_token(self, id_token, token, token_handler, request):
|
||||
# Check whether frappe server URL is set
|
||||
frappe_server_url = (
|
||||
frappe.db.get_value("Social Login Key", "frappe", "base_url") or request.uri
|
||||
)
|
||||
|
||||
- nonce, if workflow is implicit or hybrid and it was provided
|
||||
- claims, if provided to the original Authorization Code request
|
||||
id_token_header = {"typ": "jwt", "alg": "HS256"}
|
||||
|
||||
The token parameter is a dict which may contain an ``access_token`` entry, in which
|
||||
case the resulting ID Token *should* include a calculated ``at_hash`` claim.
|
||||
user = frappe.get_doc(
|
||||
"User",
|
||||
frappe.session.user,
|
||||
)
|
||||
|
||||
Similarly, when the request parameter has a ``code`` property defined, the ID Token
|
||||
*should* include a calculated ``c_hash`` claim.
|
||||
if request.nonce:
|
||||
id_token["nonce"] = request.nonce
|
||||
|
||||
http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)
|
||||
if "openid" in request.scopes:
|
||||
userinfo = get_userinfo(user, request)
|
||||
id_token.update(userinfo)
|
||||
|
||||
.. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
.. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
|
||||
.. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||
id_token_encoded = jwt.encode(
|
||||
payload=id_token,
|
||||
key=request.client.client_secret,
|
||||
algorithm="HS256",
|
||||
headers=id_token_header,
|
||||
)
|
||||
|
||||
:param token: A Bearer token dict
|
||||
:param token_handler: the token handler (BearerToken class)
|
||||
:param request: the HTTP Request (oauthlib.common.Request)
|
||||
:return: The ID Token (a JWS signed JWT)
|
||||
"""
|
||||
# the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token
|
||||
return frappe.safe_decode(id_token_encoded)
|
||||
|
||||
def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
|
||||
if frappe.get_value("OAuth Authorization Code", code, "validity") == "Valid":
|
||||
return frappe.get_value("OAuth Authorization Code", code, "nonce")
|
||||
|
||||
return None
|
||||
|
||||
def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
|
||||
scope = frappe.get_value("OAuth Client", client_id, "scopes")
|
||||
if not scope:
|
||||
scope = []
|
||||
else:
|
||||
scope = scope.split(get_url_delimiter())
|
||||
|
||||
return scope
|
||||
|
||||
def get_jwt_bearer_token(self, token, token_handler, request):
|
||||
now = datetime.datetime.now()
|
||||
id_token = dict(
|
||||
aud=token.client_id,
|
||||
iat=round(now.timestamp()),
|
||||
at_hash=calculate_at_hash(token.access_token, hashlib.sha256),
|
||||
)
|
||||
return self.finalize_id_token(id_token, token, token_handler, request)
|
||||
|
||||
def get_userinfo_claims(self, request):
|
||||
user = frappe.get_doc("User", frappe.session.user)
|
||||
userinfo = get_userinfo(user, request)
|
||||
return userinfo
|
||||
|
||||
def validate_id_token(self, token, scopes, request):
|
||||
try:
|
||||
id_token = frappe.get_doc("OAuth Bearer Token", token)
|
||||
if id_token.status == "Active":
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def validate_jwt_bearer_token(self, token, scopes, request):
|
||||
try:
|
||||
jwt = frappe.get_doc("OAuth Bearer Token", token)
|
||||
if jwt.status == "Active":
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def validate_silent_authorization(self, request):
|
||||
"""Ensure the logged in user has authorized silent OpenID authorization.
|
||||
|
|
@ -328,9 +433,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
if request.prompt == "login":
|
||||
False
|
||||
|
|
@ -351,9 +456,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
if frappe.session.user == "Guest" or request.prompt.lower() == "login":
|
||||
return False
|
||||
|
|
@ -373,32 +478,77 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
:rtype: True or False
|
||||
|
||||
Method is used by:
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
- OpenIDConnectAuthCode
|
||||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
if id_token_hint and id_token_hint == frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid"):
|
||||
if id_token_hint:
|
||||
try:
|
||||
user = None
|
||||
payload = jwt.decode(
|
||||
id_token_hint,
|
||||
options={
|
||||
"verify_signature": False,
|
||||
"verify_aud": False,
|
||||
},
|
||||
)
|
||||
client_id, client_secret = frappe.get_value(
|
||||
"OAuth Client",
|
||||
payload.get("aud"),
|
||||
["client_id", "client_secret"],
|
||||
)
|
||||
|
||||
if payload.get("sub") and client_id and client_secret:
|
||||
user = frappe.db.get_value(
|
||||
"User Social Login",
|
||||
{"userid": payload.get("sub"), "provider": "frappe"},
|
||||
"parent",
|
||||
)
|
||||
user = frappe.get_doc("User", user)
|
||||
verified_payload = jwt.decode(
|
||||
id_token_hint,
|
||||
key=client_secret,
|
||||
audience=client_id,
|
||||
algorithm="HS256",
|
||||
options={
|
||||
"verify_exp": False,
|
||||
},
|
||||
)
|
||||
|
||||
if verified_payload:
|
||||
return user.name == frappe.session.user
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
elif frappe.session.user != "Guest":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def validate_user(self, username, password, client, request, *args, **kwargs):
|
||||
"""Ensure the username and password is valid.
|
||||
|
||||
Method is used by:
|
||||
- Resource Owner Password Credentials Grant
|
||||
"""
|
||||
Method is used by:
|
||||
- Resource Owner Password Credentials Grant
|
||||
"""
|
||||
login_manager = LoginManager()
|
||||
login_manager.authenticate(username, password)
|
||||
|
||||
if login_manager.user == "Guest":
|
||||
return False
|
||||
|
||||
request.user = login_manager.user
|
||||
return True
|
||||
|
||||
|
||||
def get_cookie_dict_from_headers(r):
|
||||
cookie = cookies.BaseCookie()
|
||||
if r.headers.get('Cookie'):
|
||||
cookie.load(r.headers.get('Cookie'))
|
||||
if r.headers.get("Cookie"):
|
||||
cookie.load(r.headers.get("Cookie"))
|
||||
return cookie
|
||||
|
||||
|
||||
def calculate_at_hash(access_token, hash_alg):
|
||||
"""Helper method for calculating an access token
|
||||
hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
|
|
@ -409,21 +559,25 @@ def calculate_at_hash(access_token, hash_alg):
|
|||
then take the left-most 128 bits and base64url encode them. The at_hash value is a
|
||||
case sensitive string.
|
||||
Args:
|
||||
access_token (str): An access token string.
|
||||
hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256
|
||||
access_token (str): An access token string.
|
||||
hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256
|
||||
"""
|
||||
hash_digest = hash_alg(access_token.encode('utf-8')).digest()
|
||||
hash_digest = hash_alg(access_token.encode("utf-8")).digest()
|
||||
cut_at = int(len(hash_digest) / 2)
|
||||
truncated = hash_digest[:cut_at]
|
||||
from jwt.utils import base64url_encode
|
||||
|
||||
at_hash = base64url_encode(truncated)
|
||||
return at_hash.decode('utf-8')
|
||||
return at_hash.decode("utf-8")
|
||||
|
||||
|
||||
def delete_oauth2_data():
|
||||
# Delete Invalid Authorization Code and Revoked Token
|
||||
commit_code, commit_token = False, False
|
||||
code_list = frappe.get_all("OAuth Authorization Code", filters={"validity":"Invalid"})
|
||||
token_list = frappe.get_all("OAuth Bearer Token", filters={"status":"Revoked"})
|
||||
code_list = frappe.get_all(
|
||||
"OAuth Authorization Code", filters={"validity": "Invalid"}
|
||||
)
|
||||
token_list = frappe.get_all("OAuth Bearer Token", filters={"status": "Revoked"})
|
||||
if len(code_list) > 0:
|
||||
commit_code = True
|
||||
if len(token_list) > 0:
|
||||
|
|
@ -439,3 +593,59 @@ def delete_oauth2_data():
|
|||
def get_client_scopes(client_id):
|
||||
scopes_string = frappe.db.get_value("OAuth Client", client_id, "scopes")
|
||||
return scopes_string.split()
|
||||
|
||||
|
||||
def get_userinfo(user, request):
|
||||
picture = None
|
||||
frappe_server_url = (
|
||||
frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
|
||||
)
|
||||
|
||||
request_url = urlparse(request.uri)
|
||||
|
||||
if user.user_image:
|
||||
if frappe.utils.validate_url(user.user_image):
|
||||
picture = user.user_image
|
||||
elif frappe_server_url:
|
||||
picture = frappe_server_url + "/" + user.user_image
|
||||
else:
|
||||
picture = request_url.scheme + "://" + request_url.netloc + user.user_image
|
||||
|
||||
userinfo = frappe._dict(
|
||||
{
|
||||
"sub": frappe.db.get_value(
|
||||
"User Social Login",
|
||||
{"parent": user.name, "provider": "frappe"},
|
||||
"userid",
|
||||
),
|
||||
"name": " ".join(filter(None, [user.first_name, user.last_name])),
|
||||
"given_name": user.first_name,
|
||||
"family_name": user.last_name,
|
||||
"email": user.email,
|
||||
"picture": picture,
|
||||
"roles": frappe.get_roles(user.name),
|
||||
}
|
||||
)
|
||||
|
||||
userinfo["iss"] = frappe_server_url or request.uri
|
||||
|
||||
return userinfo
|
||||
|
||||
|
||||
def get_url_delimiter(separator_character=" "):
|
||||
return separator_character
|
||||
|
||||
|
||||
def generate_json_error_response(e):
|
||||
if not e:
|
||||
e = frappe._dict({})
|
||||
|
||||
frappe.local.response = frappe._dict(
|
||||
{
|
||||
"description": getattr(e, "description", "Internal Server Error"),
|
||||
"status_code": getattr(e, "status_code", 500),
|
||||
"error": getattr(e, "error", "internal_server_error"),
|
||||
}
|
||||
)
|
||||
frappe.local.response["http_status_code"] = getattr(e, "status_code", 500)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ class TestOAuth20(unittest.TestCase):
|
|||
"grant_type": "authorization_code",
|
||||
"code": auth_code,
|
||||
"redirect_uri": self.redirect_uri,
|
||||
"client_id": self.client_id
|
||||
"client_id": self.client_id,
|
||||
"scope": self.scope,
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from gzip import GzipFile
|
|||
from typing import Generator, Iterable
|
||||
|
||||
from six import string_types, text_type
|
||||
from six.moves.urllib.parse import quote
|
||||
from six.moves.urllib.parse import quote, urlparse
|
||||
from werkzeug.test import Client
|
||||
|
||||
import frappe
|
||||
|
|
@ -813,3 +813,11 @@ def groupby_metric(iterable: typing.Dict[str, list], key: str):
|
|||
for item in items:
|
||||
records.setdefault(item[key], {}).setdefault(category, []).append(item)
|
||||
return records
|
||||
|
||||
def validate_url(url_string):
|
||||
try:
|
||||
result = urlparse(url_string)
|
||||
return result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]
|
||||
except:
|
||||
return False
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue