apache / superset

Apache Superset is a Data Visualization and Data Exploration Platform
https://superset.apache.org/
Apache License 2.0
62.66k stars 13.83k forks source link

Api /api/v1/dashboard and /api/v1/chart returning empty responses when get request is made using user with Admin role in Python #25890

Open giacomochiarella opened 1 year ago

giacomochiarella commented 1 year ago

Dashboard api /api/v1/dashboard and /api/v1/chart returning empty responses when get request is made using user with Superset predefined Admin role in Python

Expected results

I expect to get all dashboards when user with Admin role uses /api/v1/dashboard I expect to get all charts when user with Admin role uses /api/v1/chart

Actual results

I get {"count":0,"description_columns":{},"ids":[],"label_columns":{"certification_details":"Certification Details","certified_by":"Certified By","changed_by.first_name":"Changed By First Name","changed_by.id":"Changed By Id","changed_by.last_name":"Changed By Last Name","changed_by_name":"Changed By Name","changed_on_delta_humanized":"Changed On Delta Humanized","changed_on_utc":"Changed On Utc","created_by.first_name":"Created By First Name","created_by.id":"Created By Id","created_by.last_name":"Created By Last Name","created_on_delta_humanized":"Created On Delta Humanized","css":"Css","dashboard_title":"Dashboard Title","id":"Id","is_managed_externally":"Is Managed Externally","json_metadata":"Json Metadata","owners.first_name":"Owners First Name","owners.id":"Owners Id","owners.last_name":"Owners Last Name","position_json":"Position Json","published":"Published","roles.id":"Roles Id","roles.name":"Roles Name","slug":"Slug","status":"Status","tags.id":"Tags Id","tags.name":"Tags Name","tags.type":"Tags Type","thumbnail_url":"Thumbnail Url","url":"Url"},"list_columns":["id","published","status","slug","url","css","position_json","json_metadata","thumbnail_url","certified_by","certification_details","changed_by.first_name","changed_by.last_name","changed_by.id","changed_by_name","changed_on_utc","changed_on_delta_humanized","created_on_delta_humanized","created_by.first_name","created_by.id","created_by.last_name","dashboard_title","owners.id","owners.first_name","owners.last_name","roles.id","roles.name","is_managed_externally","tags.id","tags.name","tags.type"],"list_title":"List Dashboard","order_columns":["changed_by.first_name","changed_on_delta_humanized","created_by.first_name","dashboard_title","published","changed_on"],"result":[]} on /api/v1/dashboard

I get {"count":0,"description_columns":{},"ids":[],"label_columns":{"cache_timeout":"Cache Timeout","certification_details":"Certification Details","certified_by":"Certified By","changed_by.first_name":"Changed By First Name","changed_by.last_name":"Changed By Last Name","changed_by_name":"Changed By Name","changed_on_delta_humanized":"Changed On Delta Humanized","changed_on_dttm":"Changed On Dttm","changed_on_utc":"Changed On Utc","created_by.first_name":"Created By First Name","created_by.id":"Created By Id","created_by.last_name":"Created By Last Name","created_by_name":"Created By Name","created_on_delta_humanized":"Created On Delta Humanized","dashboards.dashboard_title":"Dashboards Dashboard Title","dashboards.id":"Dashboards Id","datasource_id":"Datasource Id","datasource_name_text":"Datasource Name Text","datasource_type":"Datasource Type","datasource_url":"Datasource Url","description":"Description","description_markeddown":"Description Markeddown","edit_url":"Edit Url","form_data":"Form Data","id":"Id","is_managed_externally":"Is Managed Externally","last_saved_at":"Last Saved At","last_saved_by.first_name":"Last Saved By First Name","last_saved_by.id":"Last Saved By Id","last_saved_by.last_name":"Last Saved By Last Name","owners.first_name":"Owners First Name","owners.id":"Owners Id","owners.last_name":"Owners Last Name","params":"Params","slice_name":"Slice Name","slice_url":"Slice Url","table.default_endpoint":"Table Default Endpoint","table.table_name":"Table Table Name","tags.id":"Tags Id","tags.name":"Tags Name","tags.type":"Tags Type","thumbnail_url":"Thumbnail Url","url":"Url","viz_type":"Viz Type"},"list_columns":["is_managed_externally","certified_by","certification_details","cache_timeout","changed_by.first_name","changed_by.last_name","changed_by_name","changed_on_delta_humanized","changed_on_dttm","changed_on_utc","created_by.first_name","created_by.id","created_by.last_name","created_by_name","created_on_delta_humanized","datasource_id","datasource_name_text","datasource_type","datasource_url","description","description_markeddown","edit_url","form_data","id","last_saved_at","last_saved_by.id","last_saved_by.first_name","last_saved_by.last_name","owners.first_name","owners.id","owners.last_name","dashboards.id","dashboards.dashboard_title","params","slice_name","slice_url","table.default_endpoint","table.table_name","thumbnail_url","url","viz_type","tags.id","tags.name","tags.type"],"list_title":"List Slice","order_columns":["changed_by.first_name","changed_on_delta_humanized","datasource_id","datasource_name","last_saved_at","last_saved_by.id","last_saved_by.first_name","last_saved_by.last_name","slice_name","viz_type"],"result":[]} on /api/v1/chart

