Skip to content
Merged
7 changes: 5 additions & 2 deletions central/graphql/resolvers/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func init() {
schema := getBuilder()
utils.Must(
schema.AddQuery("groups: [Group!]!"),
schema.AddQuery("group(authProviderId: String, key: String, value: String): Group"),
schema.AddQuery("group(authProviderId: String, key: String, value: String, id: String): Group"),
)
}

Expand All @@ -30,7 +30,7 @@ func (resolver *Resolver) Groups(ctx context.Context) ([]*groupResolver, error)
}

// Group returns a GraphQL resolver for the matching group, if it exists
func (resolver *Resolver) Group(ctx context.Context, args struct{ AuthProviderID, Key, Value *string }) (*groupResolver, error) {
func (resolver *Resolver) Group(ctx context.Context, args struct{ AuthProviderID, Key, Value, ID *string }) (*groupResolver, error) {
defer metrics.SetGraphQLOperationDurationTime(time.Now(), pkgMetrics.Root, "Group")

err := readGroups(ctx)
Expand All @@ -47,6 +47,9 @@ func (resolver *Resolver) Group(ctx context.Context, args struct{ AuthProviderID
if args.Value != nil {
props.Value = *args.Value
}
if args.ID != nil {
props.Id = *args.ID
}
grp, err := resolver.GroupDataStore.Get(ctx, props)
return resolver.wrapGroup(grp, grp != nil, err)
}
1 change: 0 additions & 1 deletion central/group/datastore/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ type DataStore interface {

Add(ctx context.Context, group *storage.Group) error
Update(ctx context.Context, group *storage.Group) error
Upsert(ctx context.Context, group *storage.Group) error
Mutate(ctx context.Context, remove, update, add []*storage.Group) error
Remove(ctx context.Context, props *storage.GroupProperties) error
RemoveAllWithAuthProviderID(ctx context.Context, authProviderID string) error
Expand Down
52 changes: 44 additions & 8 deletions central/group/datastore/datastore_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/stackrox/rox/central/group/datastore/internal/store"
"github.com/stackrox/rox/central/role/resources"
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/errox"
"github.com/stackrox/rox/pkg/sac"
)

Expand Down Expand Up @@ -65,6 +66,21 @@ func (ds *dataStoreImpl) Add(ctx context.Context, group *storage.Group) error {
return sac.ErrResourceAccessDenied
}

if err := ValidateGroup(group); err != nil {
return errox.InvalidArgs.CausedBy(err)
}

if group.GetProps().GetId() != "" {
return errox.InvalidArgs.Newf("id should be empty but %q was provided", group.GetProps().GetId())
}

if group.GetProps() != nil {
group.GetProps().Id = GenerateGroupID()
} else {
// Theoretically should never happen, as the auth provider ID is required to be set.
group.Props = &storage.GroupProperties{Id: GenerateGroupID()}
}

return ds.storage.Add(group)
}

Expand All @@ -75,24 +91,40 @@ func (ds *dataStoreImpl) Update(ctx context.Context, group *storage.Group) error
return sac.ErrResourceAccessDenied
}

if err := ValidateGroup(group); err != nil {
return errox.InvalidArgs.CausedBy(err)
}

return ds.storage.Update(group)
}

