* OAuth 2.0 Classes Added Added oauthlib in requirement.txt Added class WebApplicationServer for Authorization code grant and Bearer tokens. Added class OAuthWebRequestValidator for Validating Oauth Request for Web Application * copied code from mnt_oauth needs review * [New] OAuth2 Doctypes and endpoints added Integrations > OAuth Provider Settings Integration Broker > OAuth Bearer Token Integration Broker > OAuth Authorization Code Integration Broker > OAuth Client endpoints for authorize, approve, get_token, revoke_token and test_resource * oauth2.py: renamed skipauth to skip_auth * [Fix] Parse URL instead of storing it in settings * [Fix] get skip_auth from OAuth Provider Settings * Success URL format. Failure URL added. Confirmation dialog layout changed. * Validate access token if passed during use of REST API * OAuth Confirmation colours fixed * Multiple Changes Added links for OAuth under Integrations in Module list. Updated permissions on OAuth doctypes. Updated layout of OAuth Client doctype. * [Docs] Integrations > How to setup OAuth * [Docs] Integration > Using OAuth * [Fix] get_token endpoint must to handle POST request * [Fix] http verbs and responses for OAuth 2.0 Endpoints * [Fix] accept oauth2 access_token from headers * Removed unused imports from api.pyversion-14
@@ -8,6 +8,9 @@ import frappe.handler | |||
import frappe.client | |||
from frappe.utils.response import build_response | |||
from frappe import _ | |||
from urlparse import urlparse | |||
from urllib import urlencode | |||
from frappe.integration_broker.oauth2 import oauth_server | |||
def handle(): | |||
""" | |||
@@ -32,6 +35,27 @@ def handle(): | |||
`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method | |||
""" | |||
form_dict = frappe.local.form_dict | |||
authorization_header = frappe.get_request_header("Authorization").split(" ") if frappe.get_request_header("Authorization") else None | |||
if authorization_header and authorization_header[0].lower() == "bearer": | |||
token = authorization_header[1] | |||
r = frappe.request | |||
parsed_url = urlparse(r.url) | |||
access_token = { "access_token": token} | |||
uri = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token) | |||
http_method = r.method | |||
body = r.get_data() | |||
headers = r.headers | |||
required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(";") | |||
valid, oauthlib_request = oauth_server.verify_request(uri, http_method, body, headers, required_scopes) | |||
if valid: | |||
frappe.set_user(frappe.db.get_value("OAuth Bearer Token", token, "user")) | |||
frappe.local.form_dict = form_dict | |||
parts = frappe.request.path[1:].split("/",3) | |||
call = doctype = name = None | |||
@@ -224,7 +224,17 @@ def get_data(): | |||
"type": "doctype", | |||
"name": "Integration Service", | |||
"description": _("Centralize access to Integrations"), | |||
} | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "OAuth Client", | |||
"description": _("Register OAuth Client App"), | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "OAuth Provider Settings", | |||
"description": _("Settings for OAuth Provider"), | |||
}, | |||
] | |||
}, | |||
{ | |||
@@ -0,0 +1,47 @@ | |||
# How to setup oauth? | |||
<a href="https://tools.ietf.org/html/rfc6749">OAuth 2.0</a> provider based on <a href="https://github.com/idan/oauthlib">oauthlib</a> is built into frappe. Third party apps can now access resources of users based on Frappe Role and User permission system. To setup an app to access | |||
## OAuth defines four roles | |||
#### resource owner | |||
An entity capable of granting access to a protected resource. When the resource owner is a person, it is referred to as an end-user. | |||
#### resource server | |||
The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens. | |||
#### client | |||
An application making protected resource requests on behalf of the resource owner and with its authorization. The term "client" does not imply any particular implementation characteristics (e.g., | |||
whether the application executes on a server, a desktop, or other devices). | |||
#### authorization server | |||
The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorization. | |||
## Setup OAuth Provider | |||
System Managers can setup behavior of confirmation message as `Force` or `Auto` in OAuth Provider Settings. | |||
If Force is selected the system will always ask for user's confirmation. If Auto is selected system asks for the confirmation only if there are no active tokens for the user. | |||
Go to | |||
> Setup > Integrations > OAuth Provider Settings | |||
<img class="screenshot" src="assets/frappe/img/oauth_provider_settings.png"> | |||
## Add a Client App | |||
As a System Manager go to | |||
> Setup > Integrations > OAuth Client | |||
<img class="screenshot" src="assets/frappe/img/oauth2_client_app.png"> | |||
To add a client fill in the following details | |||
1. **App Name** : Enter App Name e.g. CAVS | |||
2. **Skip Authorization** : If this is checked, during authentication there won't be me any confirmation message | |||
3. **Scopes** : List of scopes shown to user along with confirmation message. scopes are separated by semicolon ';' | |||
4. **Redirect URIs** : List of Redirect URIs separated by semicolon ';' | |||
5. **Default Redirect URIs** : Default Redirect URI from list of Redirect URIs | |||
6. **Grant Type**: select `Authorization Code` | |||
7. **Response Type**: select `Code` |
@@ -1 +1,3 @@ | |||
rest_api | |||
how_to_setup_oauth | |||
using_oauth |
@@ -0,0 +1,109 @@ | |||
# Using OAuth | |||
Once the client and provider settings are entered, following steps can be used to start using OAuth 2.0 | |||
### Authorization Code Endpoint | |||
#### Authorization Request | |||
URL: | |||
``` | |||
[GET] 0.0.0.0:8000/api/method/frappe.integration_broker.oauth2.authorize | |||
``` | |||
Params: | |||
``` | |||
client_id = <client ID of registered app> | |||
scope = <access scope, e.g. scope=project will allow you to access project doctypes.> | |||
response_type = "code" | |||
redirect_uri = <redirect uri from OAuth Client> | |||
``` | |||
#### Confirmation Dialog | |||
<img class="screenshot" src="assets/frappe/img/oauth_confirmation_page.png"> | |||
Click 'Allow' to receive authorization code in redirect uri. | |||
``` | |||
http://localhost:3000/oauth_code?code=plkj2mqDLwaLJAgDBAkyR1W8Co08Ud | |||
``` | |||
If user clicks 'Deny' receive error | |||
``` | |||
http://localhost:3000/oauth_code?error=access_denied | |||
``` | |||
### Token Endpoints | |||
#### Get Access Token | |||
URL: | |||
``` | |||
[POST] 0.0.0.0:8000/api/method/frappe.integration_broker.oauth2.get_token | |||
``` | |||
Params: | |||
``` | |||
grant_type = "authorization_code" | |||
code = <code received in redirect uri after confirmation> | |||
redirect_uri = <valid redirect uri> | |||
client_id = <client ID of app from OAuth Client> | |||
``` | |||
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 = <refresh token from the response of get_token call with grant_type=authorization_code> | |||
redirect_uri = <valid redirect uri> | |||
client_id = <client ID of app from OAuth Client> | |||
``` | |||
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 = <access token to be revoked> | |||
``` | |||
Success Response | |||
``` | |||
status : 200 | |||
{"message": "success"} | |||
``` | |||
Error Response: | |||
``` | |||
status : 400 | |||
{"message": "bad request"} | |||
``` | |||
### Accessing Resource | |||
Add header `Authorizaton: Bearer <valid_bearer_token>` to Frappe's REST API endpoints to access user's resource |
@@ -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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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 |
@@ -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 |
@@ -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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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) | |||
@@ -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 |
@@ -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) { | |||
} | |||
}); |
@@ -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.<br> 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<br>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 | |||
} |
@@ -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 |
@@ -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 |
@@ -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" |
@@ -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) { | |||
} | |||
}); |
@@ -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 | |||
} |
@@ -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 |
@@ -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 {} |
@@ -0,0 +1,47 @@ | |||
{% if not error %} | |||
<div class="panel panel-default"> | |||
<div class="panel-heading"> | |||
<h3 class="panel-title">{{ client_id }} wants to access the following details from your account</h3> | |||
</div> | |||
<div class="panel-body"> | |||
<ul class="list-group"> | |||
{% for dtl in details %} | |||
<li class="list-group-item">{{ dtl.title() }}</li> | |||
{% endfor %} | |||
</ul> | |||
<ul class="list-inline"> | |||
<li> | |||
<button id="allow" class="btn btn-sm btn-primary">Allow</button> | |||
</li> | |||
<li> | |||
<button id="deny" class="btn btn-sm btn-default">Deny</button> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
<script type="text/javascript"> | |||
frappe.ready(function() { | |||
$('#allow').on('click', function(event) { | |||
window.location.replace("{{ success_url|string }}"); | |||
}); | |||
$('#deny').on('click', function(event) { | |||
window.location.replace("{{ failure_url|string }}"); | |||
}); | |||
}); | |||
</script> | |||
{% else %} | |||
<div class="panel panel-danger"> | |||
<div class="panel-heading"> | |||
<h3 class="panel-title">Authorization error for {{ client_id }}</h3> | |||
</div> | |||
<div class="panel-body"> | |||
<p>An unexpected error occurred while authorizing {{ client_id }}.</p> | |||
<h4>{{ error }}</h4> | |||
<ul class="list-inline"> | |||
<li> | |||
<button class="btn btn-sm btn-default">OK</button> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
{% endif %} |
@@ -38,3 +38,4 @@ zxcvbn | |||
psutil | |||
unittest-xml-reporting | |||
xlwt | |||
oauthlib |