package api import ( "context" "net/http" "strconv" "time" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/auth/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/web" ) // swagger:route GET /dashboards/uid/{uid}/permissions dashboard_permissions getDashboardPermissionsListByUID // // Gets all existing permissions for the given dashboard. // // Responses: // 200: getDashboardPermissionsListResponse // 401: unauthorisedError // 403: forbiddenError // 404: notFoundError // 500: internalServerError // swagger:route GET /dashboards/id/{DashboardID}/permissions dashboard_permissions getDashboardPermissionsListByID // // Gets all existing permissions for the given dashboard. // // Please refer to [updated API](#/dashboard_permissions/getDashboardPermissionsListByUID) instead // // Deprecated: true // // Responses: // 200: getDashboardPermissionsListResponse // 401: unauthorisedError // 403: forbiddenError // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetDashboardPermissionList(c *contextmodel.ReqContext) response.Response { var dashID int64 var err error dashUID := web.Params(c.Req)[":uid"] if dashUID == "" { dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) if err != nil { return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) } } dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID) if rsp != nil { return rsp } acl, err := hs.getDashboardACL(c.Req.Context(), c.SignedInUser, dash) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to get dashboard permissions", err) } filteredACLs := make([]*dashboards.DashboardACLInfoDTO, 0, len(acl)) for _, perm := range acl { if perm.UserID > 0 && dtos.IsHiddenUser(perm.UserLogin, c.SignedInUser, hs.Cfg) { continue } perm.UserAvatarURL = dtos.GetGravatarUrl(hs.Cfg, perm.UserEmail) if perm.TeamID > 0 { perm.TeamAvatarURL = dtos.GetGravatarUrlWithDefault(hs.Cfg, perm.TeamEmail, perm.Team) } if perm.Slug != "" { perm.URL = dashboards.GetDashboardFolderURL(perm.IsFolder, perm.UID, perm.Slug) } filteredACLs = append(filteredACLs, perm) } return response.JSON(http.StatusOK, filteredACLs) } // swagger:route POST /dashboards/uid/{uid}/permissions dashboard_permissions updateDashboardPermissionsByUID // // Updates permissions for a dashboard. // // This operation will remove existing permissions if they’re not included in the request. // // Responses: // 200: okResponse // 400: badRequestError // 401: unauthorisedError // 403: forbiddenError // 404: notFoundError // 500: internalServerError // swagger:route POST /dashboards/id/{DashboardID}/permissions dashboard_permissions updateDashboardPermissionsByID // // Updates permissions for a dashboard. // // Please refer to [updated API](#/dashboard_permissions/updateDashboardPermissionsByUID) instead // // This operation will remove existing permissions if they’re not included in the request. // // Deprecated: true // // Responses: // 200: okResponse // 400: badRequestError // 401: unauthorisedError // 403: forbiddenError // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) UpdateDashboardPermissions(c *contextmodel.ReqContext) response.Response { var dashID int64 var err error apiCmd := dtos.UpdateDashboardACLCommand{} if err := web.Bind(c.Req, &apiCmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } if err := validatePermissionsUpdate(apiCmd); err != nil { return response.Error(http.StatusBadRequest, err.Error(), err) } dashUID := web.Params(c.Req)[":uid"] if dashUID == "" { dashID, err = strconv.ParseInt(web.Params(c.Req)[":dashboardId"], 10, 64) if err != nil { return response.Error(http.StatusBadRequest, "dashboardId is invalid", err) } } dash, rsp := hs.getDashboardHelper(c.Req.Context(), c.SignedInUser.GetOrgID(), dashID, dashUID) if rsp != nil { return rsp } items := make([]*dashboards.DashboardACL, 0, len(apiCmd.Items)) for _, item := range apiCmd.Items { items = append(items, &dashboards.DashboardACL{ OrgID: c.SignedInUser.GetOrgID(), DashboardID: dashID, UserID: item.UserID, TeamID: item.TeamID, Role: item.Role, Permission: item.Permission, Created: time.Now(), Updated: time.Now(), }) } acl, err := hs.getDashboardACL(c.Req.Context(), c.SignedInUser, dash) if err != nil { return response.Error(http.StatusInternalServerError, "Error while checking dashboard permissions", err) } items = append(items, hs.filterHiddenACL(c.SignedInUser, acl)...) if err := hs.updateDashboardAccessControl(c.Req.Context(), dash.OrgID, dash.UID, false, items, acl); err != nil { return response.Error(http.StatusInternalServerError, "Failed to update permissions", err) } return response.Success("Dashboard permissions updated") } var dashboardPermissionMap = map[string]dashboardaccess.PermissionType{ "View": dashboardaccess.PERMISSION_VIEW, "Edit": dashboardaccess.PERMISSION_EDIT, "Admin": dashboardaccess.PERMISSION_ADMIN, } func (hs *HTTPServer) getDashboardACL(ctx context.Context, user identity.Requester, dashboard *dashboards.Dashboard) ([]*dashboards.DashboardACLInfoDTO, error) { permissions, err := hs.dashboardPermissionsService.GetPermissions(ctx, user, dashboard.UID) if err != nil { return nil, err } acl := make([]*dashboards.DashboardACLInfoDTO, 0, len(permissions)) for _, p := range permissions { if !p.IsManaged { continue } var role *org.RoleType if p.BuiltInRole != "" { tmp := org.RoleType(p.BuiltInRole) role = &tmp } permission := dashboardPermissionMap[hs.dashboardPermissionsService.MapActions(p)] metrics.MFolderIDsAPICount.WithLabelValues(metrics.GetDashboardACL).Inc() acl = append(acl, &dashboards.DashboardACLInfoDTO{ OrgID: dashboard.OrgID, DashboardID: dashboard.ID, FolderID: dashboard.FolderID, // nolint:staticcheck Created: p.Created, Updated: p.Updated, UserID: p.UserId, UserLogin: p.UserLogin, UserEmail: p.UserEmail, TeamID: p.TeamId, TeamEmail: p.TeamEmail, Team: p.Team, Role: role, Permission: permission, PermissionName: permission.String(), UID: dashboard.UID, Title: dashboard.Title, Slug: dashboard.Slug, IsFolder: dashboard.IsFolder, URL: dashboard.GetURL(), Inherited: false, }) } return acl, nil } func (hs *HTTPServer) filterHiddenACL(user identity.Requester, acl []*dashboards.DashboardACLInfoDTO) []*dashboards.DashboardACL { var hiddenACL []*dashboards.DashboardACL if user.GetIsGrafanaAdmin() { return hiddenACL } for _, item := range acl { if item.Inherited || item.UserLogin == user.GetLogin() { continue } if _, hidden := hs.Cfg.HiddenUsers[item.UserLogin]; hidden { hiddenACL = append(hiddenACL, &dashboards.DashboardACL{ OrgID: item.OrgID, DashboardID: item.DashboardID, UserID: item.UserID, TeamID: item.TeamID, Role: item.Role, Permission: item.Permission, Created: item.Created, Updated: item.Updated, }) } } return hiddenACL } // updateDashboardAccessControl is used for api backward compatibility func (hs *HTTPServer) updateDashboardAccessControl(ctx context.Context, orgID int64, uid string, isFolder bool, items []*dashboards.DashboardACL, old []*dashboards.DashboardACLInfoDTO) error { commands := []accesscontrol.SetResourcePermissionCommand{} for _, item := range items { permissions := item.Permission.String() role := "" if item.Role != nil { role = string(*item.Role) } commands = append(commands, accesscontrol.SetResourcePermissionCommand{ UserID: item.UserID, TeamID: item.TeamID, BuiltinRole: role, Permission: permissions, }) } for _, o := range old { shouldRemove := true for _, item := range items { if item.UserID != 0 && item.UserID == o.UserID { shouldRemove = false break } if item.TeamID != 0 && item.TeamID == o.TeamID { shouldRemove = false break } if item.Role != nil && o.Role != nil && *item.Role == *o.Role { shouldRemove = false break } } if shouldRemove { role := "" if o.Role != nil { role = string(*o.Role) } commands = append(commands, accesscontrol.SetResourcePermissionCommand{ UserID: o.UserID, TeamID: o.TeamID, BuiltinRole: role, Permission: "", }) } } if isFolder { if _, err := hs.folderPermissionsService.SetPermissions(ctx, orgID, uid, commands...); err != nil { return err } return nil } if _, err := hs.dashboardPermissionsService.SetPermissions(ctx, orgID, uid, commands...); err != nil { return err } return nil } func validatePermissionsUpdate(apiCmd dtos.UpdateDashboardACLCommand) error { for _, item := range apiCmd.Items { if item.UserID > 0 && item.TeamID > 0 { return dashboardaccess.ErrPermissionsWithUserAndTeamNotAllowed } if (item.UserID > 0 || item.TeamID > 0) && item.Role != nil { return dashboardaccess.ErrPermissionsWithRoleNotAllowed } } return nil } // swagger:parameters getDashboardPermissionsListByUID type GetDashboardPermissionsListByUIDParams struct { // in:path // required:true UID string `json:"uid"` } // swagger:parameters getDashboardPermissionsListByID type GetDashboardPermissionsListByIDParams struct { // in:path DashboardID int64 } // swagger:parameters updateDashboardPermissionsByID type UpdateDashboardPermissionsByIDParams struct { // in:body // required:true Body dtos.UpdateDashboardACLCommand // in:path DashboardID int64 } // swagger:parameters updateDashboardPermissionsByUID type UpdateDashboardPermissionsByUIDParams struct { // in:body // required:true Body dtos.UpdateDashboardACLCommand // in:path // required:true // description: The dashboard UID UID string `json:"uid"` } // swagger:response getDashboardPermissionsListResponse type GetDashboardPermissionsResponse struct { // in: body Body []*dashboards.DashboardACLInfoDTO `json:"body"` }