Skip to content
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

NEOS-1690: Exposes FE to license checks, puts object storage behind license #3072

Merged
Merged
1,121 changes: 605 additions & 516 deletions backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.go

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions backend/gen/go/protos/mgmt/v1alpha1/user_account.pb.json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion backend/internal/auth/apikey/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db"
"github.com/nucleuscloud/neosync/backend/internal/apikey"
nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors"
"github.com/nucleuscloud/neosync/backend/internal/neosyncdb"
"github.com/nucleuscloud/neosync/backend/internal/utils"
pkg_utils "github.com/nucleuscloud/neosync/backend/pkg/utils"
)
Expand Down Expand Up @@ -62,8 +63,10 @@ func (c *Client) InjectTokenCtx(ctx context.Context, header http.Header, spec co
token,
)
apiKey, err := c.q.GetAccountApiKeyByKeyValue(ctx, c.db, hashedKeyValue)
if err != nil {
if err != nil && !neosyncdb.IsNoRows(err) {
return nil, err
} else if err != nil && neosyncdb.IsNoRows(err) {
return nil, InvalidApiKeyErr
}

if time.Now().After(apiKey.ExpiresAt.Time) {
Expand Down
4 changes: 2 additions & 2 deletions backend/internal/cmds/mgmt/serve/connect/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ func serve(ctx context.Context) error {
IsAuthEnabled: isAuthEnabled,
IsNeosyncCloud: ncloudlicense.IsValid(),
DefaultMaxAllowedRecords: getDefaultMaxAllowedRecords(),
}, db, temporalConfigProvider, authclient, authadminclient, billingClient, rbacclient)
}, db, temporalConfigProvider, authclient, authadminclient, billingClient, rbacclient, cascadelicense)
api.Handle(
mgmtv1alpha1connect.NewUserAccountServiceHandler(
useraccountService,
Expand All @@ -491,7 +491,7 @@ func serve(ctx context.Context) error {
connect.WithRecover(recoverHandler),
),
)
userdataclient := userdata.NewClient(useraccountService, rbacclient)
userdataclient := userdata.NewClient(useraccountService, rbacclient, cascadelicense)

apiKeyService := v1alpha1_apikeyservice.New(&v1alpha1_apikeyservice.Config{
IsAuthEnabled: isAuthEnabled,
Expand Down
6 changes: 3 additions & 3 deletions backend/internal/ee/rbac/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func (r *Rbac) EnforceJob(
return err
}
if !ok {
return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s job", action))
return nucleuserrors.NewUnauthorized(fmt.Sprintf("user does not have permission to %s job", action))
}
return nil
}
Expand All @@ -355,7 +355,7 @@ func (r *Rbac) EnforceConnection(
return err
}
if !ok {
return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s connection", action))
return nucleuserrors.NewUnauthorized(fmt.Sprintf("user does not have permission to %s connection", action))
}
return nil
}
Expand All @@ -380,7 +380,7 @@ func (r *Rbac) EnforceAccount(
return err
}
if !ok {
return nucleuserrors.NewForbidden(fmt.Sprintf("user does not have permission to %s account", action))
return nucleuserrors.NewUnauthorized(fmt.Sprintf("user does not have permission to %s account", action))
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions backend/internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ func NewAlreadyExists(message string) error {
return connect.NewError(connect.CodeAlreadyExists, errors.New(message))
}

// Identical to NewUnauthorized
func NewForbidden(message string) error {
return connect.NewError(connect.CodePermissionDenied, errors.New(message))
return NewUnauthorized(message)
}

func NewUnauthenticated(message string) error {
return connect.NewError(connect.CodeUnauthenticated, errors.New(message))
}

// Identical to NewUnauthenticated
func NewUnauthorized(message string) error {
return NewUnauthenticated(message)
return connect.NewError(connect.CodePermissionDenied, errors.New(message))
}

func NewNotImplemented(message string) error {
Expand Down
7 changes: 6 additions & 1 deletion backend/internal/userdata/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey"
"github.com/nucleuscloud/neosync/backend/internal/ee/rbac"
"github.com/nucleuscloud/neosync/backend/internal/neosyncdb"
"github.com/nucleuscloud/neosync/internal/ee/license"
)

type UserServiceClient interface {
Expand All @@ -19,6 +20,7 @@ type UserServiceClient interface {
type Client struct {
userServiceClient UserServiceClient
enforcer rbac.EntityEnforcer
license license.EEInterface
}

type Interface interface {
Expand All @@ -32,10 +34,12 @@ type GetUserResponse struct {
func NewClient(
userServiceClient UserServiceClient,
enforcer rbac.EntityEnforcer,
eeLicense license.EEInterface,
) *Client {
return &Client{
userServiceClient: userServiceClient,
enforcer: enforcer,
license: eeLicense,
}
}

Expand All @@ -55,12 +59,13 @@ func (c *Client) GetUser(ctx context.Context) (*User, error) {
id: pguuid,
apiKeyData: apiKeyData,
userAccountServiceClient: c.userServiceClient,
license: c.license,
}
user.EntityEnforcer = &UserEntityEnforcer{
enforcer: c.enforcer,
user: rbac.NewUserIdEntity(resp.Msg.GetUserId()),
enforceAccountAccess: func(ctx context.Context, accountId string) error {
return EnforceAccountAccess(ctx, user, accountId)
return enforceAccountAccess(ctx, user, accountId)
},
isApiKey: user.IsApiKey(),
}
Expand Down
41 changes: 38 additions & 3 deletions backend/internal/userdata/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
auth_apikey "github.com/nucleuscloud/neosync/backend/internal/auth/apikey"
nucleuserrors "github.com/nucleuscloud/neosync/backend/internal/errors"
"github.com/nucleuscloud/neosync/backend/internal/neosyncdb"
"github.com/nucleuscloud/neosync/internal/ee/license"
)

type UserAccountServiceClient interface {
Expand All @@ -25,6 +26,8 @@ type User struct {
userAccountServiceClient UserAccountServiceClient

EntityEnforcer

license license.EEInterface
}

func (u *User) Id() string {
Expand All @@ -42,15 +45,47 @@ func (u *User) IsApiKey() bool {
return u.apiKeyData != nil
}

func EnforceAccountAccess(ctx context.Context, user *User, accountId string) error {
func (u *User) EnforceAccountAccess(ctx context.Context, accountId string) error {
return enforceAccountAccess(ctx, u, accountId)
}

func (u *User) EnforceLicense(ctx context.Context, accountId string) error {
ok, err := u.IsLicensed(ctx, accountId)
if err != nil {
return err
}
if !ok {
return nucleuserrors.NewUnauthorized("account does not have an active license")
}
return nil
}

func (u *User) IsLicensed(ctx context.Context, accountId string) (bool, error) {
if err := u.EnforceAccountAccess(ctx, accountId); err != nil {
return false, err
}

// todo: check account type for Neosync Cloud Cloud?
// if: personal, then check if free trial is active
// if: pro, then no? or maybe still do a trial check?
// if: enterprise, then check for valid license

if u.license == nil {
return false, nil
}

return u.license.IsValid(), nil
}

func enforceAccountAccess(ctx context.Context, user *User, accountId string) error {
if user.IsApiKey() {
if user.IsWorkerApiKey() {
return nil
}
// We first want to check to make sure the api key is valid and that it says it's in the account
// However, we still want to make a DB request to ensure the DB still says it's in the account
if user.apiKeyData.ApiKey == nil || neosyncdb.UUIDString(user.apiKeyData.ApiKey.AccountID) != accountId {
return nucleuserrors.NewForbidden("api key is not valid for account")
return nucleuserrors.NewUnauthorized("api key is not valid for account")
}
}

Expand All @@ -60,7 +95,7 @@ func EnforceAccountAccess(ctx context.Context, user *User, accountId string) err
return fmt.Errorf("unable to check if user is in account: %w", err)
}
if !inAccountResp.Msg.GetOk() {
return nucleuserrors.NewForbidden("user is not in account")
return nucleuserrors.NewUnauthorized("user is not in account")
}
return nil
}
75 changes: 75 additions & 0 deletions backend/pkg/integration-test/clients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package integrationtests_test

import (
"net/http"

"github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect"
http_client "github.com/nucleuscloud/neosync/internal/http/client"
)

type NeosyncClients struct {
httpUrl string
}

func newNeosyncClients(httpUrl string) *NeosyncClients {
return &NeosyncClients{
httpUrl: httpUrl,
}
}

type clientConfig struct {
userId string
}

type ClientConfigOption func(*clientConfig)

func WithUserId(userId string) ClientConfigOption {
return func(c *clientConfig) {
c.userId = userId
}
}

func (s *NeosyncClients) Users(opts ...ClientConfigOption) mgmtv1alpha1connect.UserAccountServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewUserAccountServiceClient(getHttpClient(config), s.httpUrl)
}

func (s *NeosyncClients) Connections(opts ...ClientConfigOption) mgmtv1alpha1connect.ConnectionServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewConnectionServiceClient(getHttpClient(config), s.httpUrl)
}

func (s *NeosyncClients) Anonymize(opts ...ClientConfigOption) mgmtv1alpha1connect.AnonymizationServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewAnonymizationServiceClient(getHttpClient(config), s.httpUrl)
}

func (s *NeosyncClients) Jobs(opts ...ClientConfigOption) mgmtv1alpha1connect.JobServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewJobServiceClient(getHttpClient(config), s.httpUrl)
}

func (s *NeosyncClients) Transformers(opts ...ClientConfigOption) mgmtv1alpha1connect.TransformersServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewTransformersServiceClient(getHttpClient(config), s.httpUrl)
}

func (s *NeosyncClients) ConnectionData(opts ...ClientConfigOption) mgmtv1alpha1connect.ConnectionDataServiceClient {
config := getHydratedClientConfig(opts...)
return mgmtv1alpha1connect.NewConnectionDataServiceClient(getHttpClient(config), s.httpUrl)
}

func getHydratedClientConfig(opts ...ClientConfigOption) *clientConfig {
config := &clientConfig{}
for _, opt := range opts {
opt(config)
}
return config
}

func getHttpClient(config *clientConfig) *http.Client {
if config.userId != "" {
return http_client.WithBearerAuth(&http.Client{}, &config.userId)
}
return &http.Client{}
}
Loading
Loading