Files
Karl Persson fa74d1c36d Authn: Sync authlib and update how we construct authn client interceptor (#101124)
* Sync authlib and update how we construct authn client interceptor

* Remove namespace from checker
2025-02-26 09:22:09 +01:00

118 lines
2.6 KiB
Go

package common
import (
"context"
"strconv"
authlib "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/identity"
iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/team"
)
// OptonalFormatInt formats num as a string. If num is less or equal than 0
// an empty string is returned.
func OptionalFormatInt(num int64) string {
if num > 0 {
return strconv.FormatInt(num, 10)
}
return ""
}
func MapTeamPermission(p team.PermissionType) iamv0.TeamPermission {
if p == team.PermissionTypeAdmin {
return iamv0.TeamPermissionAdmin
} else {
return iamv0.TeamPermissionMember
}
}
// Resource is required to be implemented for list return types so we can
// perform authorization.
type Resource interface {
AuthID() string
}
type ListResponse[T Resource] struct {
Items []T
RV int64
Continue int64
}
type ListFunc[T Resource] func(ctx context.Context, ns authlib.NamespaceInfo, p Pagination) (*ListResponse[T], error)
// List is a helper function that will perform access check on resources if
// prvovided with a authlib.AccessClient.
func List[T Resource](
ctx context.Context,
resourceName string,
ac authlib.AccessClient,
p Pagination,
fn ListFunc[T],
) (*ListResponse[T], error) {
ns, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
ident, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
check := func(_, _ string) bool { return true }
if ac != nil {
var err error
check, err = ac.Compile(ctx, ident, authlib.ListRequest{
Resource: resourceName,
Namespace: ns.Value,
})
if err != nil {
return nil, err
}
}
res := &ListResponse[T]{Items: make([]T, 0, p.Limit)}
first, err := fn(ctx, ns, p)
if err != nil {
return nil, err
}
for _, item := range first.Items {
if !check(item.AuthID(), "") {
continue
}
res.Items = append(res.Items, item)
}
res.Continue = first.Continue
res.RV = first.RV
outer:
for len(res.Items) < int(p.Limit) && res.Continue != 0 {
// FIXME: it is not optimal to reduce the amout we look for here but it is the easiest way to
// correctly handle pagination and continue tokens
r, err := fn(ctx, ns, Pagination{Limit: p.Limit - int64(len(res.Items)), Continue: res.Continue})
if err != nil {
return nil, err
}
for _, item := range r.Items {
if len(res.Items) == int(p.Limit) {
res.Continue = r.Continue
break outer
}
if !check(item.AuthID(), "") {
continue
}
res.Items = append(res.Items, item)
}
}
return res, nil
}