fix: restrict state-changing whitelisted endpoints to POST (#56858)

Add methods=["POST"] to 50 whitelisted functions that create or modify
documents (get_doc followed by insert/save/submit), so they can no
longer be invoked via GET requests.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rohitwaghchaure
2026-07-03 18:47:13 +05:30
committed by GitHub
parent 9c911438f1
commit 341a07dffa
38 changed files with 52 additions and 52 deletions

View File

@@ -188,7 +188,7 @@ def get_closing_balance_as_per_statement(bank_account: str, date: str):
return {"balance": 0, "date": None}
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_closing_balance_as_per_statement(bank_account: str, date: str | datetime.date, balance: float):
"""
Set the closing balance as per statement for a bank account and date

View File

@@ -116,7 +116,7 @@ def get_account_balance(bank_account: str, till_date: str | date, company: str):
return flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def update_bank_transaction(
bank_transaction_name: str, reference_number: str, party_type: str | None = None, party: str | None = None
):
@@ -146,7 +146,7 @@ def update_bank_transaction(
)[0]
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_journal_entry_bts(
bank_transaction_name: str,
reference_number: str | None = None,
@@ -305,7 +305,7 @@ def create_journal_entry_bts(
return reconcile_vouchers(bank_transaction_name, vouchers, is_new_voucher=True)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_payment_entry_bts(
bank_transaction_name: str,
reference_number: str | None = None,
@@ -500,7 +500,7 @@ def create_bulk_internal_transfer(bank_transaction_names: list[str | int], bank_
return output
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_internal_transfer(
bank_transaction_name: str | int,
posting_date: str | date,
@@ -1057,7 +1057,7 @@ def get_auto_reconcile_message(partially_reconciled, reconciled):
return alert_message, indicator
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def reconcile_vouchers(bank_transaction_name: str | int, vouchers: str | list, is_new_voucher: bool = False):
# updated clear date of all the vouchers based on the bank transaction
vouchers = frappe.parse_json(vouchers)

View File

@@ -397,7 +397,7 @@ def unreconcile_transaction(transaction_name: str | int):
frappe.get_doc(voucher["doctype"], voucher["name"]).cancel()
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def unreconcile_transaction_entry(bank_transaction_id: str | int, voucher_type: str, voucher_id: str | int):
"""
Removes a single payment entry from a bank transaction - for example only undoing one voucher instead of undoing the entire transaction

View File

@@ -34,7 +34,7 @@ def upload_bank_statement():
return {"columns": columns, "data": data}
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_bank_entries(columns: str, data: str | list, bank_account: str):
header_map = get_header_mapping(columns, bank_account)

View File

@@ -184,7 +184,7 @@ class BisectAccountingStatements(Document):
self.get_report_summary()
self.update_node()
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def bisect_left(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
@@ -198,7 +198,7 @@ class BisectAccountingStatements(Document):
else:
frappe.msgprint(_("No more children on Left"))
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def bisect_right(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
@@ -212,7 +212,7 @@ class BisectAccountingStatements(Document):
else:
frappe.msgprint(_("No more children on Right"))
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def move_up(self):
if self.current_node is not None:
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)

View File

@@ -878,7 +878,7 @@ def get_fiscal_year_date_range(from_fiscal_year, to_fiscal_year):
return from_year.year_start_date, to_year.year_end_date
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def revise_budget(budget_name: str):
old_budget = frappe.get_doc("Budget", budget_name)

View File

@@ -46,7 +46,7 @@ class ChequePrintTemplate(Document):
pass
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_or_update_cheque_print_format(template_name: str):
frappe.only_for("System Manager")

View File

@@ -718,7 +718,7 @@ class PaymentRequest(Document):
row_number += TO_SKIP_NEW_ROW
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_payment_request(**args):
"""Make payment request"""

View File

@@ -224,7 +224,7 @@ def get_children(doctype: str, parent: str | None = None, location: str | None =
)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args

View File

@@ -55,7 +55,7 @@ def make_supplier_quotation_from_rfq(
# This method is used to make supplier quotation from supplier's portal.
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_supplier_quotation(doc: str | Document | dict):
doc = frappe.parse_json(doc)

View File

@@ -185,7 +185,7 @@ def refresh_scorecards():
frappe.get_doc("Supplier Scorecard", sc_name).save()
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_all_scorecards(docname: str):
sc = frappe.get_doc("Supplier Scorecard", docname)
supplier = frappe.get_doc("Supplier", sc.supplier)

View File

@@ -1724,7 +1724,7 @@ def get_missing_company_details(doctype: str, docname: str):
}
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def update_company_master_and_address(current_doctype: str, name: str, company: str, details: dict | str):
from frappe.utils import validate_email_address

View File

@@ -653,7 +653,7 @@ def check_item_quality_inspection(doctype: str, docstatus: str | int, items: str
return [item for item in items if item.get("item_code") in inspection_required_items]
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_quality_inspections(
company: str, doctype: str, docname: str, items: str | list, inspection_type: str
):

View File

@@ -380,7 +380,7 @@ def get_lead_with_phone_number(number):
return lead
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_lead_to_prospect(lead: str, prospect: str):
prospect = frappe.get_doc("Prospect", prospect)
prospect.append("leads", {"lead": lead})

View File

@@ -110,7 +110,7 @@ def make_quotation(source_name: str, target_doc: str | Document | None = None):
return target_doc
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_lead_from_communication(communication: str, ignore_communication_links: bool = False):
"""raise a issue from email"""

View File

@@ -124,7 +124,7 @@ def make_supplier_quotation(source_name: str, target_doc: str | Document | None
return doclist
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_opportunity_from_communication(
communication: str, company: str, ignore_communication_links: bool = False
):

View File

@@ -389,7 +389,7 @@ def get_item_details(item_code: str):
}
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_multiple_status(names: str | list[str], status: str):
names = frappe.parse_json(names)
for name in names:

View File

@@ -50,7 +50,7 @@ def get_plaid_configuration():
return "disabled"
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_institution(token: str, response: str | dict):
response = frappe.parse_json(response)
@@ -79,7 +79,7 @@ def add_institution(token: str, response: str | dict):
return bank
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_bank_accounts(response: str | dict, bank: str | dict, company: str):
response = frappe.parse_json(response)
bank = frappe.parse_json(bank)

View File

@@ -1070,7 +1070,7 @@ def get_bom_operations(doctype: str, txt: str, searchfield: str, start: int, pag
return frappe.get_all("BOM Operation", filters=filters, fields=["operation"], as_list=1)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_work_order_ops(name: str):
po = frappe.get_doc("Work Order", name)
po.set_work_order_operations()

View File

@@ -223,7 +223,7 @@ class Workstation(Document):
return schedule_date
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def start_job(self, job_card: str, from_time: DateTimeLikeObject, employee: str):
doc = frappe.get_doc("Job Card", job_card)
doc.check_permission("write")
@@ -233,7 +233,7 @@ class Workstation(Document):
return doc
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def complete_job(self, job_card: str, qty: float, to_time: DateTimeLikeObject):
doc = frappe.get_doc("Job Card", job_card)
doc.check_permission("submit")

View File

@@ -628,7 +628,7 @@ def allow_to_make_project_update(project, time, frequency):
return True
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_duplicate_project(prev_doc: str | dict, project_name: str):
"""Create duplicate project based on the old project"""
import json
@@ -779,7 +779,7 @@ def create_kanban_board_if_not_exists(project: str):
return True
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_project_status(project: str, status: str):
"""
set status for project and all related tasks

View File

@@ -369,7 +369,7 @@ def get_project(doctype: str, txt: str, searchfield: str, start: int, page_len:
)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_multiple_status(names: str | list, status: str):
names = frappe.parse_json(names)
for name in names:
@@ -451,7 +451,7 @@ def get_children(
return tasks
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args
@@ -465,7 +465,7 @@ def add_node():
frappe.get_doc(args).insert()
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_multiple_tasks(data: str | list, parent: str):
data = frappe.parse_json(data)
new_doc = {"doctype": "Task", "parent_task": parent if parent != "All Tasks" else ""}

View File

@@ -148,7 +148,7 @@ def get_children(
)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args

View File

@@ -196,7 +196,7 @@ class Customer(TransactionBase):
if sum(member.allocated_percentage or 0 for member in self.sales_team) != 100:
frappe.throw(_("Total contribution percentage should be equal to 100"))
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def get_customer_group_details(self):
doc = frappe.get_doc("Customer Group", self.customer_group)
self.accounts = []

View File

@@ -840,7 +840,7 @@ def set_delivery_date(items: list, sales_order: str) -> None:
item.schedule_date = delivery_by_bundle.get(item.product_bundle)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_work_orders(items: str | dict, sales_order: str, company: str, project: str | None = None):
"""Make Work Orders against the given Sales Order for the given `items`"""
items = frappe.parse_json(items).get("items")

View File

@@ -347,7 +347,7 @@ def check_opening_entry(user: str):
return open_vouchers
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_opening_voucher(pos_profile: str, company: str, balance_details: str | list):
balance_details = frappe.parse_json(balance_details)
@@ -438,7 +438,7 @@ def get_past_order_list(search_term: str, status: str, limit: int = 20):
return invoice_list
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def set_customer_info(fieldname: str, customer: str, value: str = ""):
customer_doc = frappe.get_doc("Customer", customer)
customer_doc.check_permission("write")

View File

@@ -1007,7 +1007,7 @@ def get_children(doctype: str, parent: str | None = None, company: str | None =
)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args
@@ -1118,7 +1118,7 @@ def get_billing_shipping_address(
return {"primary_address": primary_address, "shipping_address": shipping_address}
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_transaction_deletion_request(company: str):
frappe.only_for("System Manager")

View File

@@ -95,7 +95,7 @@ def get_children(
return frappe.get_all("Department", fields=fields, filters=filters, order_by="name")
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args

View File

@@ -432,7 +432,7 @@ def deactivate_sales_person(status: str, employee: str):
frappe.db.set_value("Sales Person", sales_person, "enabled", 0)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_user(employee: str, email: str | None = None, create_user_permission: int = 0) -> str:
emp = frappe.get_doc("Employee", employee)
emp.check_permission("write")

View File

@@ -301,7 +301,7 @@ def get_batches_by_oldest(item_code: str, warehouse: str):
return batches_dates
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def split_batch(batch_no: str, item_code: str, warehouse: str, qty: float, new_batch_id: str | None = None):
"""Split the batch into a new batch"""
batch = frappe.get_doc(doctype="Batch", item=item_code, batch_id=new_batch_id).insert()

View File

@@ -511,7 +511,7 @@ def get_material_requests_based_on_supplier(
return material_requests
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def raise_work_orders(material_request: str, company: str):
mr = frappe.get_doc("Material Request", material_request)
errors = []

View File

@@ -191,7 +191,7 @@ def get_children(
return frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_node():
from frappe.desk.treeview import make_tree_args

View File

@@ -182,7 +182,7 @@ def get_columns(filters):
]
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_reposting_entries(rows: str | list, company: str):
if isinstance(rows, str):
rows = parse_json(rows)

View File

@@ -306,7 +306,7 @@ def get_columns():
]
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def create_reposting_entries(rows: str | list, item_code: str | None = None, warehouse: str | None = None):
if isinstance(rows, str):
rows = parse_json(rows)

View File

@@ -22,7 +22,7 @@ def make_subcontract_return(source_name: str, target_doc: Document | str | None
return make_return_doc("Subcontracting Receipt", source_name, target_doc)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_purchase_receipt(
source_name: Document | str,
target_doc: Document | str | None = None,

View File

@@ -117,7 +117,7 @@ class Issue(Document):
communication.flags.ignore_mandatory = True
communication.save()
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def split_issue(self, subject: str, communication_id: str):
from copy import deepcopy
@@ -273,7 +273,7 @@ def make_task(source_name: str, target_doc: str | Document | None = None):
return get_mapped_doc("Issue", source_name, {"Issue": {"doctype": "Task"}}, target_doc)
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def make_issue_from_communication(communication: str, ignore_communication_links: bool = False):
"""raise a issue from email"""

View File

@@ -779,7 +779,7 @@ def get_response_and_resolution_duration(doc):
return priority
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def reset_service_level_agreement(doctype: str, docname: str, reason: str, user: str):
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))

View File

@@ -127,7 +127,7 @@ class CallLog(Document):
self.employee_user_id = employees[0].get("user_id")
@frappe.whitelist()
@frappe.whitelist(methods=["POST"])
def add_call_summary_and_call_type(call_log: str, summary: str, call_type: str):
doc = frappe.get_doc("Call Log", call_log)
doc.type_of_call = call_type