# 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": 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() }) 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.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(): from frappe.oauth import get_url_delimiter 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": from frappe.integrations.oauth2 import get_oauth_server 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(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", None).split(" ") if frappe.get_request_header("Authorization") else None authorization_source = frappe.get_request_header("Frappe-Authorization-Source", None) if authorization_header and authorization_header[0] == 'Basic': token = frappe.safe_decode(base64.b64decode(authorization_header[1])).split(":") elif authorization_header and authorization_header[0] == 'token': token = authorization_header[1].split(":") if authorization_source: 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 """ if not frappe_authorization_source: user = frappe.db.get_value( doctype="User", filters={"api_key": api_key}, fieldname=["name"] ) form_dict = frappe.local.form_dict user_secret = frappe.utils.password.get_decrypted_password ("User", user, fieldname='api_secret') if api_secret == user_secret: frappe.set_user(user) frappe.local.form_dict = form_dict else: doc = frappe.db.get_value( doctype=frappe_authorization_source, filters={"api_key": api_key}, fieldname=["name"] ) form_dict = frappe.local.form_dict doc_secret = frappe.utils.password.get_decrypted_password (frappe_authorization_source, doc, fieldname='api_secret') if api_secret == doc_secret: user = frappe.db.get_value(frappe_authorization_source, doc, 'user') frappe.set_user(user) frappe.local.form_dict = form_dict