From b10a4fae781420524b15c3163f3b1f05daa31e5d Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Wed, 20 Mar 2024 15:52:25 +0000 Subject: [PATCH] Return Empty Lists (#30) When an acestor doesn't exist, e.g. the organization hasn't been created yet by project creation. Makes UI handling far simpler. --- charts/unikorn/Chart.yaml | 4 +- pkg/server/handler/cluster/client.go | 6 ++- pkg/server/handler/cluster/conversion.go | 6 +-- pkg/server/handler/clustermanager/client.go | 12 ++++-- pkg/server/handler/project/client.go | 48 +++++++++++++++++++++ 5 files changed, 66 insertions(+), 10 deletions(-) diff --git a/charts/unikorn/Chart.yaml b/charts/unikorn/Chart.yaml index 0edaec98..d763603d 100644 --- a/charts/unikorn/Chart.yaml +++ b/charts/unikorn/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn type: application -version: v0.1.10 -appVersion: v0.1.10 +version: v0.1.11 +appVersion: v0.1.11 icon: https://raw.githubusercontent.com/unikorn-cloud/unikorn/main/icons/default.png diff --git a/pkg/server/handler/cluster/client.go b/pkg/server/handler/cluster/client.go index ec9e28e2..19c2051e 100644 --- a/pkg/server/handler/cluster/client.go +++ b/pkg/server/handler/cluster/client.go @@ -83,13 +83,17 @@ func NewClient(client client.Client, options *Options) *Client { } // List returns all clusters owned by the implicit control plane. -func (c *Client) List(ctx context.Context, organizationName string) ([]*generated.KubernetesCluster, error) { +func (c *Client) List(ctx context.Context, organizationName string) (generated.KubernetesClusters, error) { selector := labels.NewSelector() // TODO: a super-admin isn't scoped to a single organization! // TODO: RBAC - filter projects based on user membership here. organization, err := organization.NewClient(c.client).GetMetadata(ctx, organizationName) if err != nil { + if errors.IsHTTPNotFound(err) { + return generated.KubernetesClusters{}, nil + } + return nil, err } diff --git a/pkg/server/handler/cluster/conversion.go b/pkg/server/handler/cluster/conversion.go index 53057753..b13a933b 100644 --- a/pkg/server/handler/cluster/conversion.go +++ b/pkg/server/handler/cluster/conversion.go @@ -137,8 +137,8 @@ func (c *Client) convert(in *unikornv1.KubernetesCluster) (*generated.Kubernetes } // uconvertList converts from a custom resource list into the API definition. -func (c *Client) convertList(in *unikornv1.KubernetesClusterList) ([]*generated.KubernetesCluster, error) { - out := make([]*generated.KubernetesCluster, len(in.Items)) +func (c *Client) convertList(in *unikornv1.KubernetesClusterList) (generated.KubernetesClusters, error) { + out := make(generated.KubernetesClusters, len(in.Items)) for i := range in.Items { item, err := c.convert(&in.Items[i]) @@ -146,7 +146,7 @@ func (c *Client) convertList(in *unikornv1.KubernetesClusterList) ([]*generated. return nil, err } - out[i] = item + out[i] = *item } return out, nil diff --git a/pkg/server/handler/clustermanager/client.go b/pkg/server/handler/clustermanager/client.go index 614ee71c..c153f599 100644 --- a/pkg/server/handler/clustermanager/client.go +++ b/pkg/server/handler/clustermanager/client.go @@ -220,8 +220,8 @@ func (c *Client) convert(in *unikornv1.ClusterManager) (*generated.ClusterManage } // convertList converts from Kubernetes into OpenAPI types. -func (c *Client) convertList(in *unikornv1.ClusterManagerList) ([]*generated.ClusterManager, error) { - out := make([]*generated.ClusterManager, len(in.Items)) +func (c *Client) convertList(in *unikornv1.ClusterManagerList) (generated.ClusterManagers, error) { + out := make(generated.ClusterManagers, len(in.Items)) for i := range in.Items { item, err := c.convert(&in.Items[i]) @@ -229,20 +229,24 @@ func (c *Client) convertList(in *unikornv1.ClusterManagerList) ([]*generated.Clu return nil, err } - out[i] = item + out[i] = *item } return out, nil } // List returns all control planes. -func (c *Client) List(ctx context.Context, organizationName string) ([]*generated.ClusterManager, error) { +func (c *Client) List(ctx context.Context, organizationName string) (generated.ClusterManagers, error) { selector := labels.NewSelector() // TODO: a super-admin isn't scoped to a single organization! // TODO: RBAC - filter projects based on user membership here. organization, err := organization.NewClient(c.client).GetMetadata(ctx, organizationName) if err != nil { + if errors.IsHTTPNotFound(err) { + return generated.ClusterManagers{}, nil + } + return nil, err } diff --git a/pkg/server/handler/project/client.go b/pkg/server/handler/project/client.go index 455e630b..207978f9 100644 --- a/pkg/server/handler/project/client.go +++ b/pkg/server/handler/project/client.go @@ -22,7 +22,11 @@ import ( goerrors "errors" "slices" + "github.com/go-jose/go-jose/v3/jwt" + unikornv1core "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1" + "github.com/unikorn-cloud/core/pkg/authorization/roles" + "github.com/unikorn-cloud/core/pkg/authorization/userinfo" "github.com/unikorn-cloud/core/pkg/constants" "github.com/unikorn-cloud/core/pkg/server/errors" unikornv1 "github.com/unikorn-cloud/unikorn/pkg/apis/unikorn/v1alpha1" @@ -33,6 +37,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" ) // Client wraps up project related management handling. @@ -137,6 +142,37 @@ func convertList(in *unikornv1.ProjectList) generated.Projects { return out } +// GroupPermissions are privilege grants for a project. +type GroupPermissions struct { + // ID is the unique, immutable project identifier. + ID string `json:"id"` + // Roles are the privileges a user has for the group. + Roles []roles.Role `json:"roles"` +} + +// OrganizationPermissions are privilege grants for an organization. +type OrganizationPermissions struct { + // IsAdmin allows the user to play with all resources in an organization. + IsAdmin bool `json:"isAdmin,omitempty"` + // Name is the name of the organization. + Name string `json:"name"` + // Groups are any groups the user belongs to in an organization. + Groups []GroupPermissions `json:"groups,omitempty"` +} + +// Permissions are privilege grants for the entire system. +type Permissions struct { + // IsSuperAdmin HAS SUPER COW POWERS!!! + IsSuperAdmin bool `json:"isSuperAdmin,omitempty"` + // Organizations are any organizations the user has access to. + Organizations []OrganizationPermissions `json:"organizations,omitempty"` +} + +type UserInfoType struct { + jwt.Claims + Permissions *Permissions `json:"permissions,omitempty"` +} + func (c *Client) List(ctx context.Context, organizationName string) (generated.Projects, error) { organization, err := organization.NewClient(c.client).GetMetadata(ctx, organizationName) if err != nil { @@ -149,6 +185,18 @@ func (c *Client) List(ctx context.Context, organizationName string) (generated.P return nil, err } + ui := userinfo.FromContext(ctx) + + userinfo := &UserInfoType{} + + if err := ui.Claims(userinfo); err != nil { + return nil, errors.OAuth2ServerError("failed to extract claims").WithError(err) + } + + log := log.FromContext(ctx) + + log.Info("rbac", "userinfo", userinfo) + result := &unikornv1.ProjectList{} if err := c.client.List(ctx, result, &client.ListOptions{Namespace: organization.Namespace}); err != nil {