-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Folders: Migrate getFolder API to app platform #107617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
4ea5a35
fe84c8c
a9c2f4c
d867638
187e977
db5b914
c70aaff
8e8ad40
2a00788
c7f3908
eed14cd
34bc654
48af59b
d1d959d
421958b
d529ed1
ea906e8
634cd2a
b86b98d
845bbe6
a873b99
e7b4f3e
06f0baa
802fa46
7b30613
bd65f06
2f5b2c9
c8423b4
dc229e0
320f469
7c6dbdd
9ba694d
18b12c7
8fdb275
24e5b2b
152612b
c8b3dd7
641c091
d715ddb
d1b2fb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ package folders | |
|
||
import ( | ||
"context" | ||
"fmt" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"net/http" | ||
|
||
"k8s.io/apimachinery/pkg/runtime" | ||
|
@@ -10,14 +12,12 @@ import ( | |
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" | ||
"github.com/grafana/grafana/pkg/apimachinery/identity" | ||
"github.com/grafana/grafana/pkg/services/accesscontrol" | ||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" | ||
"github.com/grafana/grafana/pkg/services/dashboards" | ||
"github.com/grafana/grafana/pkg/services/folder" | ||
) | ||
|
||
type subAccessREST struct { | ||
service folder.Service | ||
ac accesscontrol.AccessControl | ||
getter rest.Getter | ||
ac accesscontrol.AccessControl | ||
} | ||
|
||
var _ = rest.Connecter(&subAccessREST{}) | ||
|
@@ -47,37 +47,58 @@ func (r *subAccessREST) NewConnectOptions() (runtime.Object, bool, string) { | |
} | ||
|
||
func (r *subAccessREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { | ||
ns, err := request.NamespaceInfoFrom(ctx, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
user, err := identity.GetRequester(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Can view is managed here (and in the Authorizer) | ||
f, err := r.service.Get(ctx, &folder.GetFolderQuery{ | ||
UID: &name, | ||
OrgID: ns.OrgID, | ||
SignedInUser: user, | ||
}) | ||
obj, err := r.getter.Get(ctx, name, &metav1.GetOptions{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: This probably isn't good. I assume this skips some access validation that before happened inside the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok seems like the folder API authorizer is actually checking the request before it gets to the handler. So at this point it does not need additional access checks. |
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
folderObj, ok := obj.(*folders.Folder) | ||
if !ok { | ||
return nil, fmt.Errorf("got something else than folders.Folder") | ||
} | ||
|
||
folderUID := folderObj.ObjectMeta.Name | ||
|
||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { | ||
access := &folders.FolderAccessInfo{} | ||
canEditEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)) | ||
canEditEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)) | ||
access.CanEdit, _ = r.ac.Evaluate(ctx, user, canEditEvaluator) | ||
access.CanSave = access.CanEdit | ||
canAdminEvaluator := accesscontrol.EvalAll( | ||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)), | ||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)), | ||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsRead, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)), | ||
accesscontrol.EvalPermission(dashboards.ActionFoldersPermissionsWrite, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)), | ||
) | ||
access.CanAdmin, _ = r.ac.Evaluate(ctx, user, canAdminEvaluator) | ||
canDeleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(f.UID)) | ||
canDeleteEvaluator := accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)) | ||
access.CanDelete, _ = r.ac.Evaluate(ctx, user, canDeleteEvaluator) | ||
|
||
// Cargo culted from pkg/api/folder.go#getFolderACMetadata | ||
allMetadata := getFolderAccessControl(ctx, r.getter, user, folderObj) | ||
metadata := map[string]bool{} | ||
// Flatten metadata - if any parent has a permission, the child folder inherits it | ||
for _, md := range allMetadata { | ||
for action := range md { | ||
metadata[action] = true | ||
} | ||
} | ||
|
||
access.AccessControl = metadata | ||
|
||
responder.Object(http.StatusOK, access) | ||
}), nil | ||
} | ||
|
||
func getFolderAccessControl(ctx context.Context, folderGetter rest.Getter, user identity.Requester, f *folders.Folder) map[string]accesscontrol.Metadata { | ||
parents := getFolderParents(ctx, folderGetter, f) | ||
folderIDs := map[string]bool{f.ObjectMeta.Name: true} | ||
|
||
for _, p := range parents.Items { | ||
folderIDs[p.Name] = true | ||
} | ||
return accesscontrol.GetResourcesMetadata(ctx, user.GetPermissions(), dashboards.ScopeFoldersPrefix, folderIDs) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package folders | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apiserver/pkg/registry/rest" | ||
) | ||
|
||
// getFolderParents gets a list of info objects for each parent of a Folder | ||
// TODO: There are some other implementations that seem to do similar thing like pkg/services/folder/service.go#GetParents. | ||
// | ||
// Not sure if they should be merged somehow or not. | ||
func getFolderParents(ctx context.Context, folderGetter rest.Getter, folder *folders.Folder) *folders.FolderInfoList { | ||
info := &folders.FolderInfoList{ | ||
Items: []folders.FolderInfo{}, | ||
} | ||
for folder != nil { | ||
parent := getParent(folder) | ||
descr := "" | ||
if folder.Spec.Description != nil { | ||
descr = *folder.Spec.Description | ||
} | ||
info.Items = append(info.Items, folders.FolderInfo{ | ||
Name: folder.Name, | ||
Title: folder.Spec.Title, | ||
Description: descr, | ||
Parent: parent, | ||
}) | ||
if parent == "" { | ||
break | ||
} | ||
|
||
obj, err := folderGetter.Get(ctx, parent, &metav1.GetOptions{}) | ||
if err != nil { | ||
info.Items = append(info.Items, folders.FolderInfo{ | ||
Name: parent, | ||
Detached: true, | ||
Description: err.Error(), | ||
}) | ||
break | ||
} | ||
|
||
parentFolder, ok := obj.(*folders.Folder) | ||
if !ok { | ||
info.Items = append(info.Items, folders.FolderInfo{ | ||
Name: parent, | ||
Detached: true, | ||
Description: fmt.Sprintf("expected folder, found: %T", obj), | ||
}) | ||
break | ||
} | ||
folder = parentFolder | ||
} | ||
return info | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reasoning behind adding this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is returned in the old API. The main point of this step in the migration is to use the new API but providing the same data as old one, before going on and refactoring the front end to use the more granular APIs better.