func (ds *dataStoreImpl) Upsert(ctx context.Context, group *storage.Group) error {
func (ds *dataStoreImpl) Mutate(ctx context.Context, remove, update, add []*storage.Group) error {
if ok, err := groupSAC.WriteAllowed(ctx); err != nil {
return err
} else if !ok {
return sac.ErrResourceAccessDenied
}

return ds.storage.Upsert(group)
}
for _, grp := range append(remove, update...) {
if err := ValidateGroup(grp); err != nil {
return errox.InvalidArgs.CausedBy(err)
}
}

func (ds *dataStoreImpl) Mutate(ctx context.Context, remove, update, add []*storage.Group) error {
if ok, err := groupSAC.WriteAllowed(ctx); err != nil {
return err
} else if !ok {
return sac.ErrResourceAccessDenied
for _, grp := range add {
if err := ValidateGroup(grp); err != nil {
return errox.InvalidArgs.CausedBy(err)
}

if grp.GetProps().GetId() != "" {
return errox.InvalidArgs.Newf("id should be empty but %q was provided", grp.GetProps().GetId())
}
if grp.GetProps() != nil {
grp.GetProps().Id = GenerateGroupID()
} else {
// Theoretically should never happen, as the auth provider ID is required to be set.
grp.Props = &storage.GroupProperties{Id: GenerateGroupID()}
}
}

return ds.storage.Mutate(remove, update, add)
Expand All @@ -105,6 +137,10 @@ func (ds *dataStoreImpl) Remove(ctx context.Context, props *storage.GroupPropert
return sac.ErrResourceAccessDenied
}

if err := ValidateProps(props); err != nil {
return errox.InvalidArgs.CausedBy(err)
}

return ds.storage.Remove(props)
}

Expand Down
125 changes: 89 additions & 36 deletions central/group/datastore/datastore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ func TestGroupDataStore(t *testing.T) {
suite.Run(t, new(groupDataStoreTestSuite))
}

var (
groupWithID = &storage.Group{Props: &storage.GroupProperties{
Id: "123",
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
)

type groupDataStoreTestSuite struct {
suite.Suite

Expand Down Expand Up @@ -127,99 +136,143 @@ func (s *groupDataStoreTestSuite) TestAllowsWalk() {
func (s *groupDataStoreTestSuite) TestEnforcesAdd() {
s.storage.EXPECT().Add(gomock.Any()).Times(0)

err := s.dataStore.Add(s.hasNoneCtx, &storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Add(s.hasNoneCtx, grp)
s.Error(err, "expected an error trying to write without permissions")

err = s.dataStore.Add(s.hasReadCtx, &storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Add(s.hasReadCtx, grp)
s.Error(err, "expected an error trying to write without permissions")
}

func (s *groupDataStoreTestSuite) TestAllowsAdd() {
s.storage.EXPECT().Add(gomock.Any()).Return(nil).Times(2)

err := s.dataStore.Add(s.hasWriteCtx, &storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Add(s.hasWriteCtx, grp)
s.NoError(err, "expected no error trying to write with permissions")

err = s.dataStore.Add(s.hasWriteAccessCtx, &storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Add(s.hasWriteAccessCtx, grp)
s.NoError(err, "expected no error trying to write with Access permissions")
}

func (s *groupDataStoreTestSuite) TestEnforcesUpdate() {
s.storage.EXPECT().Update(gomock.Any()).Times(0)

err := s.dataStore.Update(s.hasNoneCtx, &storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Update(s.hasNoneCtx, grp)
s.Error(err, "expected an error trying to write without permissions")

err = s.dataStore.Update(s.hasReadCtx, &storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Update(s.hasReadCtx, grp)
s.Error(err, "expected an error trying to write without permissions")
}

func (s *groupDataStoreTestSuite) TestAllowsUpdate() {
s.storage.EXPECT().Update(gomock.Any()).Return(nil).Times(2)

err := s.dataStore.Update(s.hasWriteCtx, &storage.Group{})
s.NoError(err, "expected no error trying to write with permissions")

err = s.dataStore.Update(s.hasWriteAccessCtx, &storage.Group{})
s.NoError(err, "expected no error trying to write with Access permissions")
}

func (s *groupDataStoreTestSuite) TestEnforcesUpsert() {
s.storage.EXPECT().Upsert(gomock.Any()).Times(0)

err := s.dataStore.Upsert(s.hasNoneCtx, &storage.Group{})
s.Error(err, "expected an error trying to write without permissions")

err = s.dataStore.Upsert(s.hasReadCtx, &storage.Group{})
s.Error(err, "expected an error trying to write without permissions")
}

func (s *groupDataStoreTestSuite) TestAllowsUpsert() {
s.storage.EXPECT().Upsert(gomock.Any()).Return(nil).Times(2)

err := s.dataStore.Upsert(s.hasWriteCtx, &storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Update(s.hasWriteCtx, grp)
s.NoError(err, "expected no error trying to write with permissions")

err = s.dataStore.Upsert(s.hasWriteAccessCtx, &storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Update(s.hasWriteAccessCtx, grp)
s.NoError(err, "expected no error trying to write with Access permissions")
}

func (s *groupDataStoreTestSuite) TestEnforcesMutate() {
s.storage.EXPECT().Mutate(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)

err := s.dataStore.Mutate(s.hasNoneCtx, []*storage.Group{}, []*storage.Group{}, []*storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Mutate(s.hasNoneCtx, []*storage.Group{groupWithID}, []*storage.Group{groupWithID},
[]*storage.Group{grp})
s.Error(err, "expected an error trying to write without permissions")

err = s.dataStore.Mutate(s.hasReadCtx, []*storage.Group{}, []*storage.Group{}, []*storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Mutate(s.hasReadCtx, []*storage.Group{groupWithID}, []*storage.Group{groupWithID},
[]*storage.Group{grp})
s.Error(err, "expected an error trying to write without permissions")
}

func (s *groupDataStoreTestSuite) TestAllowsMutate() {
s.storage.EXPECT().Mutate(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2)

err := s.dataStore.Mutate(s.hasWriteCtx, []*storage.Group{}, []*storage.Group{}, []*storage.Group{})
grp := &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err := s.dataStore.Mutate(s.hasWriteCtx, []*storage.Group{groupWithID}, []*storage.Group{groupWithID},
[]*storage.Group{grp})
s.NoError(err, "expected no error trying to write with permissions")

err = s.dataStore.Mutate(s.hasWriteAccessCtx, []*storage.Group{}, []*storage.Group{}, []*storage.Group{})
grp = &storage.Group{Props: &storage.GroupProperties{
AuthProviderId: "123",
Key: "123",
Value: "123",
}, RoleName: "123"}
err = s.dataStore.Mutate(s.hasWriteAccessCtx, []*storage.Group{groupWithID}, []*storage.Group{groupWithID},
[]*storage.Group{grp})
s.NoError(err, "expected no error trying to write with Access permissions")
}

func (s *groupDataStoreTestSuite) TestEnforcesRemove() {
s.storage.EXPECT().Remove(gomock.Any()).Times(0)

err := s.dataStore.Remove(s.hasNoneCtx, &storage.GroupProperties{})
err := s.dataStore.Remove(s.hasNoneCtx, groupWithID.GetProps())
s.Error(err, "expected an error trying to write without permissions")

err = s.dataStore.Remove(s.hasReadCtx, &storage.GroupProperties{})
err = s.dataStore.Remove(s.hasReadCtx, groupWithID.GetProps())
s.Error(err, "expected an error trying to write without permissions")
}

func (s *groupDataStoreTestSuite) TestAllowsRemove() {
s.storage.EXPECT().Remove(gomock.Any()).Return(nil).Times(2)

err := s.dataStore.Remove(s.hasWriteCtx, &storage.GroupProperties{})
err := s.dataStore.Remove(s.hasWriteCtx, groupWithID.GetProps())
s.NoError(err, "expected no error trying to write with permissions")

err = s.dataStore.Remove(s.hasWriteAccessCtx, &storage.GroupProperties{})
err = s.dataStore.Remove(s.hasWriteAccessCtx, groupWithID.GetProps())
s.NoError(err, "expected no error trying to write with Access permissions")
}
14 changes: 0 additions & 14 deletions central/group/datastore/internal/store/mocks/store.go

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

18 changes: 14 additions & 4 deletions central/group/datastore/internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ package store
import (
"github.com/stackrox/rox/generated/storage"
"github.com/stackrox/rox/pkg/bolthelper"
"github.com/stackrox/rox/pkg/utils"
bolt "go.etcd.io/bbolt"
)

var groupsBucket = []byte("groups2")

var isEmptyGroupPropertiesF = func(props *storage.GroupProperties) bool {
if props.GetAuthProviderId() == "" && props.GetKey() == "" && props.GetValue() == "" {
return true
}
return false
}

// Store updates and utilizes groups, which are attribute to role mappings.
//go:generate mockgen-wrapper
type Store interface {
Expand All @@ -19,7 +27,6 @@ type Store interface {

Add(*storage.Group) error
Update(*storage.Group) error
Upsert(*storage.Group) error
Mutate(remove, update, add []*storage.Group) error
Remove(props *storage.GroupProperties) error
}
Expand All @@ -31,9 +38,12 @@ func New(db *bolt.DB) Store {
store := &storeImpl{
db: db,
}

allEmptyGroupProperty := storage.GroupProperties{AuthProviderId: "", Key: "", Value: ""}
_ = store.Remove(&allEmptyGroupProperty) // ignore error to suppress warning
grps, err := store.GetFiltered(isEmptyGroupPropertiesF)
utils.Should(err)
for _, grp := range grps {
err = store.Remove(grp.GetProps())
utils.Should(err)
}

return store
}
Loading