+redirect_uri =
+client_id =
+```
+Response:
+```
+{
+ "access_token": "pNO2DpTMHTcFHYUXwzs74k6idQBmnI",
+ "token_type": "Bearer",
+ "expires_in": 3600,
+ "refresh_token": "cp74cxbbDgaxFuUZ8Usc7egYlhKbH1",
+ "scope": "project"
+}
+```
+
+#### Refresh Access Token
+
+URL:
+```
+[POST] 0.0.0.0:8000/api/method/frappe.integration_broker.oauth2.get_token
+```
+Params:
+```
+grant_type = "refresh_token"
+refresh_token =
+redirect_uri =
+client_id =
+```
+Response:
+```
+{
+ "access_token": "Ywz1iNk0b21iAmjWAYnFWT4CuudHD5",
+ "token_type": "Bearer",
+ "expires_in": 3600,
+ "refresh_token": "PNux3Q8Citr3s9rl2zEsKuU1l8bSN5",
+ "scope": "project"
+}
+```
+#### Revoke Token Endpoint
+
+URL:
+```
+[POST] 0.0.0.0:8000/api/method/frappe.integration_broker.oauth2.revoke_token
+```
+Params:
+```
+token =
+```
+Success Response
+```
+status : 200
+
+{"message": "success"}
+```
+Error Response:
+```
+status : 400
+
+{"message": "bad request"}
+```
+
+### Accessing Resource
+
+Add header `Authorizaton: Bearer ` to Frappe's REST API endpoints to access user's resource
diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/__init__.py b/frappe/integration_broker/doctype/oauth_authorization_code/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.js b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.js
new file mode 100644
index 0000000000..32746e6752
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('OAuth Authorization Code', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json
new file mode 100644
index 0000000000..35ee594063
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.json
@@ -0,0 +1,241 @@
+{
+ "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",
+ "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,
+ "label": "Client",
+ "length": 0,
+ "no_copy": 0,
+ "options": "OAuth Client",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "label": "User",
+ "length": 0,
+ "no_copy": 0,
+ "options": "User",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "scopes",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Scopes",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "authorization_code",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Authorization Code",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "expiration_time",
+ "fieldtype": "Datetime",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Expiration time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "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,
+ "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,
+ "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": "validity",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "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,
+ "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": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2016-10-20 00:13:53.224437",
+ "modified_by": "Administrator",
+ "module": "Integration Broker",
+ "name": "OAuth Authorization Code",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "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": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.py b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.py
new file mode 100644
index 0000000000..f08e7eb5bb
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_authorization_code/oauth_authorization_code.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class OAuthAuthorizationCode(Document):
+ pass
diff --git a/frappe/integration_broker/doctype/oauth_authorization_code/test_oauth_authorization_code.py b/frappe/integration_broker/doctype/oauth_authorization_code/test_oauth_authorization_code.py
new file mode 100644
index 0000000000..cecf187e61
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_authorization_code/test_oauth_authorization_code.py
@@ -0,0 +1,12 @@
+# -*- 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('OAuth Authorization Code')
+
+class TestOAuthAuthorizationCode(unittest.TestCase):
+ pass
diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/__init__.py b/frappe/integration_broker/doctype/oauth_bearer_token/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.js b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.js
new file mode 100644
index 0000000000..da69753903
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('OAuth Bearer Token', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json
new file mode 100644
index 0000000000..029b8754fe
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.json
@@ -0,0 +1,266 @@
+{
+ "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",
+ "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,
+ "label": "Client",
+ "length": 0,
+ "no_copy": 0,
+ "options": "OAuth Client",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "label": "User",
+ "length": 0,
+ "no_copy": 0,
+ "options": "User",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "scopes",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Scopes",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "access_token",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Access Token",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "refresh_token",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Refresh Token",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "expiration_time",
+ "fieldtype": "Datetime",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Expiration time",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "expires_in",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Expires In",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": 0,
+ "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,
+ "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": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2016-10-20 00:13:41.747141",
+ "modified_by": "Administrator",
+ "module": "Integration Broker",
+ "name": "OAuth Bearer Token",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "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": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.py b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.py
new file mode 100644
index 0000000000..1c7e921140
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_bearer_token/oauth_bearer_token.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class OAuthBearerToken(Document):
+ def validate(self):
+ if not self.expiration_time:
+ self.expiration_time = frappe.utils.datetime.datetime.strptime(self.creation, "%Y-%m-%d %H:%M:%S.%f") + frappe.utils.datetime.timedelta(seconds=self.expires_in)
+
diff --git a/frappe/integration_broker/doctype/oauth_bearer_token/test_oauth_bearer_token.py b/frappe/integration_broker/doctype/oauth_bearer_token/test_oauth_bearer_token.py
new file mode 100644
index 0000000000..af7de360ab
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_bearer_token/test_oauth_bearer_token.py
@@ -0,0 +1,12 @@
+# -*- 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('OAuth Bearer Token')
+
+class TestOAuthBearerToken(unittest.TestCase):
+ pass
diff --git a/frappe/integration_broker/doctype/oauth_client/__init__.py b/frappe/integration_broker/doctype/oauth_client/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integration_broker/doctype/oauth_client/oauth_client.js b/frappe/integration_broker/doctype/oauth_client/oauth_client.js
new file mode 100644
index 0000000000..b0caa562b1
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_client/oauth_client.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('OAuth Client', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/frappe/integration_broker/doctype/oauth_client/oauth_client.json b/frappe/integration_broker/doctype/oauth_client/oauth_client.json
new file mode 100644
index 0000000000..83ec6bebdd
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_client/oauth_client.json
@@ -0,0 +1,428 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "",
+ "beta": 0,
+ "creation": "2016-08-24 14:07:21.955052",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "app_name",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "App Name",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "user",
+ "fieldtype": "Link",
+ "hidden": 1,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 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,
+ "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": "cb_1",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "default": "",
+ "fieldname": "client_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "App Client ID",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "description": "If checked, users will not see the Confirm Access dialog.",
+ "fieldname": "skip_authorization",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Skip Authorization",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "description": "",
+ "fieldname": "sb_1",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "description": "A list of resources which the Client App will have access to after the user allows it.
e.g. project",
+ "fieldname": "scopes",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Scopes",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "cb_3",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n
e.g. http://hostname//api/method/frappe.www.login.login_via_facebook",
+ "fieldname": "redirect_uris",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Redirect URIs",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "default_redirect_uri",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Default Redirect URI",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 1,
+ "collapsible_depends_on": "1",
+ "columns": 0,
+ "fieldname": "sb_advanced",
+ "fieldtype": "Section Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": " Advanced Settings",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "grant_type",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Grant Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Authorization Code\nImplicit\nResource Owner Password Credentials\nClient Credentials",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": "cb_2",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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,
+ "default": "Code",
+ "fieldname": "response_type",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Response Type",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Code\nToken",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2016-10-20 00:32:11.993940",
+ "modified_by": "Administrator",
+ "module": "Integration Broker",
+ "name": "OAuth Client",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "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": 1
+ }
+ ],
+ "quick_entry": 0,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "app_name",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/integration_broker/doctype/oauth_client/oauth_client.py b/frappe/integration_broker/doctype/oauth_client/oauth_client.py
new file mode 100644
index 0000000000..3493832064
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_client/oauth_client.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class OAuthClient(Document):
+ def validate(self):
+ self.client_id = self.name
diff --git a/frappe/integration_broker/doctype/oauth_client/test_oauth_client.py b/frappe/integration_broker/doctype/oauth_client/test_oauth_client.py
new file mode 100644
index 0000000000..ee119455e5
--- /dev/null
+++ b/frappe/integration_broker/doctype/oauth_client/test_oauth_client.py
@@ -0,0 +1,12 @@
+# -*- 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('OAuth Client')
+
+class TestOAuthClient(unittest.TestCase):
+ pass
diff --git a/frappe/integration_broker/oauth2.py b/frappe/integration_broker/oauth2.py
new file mode 100644
index 0000000000..422b6c9d4e
--- /dev/null
+++ b/frappe/integration_broker/oauth2.py
@@ -0,0 +1,123 @@
+from __future__ import unicode_literals
+import frappe, json
+from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer
+from oauthlib.oauth2 import FatalClientError, OAuth2Error
+from urllib import quote, urlencode
+from urlparse import urlparse
+from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings
+
+#Variables required across requests
+oauth_validator = OAuthWebRequestValidator()
+oauth_server = WebApplicationServer(oauth_validator)
+credentials = None
+
+def get_urlparams_from_kwargs(param_kwargs):
+ arguments = param_kwargs
+ if arguments.get("data"):
+ arguments.pop("data")
+ if arguments.get("cmd"):
+ arguments.pop("cmd")
+
+ return urlencode(arguments)
+
+@frappe.whitelist()
+def approve(*args, **kwargs):
+ r = frappe.request
+ uri = r.url
+ http_method = r.method
+ body = r.get_data()
+ headers = r.headers
+
+ try:
+ scopes, credentials = oauth_server.validate_authorization_request(uri, http_method, body, headers)
+
+ headers, body, status = oauth_server.create_authorization_response(uri=credentials['redirect_uri'], \
+ body=body, headers=headers, scopes=scopes, credentials=credentials)
+ uri = headers.get('Location', None)
+
+ frappe.local.response["type"] = "redirect"
+ frappe.local.response["location"] = uri
+
+ except FatalClientError as e:
+ return e
+ except OAuth2Error as e:
+ return e
+
+@frappe.whitelist(allow_guest=True)
+def authorize(*args, **kwargs):
+ #Fetch provider URL from settings
+ oauth_settings = get_oauth_settings()
+ params = get_urlparams_from_kwargs(kwargs)
+ request_url = urlparse(frappe.request.url)
+ success_url = request_url.scheme + "://" + request_url.netloc + "/api/method/frappe.integration_broker.oauth2.approve?" + params
+ failure_url = frappe.form_dict["redirect_uri"] + "?error=access_denied"
+
+ if frappe.session['user']=='Guest':
+ #Force login, redirect to preauth again.
+ frappe.local.response["type"] = "redirect"
+ frappe.local.response["location"] = "/login?redirect-to=/api/method/frappe.integration_broker.oauth2.authorize?" + quote(params)
+
+ elif frappe.session['user']!='Guest':
+ try:
+ r = frappe.request
+ uri = r.url
+ http_method = r.method
+ body = r.get_data()
+ headers = r.headers
+
+ scopes, credentials = oauth_server.validate_authorization_request(uri, http_method, body, headers)
+
+ skip_auth = frappe.db.get_value("OAuth Client", credentials['client_id'], "skip_authorization")
+ unrevoked_tokens = frappe.get_all("OAuth Bearer Token", filters={"status":"Active"})
+
+ if skip_auth or (oauth_settings["skip_authorization"] == "Auto" and len(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)
+ frappe.respond_as_web_page("Confirm Access", resp_html)
+
+ except FatalClientError as e:
+ return e
+ except OAuth2Error as e:
+ return e
+
+@frappe.whitelist(allow_guest=True)
+def get_token(*args, **kwargs):
+ r = frappe.request
+
+ uri = r.url
+ http_method = r.method
+ body = r.form
+ headers = r.headers
+
+ try:
+ headers, body, status = oauth_server.create_token_response(uri, http_method, body, headers, credentials)
+ frappe.local.response = frappe._dict(json.loads(body))
+ except FatalClientError as e:
+ return e
+
+
+@frappe.whitelist(allow_guest=True)
+def revoke_token(*args, **kwargs):
+ r = frappe.request
+ uri = r.url
+ http_method = r.method
+ body = r.form
+ headers = r.headers
+
+ headers, body, status = oauth_server.create_revocation_response(uri, headers=headers, body=body, http_method=http_method)
+
+ frappe.local.response['http_status_code'] = status
+ if status == 200:
+ return "success"
+ else:
+ return "bad request"
\ No newline at end of file
diff --git a/frappe/integrations/doctype/oauth_provider_settings/__init__.py b/frappe/integrations/doctype/oauth_provider_settings/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.js b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.js
new file mode 100644
index 0000000000..6d7d071934
--- /dev/null
+++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('OAuth Provider Settings', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json
new file mode 100644
index 0000000000..b48c50a000
--- /dev/null
+++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json
@@ -0,0 +1,87 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "beta": 0,
+ "creation": "2016-09-03 11:42:42.575525",
+ "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": "skip_authorization",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Skip Authorization",
+ "length": 0,
+ "no_copy": 0,
+ "options": "Force\nAuto",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 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": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 1,
+ "istable": 0,
+ "max_attachments": 0,
+ "modified": "2016-10-20 00:13:21.988739",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "OAuth Provider Settings",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 0,
+ "if_owner": 0,
+ "import": 0,
+ "is_custom": 0,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 0,
+ "role": "System Manager",
+ "set_user_permissions": 0,
+ "share": 1,
+ "submit": 0,
+ "write": 1
+ }
+ ],
+ "quick_entry": 1,
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_seen": 0
+}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py
new file mode 100644
index 0000000000..2bf086e0fe
--- /dev/null
+++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+from frappe import _
+
+class OAuthProviderSettings(Document):
+ pass
+
+def get_oauth_settings():
+ """Returns oauth settings"""
+ out = frappe._dict({
+ "skip_authorization" : frappe.db.get_value("OAuth Provider Settings", None, "skip_authorization")
+ })
+
+ return out
\ No newline at end of file
diff --git a/frappe/oauth.py b/frappe/oauth.py
new file mode 100644
index 0000000000..22e1f17562
--- /dev/null
+++ b/frappe/oauth.py
@@ -0,0 +1,282 @@
+import frappe, urllib
+
+from urlparse import parse_qs, urlparse
+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
+
+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.
+ """
+ auth_grant = AuthorizationCodeGrant(request_validator)
+ refresh_grant = RefreshTokenGrant(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},
+ default_token_type=bearer)
+ TokenEndpoint.__init__(self, default_grant_type='authorization_code',
+ grant_types={
+ 'authorization_code': auth_grant,
+ 'refresh_token': refresh_grant,
+ },
+ default_token_type=bearer)
+ ResourceEndpoint.__init__(self, default_token='Bearer',
+ token_types={'Bearer': bearer})
+ RevocationEndpoint.__init__(self, request_validator)
+
+
+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 })
+ if cli_id:
+ request.client = frappe.get_doc("OAuth Client", client_id).as_dict()
+ return True
+ else:
+ return False
+
+ def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs):
+ # 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(';')
+
+ if redirect_uri in redirect_uris:
+ return True
+ else:
+ return False
+
+ def get_default_redirect_uri(self, client_id, request, *args, **kwargs):
+ # 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')
+ return redirect_uri
+
+ def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
+ # Is the client allowed to access the requested scopes?
+ client_scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(';')
+
+ are_scopes_valid = True
+
+ for scp in scopes:
+ are_scopes_valid = are_scopes_valid and True if scp in client_scopes else False
+
+ return are_scopes_valid
+
+ def get_default_scopes(self, client_id, request, *args, **kwargs):
+ # Scopes a client will authorize for if none are supplied in the
+ # authorization request.
+ scopes = frappe.db.get_value("OAuth Client", client_id, 'scopes').split(';')
+ 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".
+
+ return (client.response_type.lower() == response_type)
+
+
+ # Post-authorization
+
+ def save_authorization_code(self, client_id, code, request, *args, **kwargs):
+
+ cookie_dict = get_cookie_dict_from_headers(request)
+
+ oac = frappe.new_doc('OAuth Authorization Code')
+ oac.scopes = ';'.join(request.scopes)
+ oac.redirect_uri_bound_to_authorization_code = request.redirect_uri
+ oac.client = client_id
+ oac.user = urllib.unquote(cookie_dict['user_id'])
+ oac.authorization_code = code['code']
+ oac.save(ignore_permissions=True)
+ frappe.db.commit()
+
+ def authenticate_client(self, request, *args, **kwargs):
+
+ cookie_dict = get_cookie_dict_from_headers(request)
+
+ #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.
+ if frappe.form_dict.has_key("refresh_token"):
+ oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", {"refresh_token": frappe.form_dict["refresh_token"]}, 'client'))
+ else:
+ oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], 'client'))
+ try:
+ request.client = request.client or oc.as_dict()
+ except Exception, e:
+ print "Failed body authentication: Application %s does not exist".format(cid=request.client_id)
+
+ return frappe.session.user == urllib.unquote(cookie_dict.get('user_id', "Guest"))
+
+ def authenticate_client_id(self, client_id, request, *args, **kwargs):
+ cli_id = frappe.db.get_value('OAuth Client', client_id, 'name')
+ if not cli_id:
+ # Don't allow public (non-authenticated) clients
+ return False
+ else:
+ request["client"] = frappe.get_doc("OAuth Client", cli_id)
+ return True
+
+ def validate_code(self, client_id, code, client, request, *args, **kwargs):
+ # 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"})
+
+ 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(';')
+ request.user = frappe.db.get_value("OAuth Authorization Code", code, 'user')
+ return True
+ else:
+ 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):
+ # 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"])
+
+ def save_bearer_token(self, token, request, *args, **kwargs):
+ # Remember to associate it with request.scopes, request.user and
+ # request.client. The two former will be set when you validate
+ # the authorization code. Don't forget to save both the
+ # access_token and the refresh_token and set expiration for the
+ # access_token to now + expires_in seconds.
+
+ otoken = frappe.new_doc("OAuth Bearer Token")
+ otoken.client = request.client['name']
+ otoken.user = request.user
+ otoken.scopes = ";".join(request.scopes)
+ otoken.access_token = token['access_token']
+ otoken.refresh_token = token['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")
+ return default_redirect_uri
+
+ def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
+ # Authorization codes are use once, invalidate it when a Bearer token
+ # has been acquired.
+
+ frappe.db.set_value("OAuth Authorization Code", code, "validity", "Invalid")
+ frappe.db.commit()
+
+ # Protected resource request
+
+ def validate_bearer_token(self, token, scopes, request):
+ # Remember to check expiration and scope membership
+ otoken = frappe.get_doc("OAuth Bearer Token", token) #{"access_token": str(token)})
+ is_token_valid = (frappe.utils.datetime.datetime.now() < otoken.expiration_time) \
+ and otoken.status != "Revoked"
+ client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(';')
+ are_scopes_valid = True
+ for scp in scopes:
+ 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})
+ return obearer_token.scopes
+
+ def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
+ """Revoke an access or refresh token.
+
+ :param token: The token string.
+ :param token_type_hint: access_token or refresh_token.
+ :param request: The HTTP Request (oauthlib.common.Request)
+
+ Method is used by:
+ - Revocation Endpoint
+ """
+ otoken = None
+
+ if token_type_hint == "access_token":
+ 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')
+ else:
+ 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.
+
+ # 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
+
+ # Method is used by:
+ # - Authorization Code Grant (indirectly by issuing refresh tokens)
+ # - Resource Owner Password Credentials Grant (also indirectly)
+ # - Refresh Token Grant
+ # """
+ # raise NotImplementedError('Subclasses must implement this method.')
+
+ otoken = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"})
+
+ if not otoken:
+ return False
+ else:
+ return True
+
+ #TODO: Validate scopes.
+
+def get_cookie_dict_from_headers(r):
+ if r.headers.get('Cookie'):
+ cookie = r.headers.get('Cookie')
+ cookie = cookie.split("; ")
+ cookie_dict = {k:v for k,v in (x.split('=') for x in cookie)}
+ return cookie_dict
+ else:
+ return {}
diff --git a/frappe/templates/includes/oauth_confirmation.html b/frappe/templates/includes/oauth_confirmation.html
new file mode 100644
index 0000000000..3b0868e34d
--- /dev/null
+++ b/frappe/templates/includes/oauth_confirmation.html
@@ -0,0 +1,47 @@
+{% if not error %}
+
+
+ {{ client_id }} wants to access the following details from your account
+
+
+
+ {% for dtl in details %}
+ - {{ dtl.title() }}
+ {% endfor %}
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+{% else %}
+
+
+ Authorization error for {{ client_id }}
+
+
+ An unexpected error occurred while authorizing {{ client_id }}.
+ {{ error }}
+
+ -
+
+
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index c198014550..486e96a8a7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -38,3 +38,4 @@ zxcvbn
psutil
unittest-xml-reporting
xlwt
+oauthlib