210 lines
6.4 KiB
Python
210 lines
6.4 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# MIT License. See license.txt
|
|
from __future__ import unicode_literals
|
|
|
|
import json
|
|
import frappe
|
|
import frappe.handler
|
|
import frappe.client
|
|
from frappe.utils.response import build_response
|
|
from frappe import _
|
|
from six.moves.urllib.parse import urlparse, urlencode
|
|
import base64
|
|
|
|
def handle():
|
|
"""
|
|
Handler for `/api` methods
|
|
|
|
### Examples:
|
|
|
|
`/api/method/{methodname}` will call a whitelisted method
|
|
|
|
`/api/resource/{doctype}` will query a table
|
|
examples:
|
|
- `?fields=["name", "owner"]`
|
|
- `?filters=[["Task", "name", "like", "%005"]]`
|
|
- `?limit_start=0`
|
|
- `?limit_page_length=20`
|
|
|
|
`/api/resource/{doctype}/{name}` will point to a resource
|
|
`GET` will return doclist
|
|
`POST` will insert
|
|
`PUT` will update
|
|
`DELETE` will delete
|
|
|
|
`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
|
|
"""
|
|
|
|
validate_oauth()
|
|
validate_auth_via_api_keys()
|
|
|
|
parts = frappe.request.path[1:].split("/",3)
|
|
call = doctype = name = None
|
|
|
|
if len(parts) > 1:
|
|
call = parts[1]
|
|
|
|
if len(parts) > 2:
|
|
doctype = parts[2]
|
|
|
|
if len(parts) > 3:
|
|
name = parts[3]
|
|
|
|
if call=="method":
|
|
frappe.local.form_dict.cmd = doctype
|
|
return frappe.handler.handle()
|
|
|
|
elif call=="resource":
|
|
if "run_method" in frappe.local.form_dict:
|
|
method = frappe.local.form_dict.pop("run_method")
|
|
doc = frappe.get_doc(doctype, name)
|
|
doc.is_whitelisted(method)
|
|
|
|
if frappe.local.request.method=="GET":
|
|
if not doc.has_permission("read"):
|
|
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
|
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
|
|
|
|
if frappe.local.request.method=="POST":
|
|
if not doc.has_permission("write"):
|
|
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
|
|
|
frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
|
|
frappe.db.commit()
|
|
|
|
else:
|
|
if name:
|
|
if frappe.local.request.method=="GET":
|
|
doc = frappe.get_doc(doctype, name)
|
|
if not doc.has_permission("read"):
|
|
raise frappe.PermissionError
|
|
frappe.local.response.update({"data": doc})
|
|
|
|
if frappe.local.request.method=="PUT":
|
|
if frappe.local.form_dict.data is None:
|
|
data = json.loads(frappe.safe_decode(frappe.local.request.get_data()))
|
|
else:
|
|
data = json.loads(frappe.local.form_dict.data)
|
|
doc = frappe.get_doc(doctype, name)
|
|
|
|
if "flags" in data:
|
|
del data["flags"]
|
|
|
|
# Not checking permissions here because it's checked in doc.save
|
|
doc.update(data)
|
|
|
|
frappe.local.response.update({
|
|
"data": doc.save().as_dict()
|
|
})
|
|
|
|
if doc.parenttype and doc.parent:
|
|
frappe.get_doc(doc.parenttype, doc.parent).save()
|
|
|
|
frappe.db.commit()
|
|
|
|
if frappe.local.request.method == "DELETE":
|
|
# Not checking permissions here because it's checked in delete_doc
|
|
frappe.delete_doc(doctype, name, ignore_missing=False)
|
|
frappe.local.response.http_status_code = 202
|
|
frappe.local.response.message = "ok"
|
|
frappe.db.commit()
|
|
|
|
elif doctype:
|
|
if frappe.local.request.method == "GET":
|
|
if frappe.local.form_dict.get('fields'):
|
|
frappe.local.form_dict['fields'] = json.loads(frappe.local.form_dict['fields'])
|
|
frappe.local.form_dict.setdefault('limit_page_length', 20)
|
|
frappe.local.response.update({
|
|
"data": frappe.call(
|
|
frappe.client.get_list,
|
|
doctype,
|
|
**frappe.local.form_dict
|
|
)
|
|
})
|
|
|
|
if frappe.local.request.method == "POST":
|
|
if frappe.local.form_dict.data is None:
|
|
data = json.loads(frappe.safe_decode(frappe.local.request.get_data()))
|
|
else:
|
|
data = json.loads(frappe.local.form_dict.data)
|
|
data.update({
|
|
"doctype": doctype
|
|
})
|
|
frappe.local.response.update({
|
|
"data": frappe.get_doc(data).insert().as_dict()
|
|
})
|
|
frappe.db.commit()
|
|
else:
|
|
raise frappe.DoesNotExistError
|
|
|
|
else:
|
|
raise frappe.DoesNotExistError
|
|
|
|
return build_response("json")
|
|
|
|
|
|
def validate_oauth():
|
|
""" authentication using oauth """
|
|
from frappe.oauth import get_url_delimiter
|
|
form_dict = frappe.local.form_dict
|
|
authorization_header = frappe.get_request_header("Authorization", "").split(" ")
|
|
if authorization_header and authorization_header[0].lower() == "bearer":
|
|
from frappe.integrations.oauth2 import get_oauth_server
|
|
token = authorization_header[1]
|
|
req = frappe.request
|
|
parsed_url = urlparse(req.url)
|
|
access_token = {"access_token": token}
|
|
uri = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token)
|
|
http_method = req.method
|
|
body = req.get_data()
|
|
headers = req.headers
|
|
|
|
required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(get_url_delimiter())
|
|
|
|
valid, _oauthlib_request = get_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
|
|
|
|
|
|
def validate_auth_via_api_keys():
|
|
"""
|
|
authentication using api key and api secret
|
|
|
|
set user
|
|
"""
|
|
try:
|
|
authorization_header = frappe.get_request_header("Authorization", "").split(" ")
|
|
authorization_source = frappe.get_request_header("Frappe-Authorization-Source")
|
|
if authorization_header and authorization_header[0] == 'Basic':
|
|
token = frappe.safe_decode(base64.b64decode(authorization_header[1])).split(":")
|
|
validate_api_key_secret(token[0], token[1], authorization_source)
|
|
elif authorization_header and authorization_header[0] == 'token':
|
|
token = authorization_header[1].split(":")
|
|
validate_api_key_secret(token[0], token[1], authorization_source)
|
|
except Exception as e:
|
|
raise e
|
|
|
|
|
|
def validate_api_key_secret(api_key, api_secret, frappe_authorization_source=None):
|
|
"""frappe_authorization_source to provide api key and secret for a doctype apart from User"""
|
|
doctype = frappe_authorization_source or 'User'
|
|
doc = frappe.db.get_value(
|
|
doctype=doctype,
|
|
filters={"api_key": api_key},
|
|
fieldname=["name"]
|
|
)
|
|
form_dict = frappe.local.form_dict
|
|
doc_secret = frappe.utils.password.get_decrypted_password(doctype, doc, fieldname='api_secret')
|
|
if api_secret == doc_secret:
|
|
if doctype == 'User':
|
|
user = frappe.db.get_value(
|
|
doctype="User",
|
|
filters={"api_key": api_key},
|
|
fieldname=["name"]
|
|
)
|
|
else:
|
|
user = frappe.db.get_value(doctype, doc, 'user')
|
|
frappe.set_user(user)
|
|
frappe.local.form_dict = form_dict
|