I get 404 if I call /api/v1/dashboard/{id} on an existing dashboard (the id is read by accessing the dashboard and read the id in the url).

Calling /api/v1/log or /api/v1/dashboard return a 308 which is successfully redirected for /api/v1/log, instead it gives above response for /api/v1/dashboard

Environment

Running docker-compose.yml Python 3.9.18 Flask 2.2.5 Werkzeug 2.3.3 Superset images: 3.1.0

DASHBOARD_RBAC: True

Checklist

I can use /api/v1/log without any issue in Python and in RESTClient I can use /api/v1/dashboard in RESTCLient, when I call /api/v1/dashboard in Python I get 308 which is redirected but then I get the above response (no dashboards included)

gdowmont-gss commented 12 months ago

I am having the same problem. Tried with curl and python requests and nothing returned. dataset endpoint works fine.

Also checked and doing PUT to /api/v1/dashboard/{id} works fine so only GET doesn't get anything.

Using 3.0,1 image.

gdowmont-gss commented 12 months ago

I have just discovered that if you make a request and pass Cookie header with syntactically valid session cookie (you can get it from making the same request in a browser) I get expected response from /database and /chart endpoints.

Stange that they behave differently from /dataset


url = "http://localhost/api/v1/dashboard/"

payload = {}
headers = {
  'Accept': 'application/json',
  'Cookie': 'session=valid_cookie here',
  'Authorization': 'Bearer valid_token'
}

response = requests.request("GET", url, headers=headers, data=payload)

print(response.text)
bronz3beard commented 9 months ago

I have found that if I take the session=.cookie from my swagger and use that (hardcoded) in my api request it works (the dot before the cookie is intentional as it is not apart of the other cookie).

If I use the session=cookie returned from /api/v1/security/csrf_token/ it does not work, I am not sure what that means and I have no idea how to solve it right now, but being able to display the dashboards as a list is essential to any application that has embedded dashboards.

If I figure out how I can be apart of the solution or if I find a solution I will share it here ✌️ Also if anyone has some ideas of how I can get this endpoint to work /api/v1/dashboard/ I welcome all of them 😅.

code example:

import axios from 'axios';
import { SUPERSET_BASE } from '../../constants';
import { getCsrfToken, login } from './auth.helper';

export async function getDashboards() {
  const { accessToken } = await login();
  // const { sessionCookie, csrfToken } = await getCsrfToken(accessToken);

  const headers = {
    Accept: 'application/json',
    Authorization: `Bearer ${accessToken}`,
    // Cookie: `session=${sessionCookie}`, // This does not work 😑
    Cookie:
      'session=.session_cookie_copied_from_hosted_swagger_dev_tools' // this works...
  };

  const response = await axios.get(`${SUPERSET_BASE}/api/v1/dashboard/`, {
    headers
  });

  return response;
}
bronz3beard commented 9 months ago

