Bug description
When i run this code,
import requests
import os
import json
# --- REQUIRED CONFIGURATION ---
SUPERSET_URL = os.getenv("SUPERSET_URL", "http://localhost:8088")
ADMIN_USERNAME = os.getenv("SUPERSET_USER", "admin")
ADMIN_PASSWORD = os.getenv("SUPERSET_PASSWORD", "admin")
# --- PROGRAM STUDY CODE MAPPING (KAPRODI) ---
# REQUIRED: Complete this dictionary with your data.
# Format: "short_program_name_from_role": numeric_program_code
KAPRODI_CODE_MAPPING = {
"if": 135,
"mesin": 131,
# "elektro": 133,
# Add all your kaprodi (program study) mappings here
}
def main():
"""Main function to run the automation process with CREATE or UPDATE logic."""
# Use a session object to maintain login cookies, which are needed for CSRF
with requests.Session() as session:
# 1. Authenticate and get CSRF token
print("1. Authenticating to Superset...")
try:
# Login
login_data = {"username": ADMIN_USERNAME, "password": ADMIN_PASSWORD, "provider": "db"}
r_login = session.post(f"{SUPERSET_URL}/api/v1/security/login", json=login_data)
r_login.raise_for_status()
access_token = r_login.json()["access_token"]
# Set headers for the entire session
session.headers.update({
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
})
print(" -> Authentication successful.")
# Get CSRF token
print(" -> Fetching CSRF token...")
r_csrf = session.get(f"{SUPERSET_URL}/api/v1/security/csrf_token/")
r_csrf.raise_for_status()
csrf_token = r_csrf.json()['result']
# Add CSRF token to headers for all future state-changing requests (POST, PUT, DELETE)
session.headers['X-CSRFToken'] = csrf_token
print(" -> CSRF token obtained.")
except requests.exceptions.RequestException as e:
print(f" -> FAILED during login/CSRF process: {e}")
return
# 2. Fetch all necessary data
print("\n2. Fetching data from Superset...")
params = {"q": json.dumps({"page_size": -1})}
try:
r_datasets = session.get(f"{SUPERSET_URL}/api/v1/dataset/", params=params)
r_datasets.raise_for_status()
all_dataset_ids = [ds['id'] for ds in r_datasets.json()['result']]
r_roles = session.get(f"{SUPERSET_URL}/api/v1/security/roles/", params=params)
r_roles.raise_for_status()
roles_map = {role['name']: role['id'] for role in r_roles.json()['result']}
# IMPORTANT CHANGE: Store the NAME and ID of existing rules in a dictionary/map
r_rls = session.get(f"{SUPERSET_URL}/api/v1/rowlevelsecurity/", params=params)
r_rls.raise_for_status()
existing_rules_map = {rule['name']: rule['id'] for rule in r_rls.json()['result']}
print(f" -> Found {len(all_dataset_ids)} datasets, {len(roles_map)} roles, and {len(existing_rules_map)} existing RLS rules.")
except requests.exceptions.RequestException as e:
print(f" -> FAILED to fetch data: {e}")
return
if not all_dataset_ids:
print("\nWARNING: No datasets found. No rules will be created or updated.")
return
print("\n3. Processing roles and creating or updating RLS rules...")
for role_name, role_id in roles_map.items():
rule_name = None
clause = None
# Logic to determine rule_name and clause remains the same
if role_name.lower().startswith('dekan ') and role_name.lower() != 'dekan':
fakultas_code = role_name.split(' ', 1)[1].upper()
rule_name = f"Rule Dekan {fakultas_code}"
clause = f"kd_fak = '{fakultas_code}'"
elif role_name.lower().startswith('kaprodi ') and role_name.lower() != 'kaprodi':
prodi_short_name = role_name.split(' ', 1)[1].lower()
prodi_code = KAPRODI_CODE_MAPPING.get(prodi_short_name)
if prodi_code:
rule_name = f"Rule Kaprodi {prodi_short_name.upper()}"
clause = f"no_ps = {prodi_code}"
else:
if prodi_short_name: # Only print warning if there is a name to warn about
print(f"\n- WARNING: No code mapping found for '{prodi_short_name}'. Skipping role '{role_name}'.")
continue
# --- NEW LOGIC: CREATE OR UPDATE ---
if rule_name:
print(f"\n- Processing rule for role: '{role_name}'")
# This payload will be used for both creating (POST) and updating (PUT)
payload = {
"name": rule_name,
"filter_type": "Regular",
"clause": clause,
"roles": [role_id],
"tables": all_dataset_ids,
"description": "Automatically created or updated by Python script."
}
if rule_name in existing_rules_map:
# --- IF RULE EXISTS: PERFORM UPDATE (PUT) ---
rule_id_to_update = existing_rules_map[rule_name]
print(f" -> Rule '{rule_name}' exists (ID: {rule_id_to_update}). Updating...")
try:
r_update = session.put(f"{SUPERSET_URL}/api/v1/rowlevelsecurity/{rule_id_to_update}", json=payload)
if r_update.status_code == 200:
print(f" -> SUCCESS: Rule updated.")
else:
print(f" -> FAILED to update. Status: {r_update.status_code}, Message: {r_update.text}")
except requests.exceptions.RequestException as e:
print(f" -> FAILED due to an exception: {e}")
else:
# --- IF RULE DOES NOT EXIST: PERFORM CREATE (POST) ---
print(f" -> Rule '{rule_name}' does not exist. Creating...")
try:
r_create = session.post(f"{SUPERSET_URL}/api/v1/rowlevelsecurity/", json=payload)
if r_create.status_code == 201:
print(f" -> SUCCESS: Rule created.")
# Add the new rule to the map so we don't try to re-create it in the same session
new_rule_id = r_create.json().get('id')
if new_rule_id:
existing_rules_map[rule_name] = new_rule_id
else:
print(f" -> FAILED to create. Status: {r_create.status_code}, Message: {r_create.text}")
except requests.exceptions.RequestException as e:
print(f" -> FAILED due to an exception: {e}")
print("\n4. Process finished.")
if __name__ == "__main__":
main()
i got an error message when try to update rules.
- Processing rule for role: 'Kaprodi Mesin'
-> Rule 'Rule Kaprodi MESIN' exists (ID: 12). Updating...
-> FAILED to update. Status: 201, Message: {
"id": 12,
"result": {
"clause": "no_ps = 131",
"description": "Automatically created or updated by Python script.",
"filter_type": "Regular",
"name": "Rule Kaprodi MESIN",
"roles": [
9
],
"tables": [
25,
20,
11,
17,
12,
10,
18,
2,
15,
13,
21,
5,
19,
24,
8,
6,
16,
4,
23,
1,
22,
3,
14,
9,
7
]
}
}
In https://superset.apache.org/docs/api, there is not response with code 201 at method put endpoint /api/v1/rowlevelsecurity/{pk}.
Anyone can help me?
Screenshots/recordings
No response
Superset version
5.0.0
Python version
3.11
Node version
18 or greater
Browser
Chrome
Additional context
No response
Checklist
- [x] I have searched Superset docs and Slack and didn't find a solution to my problem.
- [x] I have searched the GitHub issue tracker and didn't find a similar bug report.
- [x] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
Comment From: dosubot[bot]
This is a bug in Superset 5.0.0: the PUT /api/v1/rowlevelsecurity/{pk} endpoint returns a 201 status code on update, even though the API docs and REST standards say it should return 200. The code explicitly returns 201 on successful update, which is nonstandard and not intended behavior (see source code). There are no existing issues or fixes for this yet.
As a workaround, you can treat both 200 and 201 as success for this endpoint in your automation scripts. This bug could affect other endpoints that use the same generic API base classes, so watch for similar patterns elsewhere.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Comment From: msyavuz
Nice catch! You are right that 201 should be used with resource creation only. Opened a pr to fix it.