diff --git a/apiserver/organization/create.go b/apiserver/organization/create.go index 1b21af95..a64ffad1 100644 --- a/apiserver/organization/create.go +++ b/apiserver/organization/create.go @@ -5,11 +5,14 @@ import ( "fmt" "strings" + billingv1 "github.com/appuio/control-api/apis/billing/v1" orgv1 "github.com/appuio/control-api/apis/organization/v1" controlv1 "github.com/appuio/control-api/apis/v1" + "sigs.k8s.io/controller-runtime/pkg/client" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" ) @@ -90,3 +93,26 @@ func newOrganizationMembers(ctx context.Context, organization *orgv1.Organizatio }, } } + +func (s *organizationStorage) billingEntityValidator(ctx context.Context, organization *orgv1.Organization) error { + if organization.Spec.BillingEntityRef == "" && s.allowEmptyBillingEntity { + return nil + } + + user, ok := request.UserFrom(ctx) + if !ok { + return fmt.Errorf("no user in context") + } + + var be billingv1.BillingEntity + c, err := s.impersonator.Impersonate(user) + if err != nil { + return fmt.Errorf("failed to impersonate user: %w", err) + } + + if err := c.Get(ctx, client.ObjectKey{Name: organization.Name}, &be); err != nil { + return err + } + + return nil +} diff --git a/apiserver/organization/organization.go b/apiserver/organization/organization.go index fb9720b7..44f882e3 100644 --- a/apiserver/organization/organization.go +++ b/apiserver/organization/organization.go @@ -5,6 +5,7 @@ import ( "errors" "strings" + billingv1 "github.com/appuio/control-api/apis/billing/v1" orgv1 "github.com/appuio/control-api/apis/organization/v1" controlv1 "github.com/appuio/control-api/apis/v1" "github.com/appuio/control-api/apiserver/authwrapper" @@ -17,6 +18,7 @@ import ( "k8s.io/apiserver/pkg/endpoints/request" genericregistry "k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/rest" + restclient "k8s.io/client-go/rest" restbuilder "sigs.k8s.io/apiserver-runtime/pkg/builder/rest" "sigs.k8s.io/apiserver-runtime/pkg/util/loopback" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,11 +31,19 @@ import ( // New returns a new storage provider for Organizations func New(clusterRoles *[]string, usernamePrefix *string) restbuilder.ResourceHandlerProvider { return func(s *runtime.Scheme, g genericregistry.RESTOptionsGetter) (rest.Storage, error) { - c, err := client.NewWithWatch(loopback.GetLoopbackMasterClientConfig(), client.Options{}) - if err != nil { + masterConfig := loopback.GetLoopbackMasterClientConfig() + + scheme := runtime.NewScheme() + if err := controlv1.AddToScheme(scheme); err != nil { + return nil, err + } + if err := billingv1.AddToScheme(scheme); err != nil { return nil, err } - err = controlv1.AddToScheme(c.Scheme()) + + c, err := client.NewWithWatch(loopback.GetLoopbackMasterClientConfig(), client.Options{ + Scheme: scheme, + }) if err != nil { return nil, err } @@ -50,6 +60,7 @@ func New(clusterRoles *[]string, usernamePrefix *string) restbuilder.ResourceHan Client: c, }, usernamePrefix: *usernamePrefix, + impersonator: impersonator{masterConfig, client.Options{Scheme: scheme}}, } return authwrapper.NewAuthorizedStorage(stor, metav1.GroupVersionResource{ @@ -68,6 +79,26 @@ type organizationStorage struct { rbac roleBindingCreator clusterRoles []string + + impersonator impersonator +} + +type impersonator struct { + config *restclient.Config + opts client.Options +} + +func (c impersonator) Impersonate(u user.Info) (client.Client, error) { + var conf *restclient.Config + *conf = *c.config + + conf.Impersonate = restclient.ImpersonationConfig{ + UserName: u.GetName(), + UID: u.GetUID(), + Groups: u.GetGroups(), + Extra: u.GetExtra(), + } + return client.New(conf, c.opts) } func (s organizationStorage) New() runtime.Object {