The solution for this issue can be found here, I think this should be marked as resolved. https://apache-superset.slack.com/archives/C01EP56QGTS/p1705521514606739?thread_ts=1704717577.433489&cid=C01EP56QGTS


Okay so as discussed below this is a fix for displaying the dashboard list only, if you try to view an embedded dashboard with the changes it will not work.

When getting the guest token there is a request to the /api/v1/me/roles/ endpoint which returns the Public role, even if you assign a different Role to the user requesting to view the embedded dashboard, the token is requested as a guest(Public Role), not as the assigned role, maybe after manually getting the roles of the signed in user (GET /api/v1/me/roles/), we should then be able to send them in the payload along with the rls?

For example; if the roles matrix is provided in the payload of the request, the token is granted with the provided roles, if not, it is granted with the default Public role. This is a naive idea of a solution, I dont know the complexities of the python code. maybe it is related to this? config.py line 345 fe-embedded

    const payload = {
      user: {
        username: 'js1',
        firstName: 'John',
        lastName: 'Smith'
      },
      resources: [
        {
          type: 'dashboard',
          id
        }
      ],
      roles: {
        gamma: [
          ['can_read', 'Chart'],
          ['can_read', 'Dashboard'],
          ['can_list', 'DynamicPlugin'],
          ['can_explore_json', 'Superset'],
          ...
        ]
      },
      rls: []
    };
ss-ravi commented 9 months ago

@bronz3beard I think the link you sent is not a solution to the problem. It seems that having can read on Dashboard or can read on Chart in the public role breaks the dashboard/chart api. We need a workaround in case we need these permissions on the public role.

bronz3beard commented 9 months ago

@ss-ravi Ahh okay I see, I thought the permission change to public was intentional, but it is just a temp fix not a solution. thanks for the extra context 🙌

bronz3beard commented 9 months ago

