Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 
 

261 rinda
7.6 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # License: MIT. See LICENSE
  3. import base64
  4. import binascii
  5. import json
  6. from urllib.parse import urlencode, urlparse
  7. import frappe
  8. import frappe.client
  9. import frappe.handler
  10. from frappe import _
  11. from frappe.utils.response import build_response
  12. from frappe.utils.data import sbool
  13. def handle():
  14. """
  15. Handler for `/api` methods
  16. ### Examples:
  17. `/api/method/{methodname}` will call a whitelisted method
  18. `/api/resource/{doctype}` will query a table
  19. examples:
  20. - `?fields=["name", "owner"]`
  21. - `?filters=[["Task", "name", "like", "%005"]]`
  22. - `?limit_start=0`
  23. - `?limit_page_length=20`
  24. `/api/resource/{doctype}/{name}` will point to a resource
  25. `GET` will return doclist
  26. `POST` will insert
  27. `PUT` will update
  28. `DELETE` will delete
  29. `/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
  30. """
  31. parts = frappe.request.path[1:].split("/",3)
  32. call = doctype = name = None
  33. if len(parts) > 1:
  34. call = parts[1]
  35. if len(parts) > 2:
  36. doctype = parts[2]
  37. if len(parts) > 3:
  38. name = parts[3]
  39. if call=="method":
  40. frappe.local.form_dict.cmd = doctype
  41. return frappe.handler.handle()
  42. elif call=="resource":
  43. if "run_method" in frappe.local.form_dict:
  44. method = frappe.local.form_dict.pop("run_method")
  45. doc = frappe.get_doc(doctype, name)
  46. doc.is_whitelisted(method)
  47. if frappe.local.request.method=="GET":
  48. if not doc.has_permission("read"):
  49. frappe.throw(_("Not permitted"), frappe.PermissionError)
  50. frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
  51. if frappe.local.request.method=="POST":
  52. if not doc.has_permission("write"):
  53. frappe.throw(_("Not permitted"), frappe.PermissionError)
  54. frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
  55. frappe.db.commit()
  56. else:
  57. if name:
  58. if frappe.local.request.method=="GET":
  59. doc = frappe.get_doc(doctype, name)
  60. if not doc.has_permission("read"):
  61. raise frappe.PermissionError
  62. frappe.local.response.update({"data": doc})
  63. if frappe.local.request.method=="PUT":
  64. data = get_request_form_data()
  65. doc = frappe.get_doc(doctype, name, for_update=True)
  66. if "flags" in data:
  67. del data["flags"]
  68. # Not checking permissions here because it's checked in doc.save
  69. doc.update(data)
  70. frappe.local.response.update({
  71. "data": doc.save().as_dict()
  72. })
  73. if doc.parenttype and doc.parent:
  74. frappe.get_doc(doc.parenttype, doc.parent).save()
  75. frappe.db.commit()
  76. if frappe.local.request.method == "DELETE":
  77. # Not checking permissions here because it's checked in delete_doc
  78. frappe.delete_doc(doctype, name, ignore_missing=False)
  79. frappe.local.response.http_status_code = 202
  80. frappe.local.response.message = "ok"
  81. frappe.db.commit()
  82. elif doctype:
  83. if frappe.local.request.method == "GET":
  84. # set fields for frappe.get_list
  85. if frappe.local.form_dict.get("fields"):
  86. frappe.local.form_dict["fields"] = json.loads(frappe.local.form_dict["fields"])
  87. # set limit of records for frappe.get_list
  88. frappe.local.form_dict.setdefault(
  89. "limit_page_length",
  90. frappe.local.form_dict.limit or frappe.local.form_dict.limit_page_length or 20,
  91. )
  92. # convert strings to native types - only as_dict and debug accept bool
  93. for param in ["as_dict", "debug"]:
  94. param_val = frappe.local.form_dict.get(param)
  95. if param_val is not None:
  96. frappe.local.form_dict[param] = sbool(param_val)
  97. # evaluate frappe.get_list
  98. data = frappe.call(frappe.client.get_list, doctype, **frappe.local.form_dict)
  99. # set frappe.get_list result to response
  100. frappe.local.response.update({"data": data})
  101. if frappe.local.request.method == "POST":
  102. # fetch data from from dict
  103. data = get_request_form_data()
  104. data.update({"doctype": doctype})
  105. # insert document from request data
  106. doc = frappe.get_doc(data).insert()
  107. # set response data
  108. frappe.local.response.update({"data": doc.as_dict()})
  109. # commit for POST requests
  110. frappe.db.commit()
  111. else:
  112. raise frappe.DoesNotExistError
  113. else:
  114. raise frappe.DoesNotExistError
  115. return build_response("json")
  116. def get_request_form_data():
  117. if frappe.local.form_dict.data is None:
  118. data = frappe.safe_decode(frappe.local.request.get_data())
  119. else:
  120. data = frappe.local.form_dict.data
  121. return frappe.parse_json(data)
  122. def validate_auth():
  123. """
  124. Authenticate and sets user for the request.
  125. """
  126. authorization_header = frappe.get_request_header("Authorization", str()).split(" ")
  127. if len(authorization_header) == 2:
  128. validate_oauth(authorization_header)
  129. validate_auth_via_api_keys(authorization_header)
  130. validate_auth_via_hooks()
  131. def validate_oauth(authorization_header):
  132. """
  133. Authenticate request using OAuth and set session user
  134. Args:
  135. authorization_header (list of str): The 'Authorization' header containing the prefix and token
  136. """
  137. from frappe.integrations.oauth2 import get_oauth_server
  138. from frappe.oauth import get_url_delimiter
  139. form_dict = frappe.local.form_dict
  140. token = authorization_header[1]
  141. req = frappe.request
  142. parsed_url = urlparse(req.url)
  143. access_token = {"access_token": token}
  144. uri = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token)
  145. http_method = req.method
  146. headers = req.headers
  147. body = req.get_data()
  148. if req.content_type and "multipart/form-data" in req.content_type:
  149. body = None
  150. try:
  151. required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(get_url_delimiter())
  152. valid, oauthlib_request = get_oauth_server().verify_request(uri, http_method, body, headers, required_scopes)
  153. if valid:
  154. frappe.set_user(frappe.db.get_value("OAuth Bearer Token", token, "user"))
  155. frappe.local.form_dict = form_dict
  156. except AttributeError:
  157. pass
  158. def validate_auth_via_api_keys(authorization_header):
  159. """
  160. Authenticate request using API keys and set session user
  161. Args:
  162. authorization_header (list of str): The 'Authorization' header containing the prefix and token
  163. """
  164. try:
  165. auth_type, auth_token = authorization_header
  166. authorization_source = frappe.get_request_header("Frappe-Authorization-Source")
  167. if auth_type.lower() == 'basic':
  168. api_key, api_secret = frappe.safe_decode(base64.b64decode(auth_token)).split(":")
  169. validate_api_key_secret(api_key, api_secret, authorization_source)
  170. elif auth_type.lower() == 'token':
  171. api_key, api_secret = auth_token.split(":")
  172. validate_api_key_secret(api_key, api_secret, authorization_source)
  173. except binascii.Error:
  174. frappe.throw(_("Failed to decode token, please provide a valid base64-encoded token."), frappe.InvalidAuthorizationToken)
  175. except (AttributeError, TypeError, ValueError):
  176. pass
  177. def validate_api_key_secret(api_key, api_secret, frappe_authorization_source=None):
  178. """frappe_authorization_source to provide api key and secret for a doctype apart from User"""
  179. doctype = frappe_authorization_source or 'User'
  180. doc = frappe.db.get_value(
  181. doctype=doctype,
  182. filters={"api_key": api_key},
  183. fieldname=["name"]
  184. )
  185. form_dict = frappe.local.form_dict
  186. doc_secret = frappe.utils.password.get_decrypted_password(doctype, doc, fieldname='api_secret')
  187. if api_secret == doc_secret:
  188. if doctype == 'User':
  189. user = frappe.db.get_value(
  190. doctype="User",
  191. filters={"api_key": api_key},
  192. fieldname=["name"]
  193. )
  194. else:
  195. user = frappe.db.get_value(doctype, doc, 'user')
  196. if frappe.local.login_manager.user in ('', 'Guest'):
  197. frappe.set_user(user)
  198. frappe.local.form_dict = form_dict
  199. def validate_auth_via_hooks():
  200. for auth_hook in frappe.get_hooks('auth_hooks', []):
  201. frappe.get_attr(auth_hook)()