if I change this GUEST_ROLE_NAME = "Public" in the config.py on line 1561 toGUEST_ROLE_NAME = "Guest" and then create and give the Guest role the same permissions asPublic role` everything is working as expected.

cw1427 commented 9 months ago

@bronz3beard I think the link you sent is not a solution to the problem. It seems that having can read on Dashboard or can read on Chart in the public role breaks the dashboard/chart api. We need a workaround in case we need these permissions on the public role.

Thanks to save my life. Just removed the Public role's perm and everything works.... Don't get it.

giacomochiarella commented 8 months ago

@cw1427 do you mean you just deleted the Public role?

bronz3beard commented 8 months ago

@cw1427 do you mean you just deleted the Public role?

You need to update the config to use GUEST (a role you create with exactly the same permissions as PUBLIC Role) rather than PUBLIC.

After you make that change to the config you could delete it if you want, up to you.

giacomochiarella commented 8 months ago

@bronz3beard thanks for the answer. It was not clear. What is the difference between PUBLIC_ROLE_LIKE and GUEST_ROLE_NAME? Right now I have setup PUBLIC_ROLE_LIKE to a custom role, can I assign the same role to GUEST_ROLE_NAME?

bronz3beard commented 7 months ago

@bronz3beard thanks for the answer. It was not clear. What is the difference between PUBLIC_ROLE_LIKE and GUEST_ROLE_NAME? Right now I have setup PUBLIC_ROLE_LIKE to a custom role, can I assign the same role to GUEST_ROLE_NAME?


My response was as clear as your question.

I dont know what your setup is, but from your question I would experiment with this solution, you suggested as a question. PUBLIC_ROLE_LIKE to a custom role, can I assign the same role to GUEST_ROLE_NAME?

for me all I had to do was this to get ti to work, obviously this is a work around.


This is what my config looks like.

Screenshot 2024-03-15 at 2 34 32 pm

Screenshot 2024-03-15 at 2 35 07 pm

giacomochiarella commented 7 months ago

@bronz3beard unfortunately it does not work. I've setup PUBLIC_ROLE_LIKE = None and GUEST_ROLE_NAME = 'custom_role' but I still have the same issue. The only way it worked is by deleting Public role but it gets recreated if I restart Superset. Moreover, if I delete Public role I get Internal server error 500 if I just open Superset homepage and I'm not logged in (the error is name does not exist on None object or something, it tries to call .name on role object which is None, because Public does not exist). I've also tried PUBLIC_ROLE_LIKE = None and GUEST_ROLE_NAME = 'custom_role' with custom_role equal to Public and nothing changed. The api still return no dashboards/charts even though the credentials have Admin role. I've done these changes in superset_config.py

giacomochiarella commented 7 months ago

any news here? It is crucial to be able use the api

gruz commented 5 months ago

Check Public role permissions. If Dashboard can_read is set for Public role, then this happens. I'm not sure if it's intended behavior or bug.

giacomochiarella commented 5 months ago

Check Public role permissions. If Dashboard can_read is set for Public role, then this happens. I'm not sure if it's intended behavior or bug.

@gruz could you be more specific on what you are suggesting to do? can_read on Dashboard is automatically assigned when you start Superset. What are the steps you are suggesting with your statement?

gruz commented 5 months ago

@giacomochiarella In my case someone before me added permissions to the Public role. From scratch Public role doesn't have any permissions.

Among those permissions what can_read on Dashboard in Public role. After removing it from Public role I can get the list of Dashboards as expected (count to zero).

зображення

P.S. A guy who added the permissions to the Public role have told, at least some of them are needed for Dashboard embedding to work.

zarkerus commented 3 months ago

Same problem here, we want to make a simple embedded dashboard from superset with a page listing all dashboard available, when click on one of the item it will redirect to the detail of that dashboard, so to make dashboard embedded work, we have to add

FEATURE_FLAGS = {
    "EMBEDDED_SUPERSET": True,
    "GUEST_TOKEN": True
}

~~PUBLIC_ROLE_LIKE = "Gamma" # <-- This one will add a bunch of permissions to Public role which is necessary to embedding dashboard to the site~~

But if Public Role have these permission then these API will no longer working as expected

GET: /api/v1/dashboard/ # <-- return count = 0
GET: /api/v1/dashboard/1/embedded # <-- return not found

If we delete public role then these 2 API will working as expected but then we will get internal server error in the embedded dashboard on our site.

If we use Cookie created when login from the browser then we can make these 2 APIs work as well but ofc we can't use that because that cookie will be delete as soon as we close the browser.

We used apache/superset:4.0.2 docker image, if anyone know a version where these APIs work as expected, please let me know

P/S: Nvm, I think I understand the problem now, the problem is, when we login as Guest through API, we can only see published dashboard not all dashboards. This is not a bug at all, hope this answer help others as well.

jacob-roldan commented 3 months ago

I think I found a workaround to use the API when the PUBLIC_ROLE_LIKE var is defined.

I’ve attached an example bash script with curl calls. The key is the method save_cookie_from_login. After get access and csrf tokens, the idea is to simulate to access to login html page and save the new cookie. test_list_dashboards.zip

save_cookie_from_login () {
    # URL to send the request to
    local URL="${superset_url}/login/"

    local payload="csrf_token=${csrf_token}&username=admin&password=admin"

    # Do a POST request to login saving the new cookie
    local response2; response2=$(curl -b /tmp/cookies.txt -c /tmp/cookies.txt -s -X POST "$URL" -d "$payload")

}

I think this is could be a solution for these issues: https://github.com/apache/superset/issues/25876 https://github.com/apache/superset/issues/25740

giacomochiarella commented 3 months ago

@jacob-roldan could you be more specific how to use your workaround in Python? I'm still having issues in calling /api/v1/database or database/{pk}, /api/v1/dataset or dataset/{pk}, /api/v1/chart or chart/{pk}, /api/v1/dashboard or dashboard/{pk}. They return 0 or 404

jacob-roldan commented 3 months ago

A simple example using python to get all dashboards when user admin uses /api/v1/dashboard

test_list_dashboards.py.txt