Skip to content

Commit

Permalink
enhance: Add Describe User methods (#697)
Browse files Browse the repository at this point in the history
resolves: #698

I've added an entity:

```
type UserDescription struct {
	Name  string
	Roles []string
}
```

And two methods and tests for them:

- `DescribeUser(ctx context.Context, username string)
(entity.UserDescription, error)` - which returns UserDescription for a
specific user.
- `DescribeUsers(ctx context.Context) ([]entity.UserDescription, error)`
- which returns UserDescription for all users presented.

The thing that I want to raise is that I am not sure if it is feasible
to return empty `UserDescription` entity in case the user from
`DescribeUser` method doesn't exist, I would highly appreciate
suggestions here.

Also, I am not much familiar with the `milvus-sdk-go` codebase and this
is my first PR here, so any comments/feedback/suggestions would be
highly appreciated, thanks!

---------

Signed-off-by: punkerpunker <gleb.vazhenin@team.bumble.dev>
Co-authored-by: punkerpunker <gleb.vazhenin@team.bumble.dev>
  • Loading branch information
punkerpunker and punkerpunker authored Apr 26, 2024
1 parent 9c64a47 commit 931d6e0
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 1 deletion.
6 changes: 5 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,11 @@ type Client interface {
ListRoles(ctx context.Context) ([]entity.Role, error)
// ListUsers lists the user objects in system.
ListUsers(ctx context.Context) ([]entity.User, error)
// Grant adds object privileged for role.
// DescribeUser describes specific user attributes in the system
DescribeUser(ctx context.Context, username string) (entity.UserDescription, error)
// DescribeUsers describe all users attributes in the system
DescribeUsers(ctx context.Context) ([]entity.UserDescription, error)
// Grant adds privilege for role.
Grant(ctx context.Context, role string, objectType entity.PriviledgeObjectType, object string) error
// Revoke removes privilege from role.
Revoke(ctx context.Context, role string, objectType entity.PriviledgeObjectType, object string) error
Expand Down
74 changes: 74 additions & 0 deletions client/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,80 @@ func (c *GrpcClient) ListUsers(ctx context.Context) ([]entity.User, error) {
return users, nil
}

// DescribeUser lists the user descriptions in the system (name, roles)
func (c *GrpcClient) DescribeUser(ctx context.Context, username string) (entity.UserDescription, error) {
if c.Service == nil {
return entity.UserDescription{}, ErrClientNotReady
}

req := &milvuspb.SelectUserRequest{
User: &milvuspb.UserEntity{
Name: username,
},
IncludeRoleInfo: true,
}

resp, err := c.Service.SelectUser(ctx, req)

if err != nil {
return entity.UserDescription{}, err
}
if err = handleRespStatus(resp.GetStatus()); err != nil {
return entity.UserDescription{}, err
}
results := resp.GetResults()

if len(results) == 0 {
return entity.UserDescription{}, nil
}

userDescription := entity.UserDescription{
Name: results[0].GetUser().GetName(),
Roles: make([]string, 0, len(results[0].GetRoles())),
}

for _, role := range results[0].GetRoles() {
userDescription.Roles = append(userDescription.Roles, role.GetName())
}
return userDescription, nil
}

// DescribeUsers lists all users with descriptions (names, roles)
func (c *GrpcClient) DescribeUsers(ctx context.Context) ([]entity.UserDescription, error) {
if c.Service == nil {
return nil, ErrClientNotReady
}

req := &milvuspb.SelectUserRequest{
IncludeRoleInfo: true,
}

resp, err := c.Service.SelectUser(ctx, req)

if err != nil {
return nil, err
}
if err = handleRespStatus(resp.GetStatus()); err != nil {
return nil, err
}
results := resp.GetResults()

userDescriptions := make([]entity.UserDescription, 0, len(results))

for _, result := range results {
userDescription := entity.UserDescription{
Name: result.GetUser().GetName(),
Roles: make([]string, 0, len(result.GetRoles())),
}
for _, role := range result.GetRoles() {
userDescription.Roles = append(userDescription.Roles, role.GetName())
}
userDescriptions = append(userDescriptions, userDescription)
}

return userDescriptions, nil
}

// Grant adds object privileged for role.
func (c *GrpcClient) Grant(ctx context.Context, role string, objectType entity.PriviledgeObjectType, object string) error {
if c.Service == nil {
Expand Down
158 changes: 158 additions & 0 deletions client/rbac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,164 @@ func (s *RBACSuite) TestListUser() {
})
}

func (s *RBACSuite) TestDescribeUser() {
ctx := context.Background()
userName := "testUser"

s.Run("normal run", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()
s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Run(func(ctx context.Context, req *milvuspb.SelectUserRequest) {
s.True(req.GetIncludeRoleInfo())
s.Equal(req.GetUser().GetName(), userName)
}).Return(&milvuspb.SelectUserResponse{
Status: &commonpb.Status{ErrorCode: commonpb.ErrorCode_Success},
Results: []*milvuspb.UserResult{
{
User: &milvuspb.UserEntity{
Name: userName,
},
Roles: []*milvuspb.RoleEntity{
{Name: "role1"},
{Name: "role2"},
},
},
},
}, nil)

userDesc, err := s.client.DescribeUser(ctx, userName)

s.NoError(err)
s.Equal(userDesc.Name, userName)
s.ElementsMatch(userDesc.Roles, []string{"role1", "role2"})
})

s.Run("rpc error", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()
s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Return(nil, errors.New("mock error"))

_, err := s.client.DescribeUser(ctx, userName)
s.Error(err)
})

s.Run("status error", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()
s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Return(&milvuspb.SelectUserResponse{
Status: &commonpb.Status{ErrorCode: commonpb.ErrorCode_UnexpectedError},
}, nil)

_, err := s.client.DescribeUser(ctx, userName)
s.Error(err)
})

s.Run("service not ready", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

c := &GrpcClient{}
_, err := c.DescribeUser(ctx, userName)
s.Error(err)
s.ErrorIs(err, ErrClientNotReady)
})
}

func (s *RBACSuite) TestDescribeUsers() {
ctx := context.Background()

s.Run("normal run", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()

mockResults := []*milvuspb.UserResult{
{
User: &milvuspb.UserEntity{
Name: "user1",
},
Roles: []*milvuspb.RoleEntity{
{Name: "role1"},
{Name: "role2"},
},
},
{
User: &milvuspb.UserEntity{
Name: "user2",
},
Roles: []*milvuspb.RoleEntity{
{Name: "role3"},
},
},
}

s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Run(func(ctx context.Context, req *milvuspb.SelectUserRequest) {
s.True(req.GetIncludeRoleInfo())
}).Return(&milvuspb.SelectUserResponse{
Status: &commonpb.Status{ErrorCode: commonpb.ErrorCode_Success},
Results: mockResults,
}, nil)

userDescs, err := s.client.DescribeUsers(ctx)

s.NoError(err)
s.Len(userDescs, 2)

expectedDescs := []entity.UserDescription{
{
Name: "user1",
Roles: []string{"role1", "role2"},
},
{
Name: "user2",
Roles: []string{"role3"},
},
}

for i, desc := range userDescs {
s.Equal(expectedDescs[i].Name, desc.Name)
s.ElementsMatch(expectedDescs[i].Roles, desc.Roles)
}
})

s.Run("rpc error", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()

s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Return(nil, errors.New("mock error"))

_, err := s.client.DescribeUsers(ctx)
s.Error(err)
})

s.Run("status error", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer s.resetMock()

s.mock.EXPECT().SelectUser(mock.Anything, mock.Anything).Return(&milvuspb.SelectUserResponse{
Status: &commonpb.Status{ErrorCode: commonpb.ErrorCode_UnexpectedError},
}, nil)

_, err := s.client.DescribeUsers(ctx)
s.Error(err)
})

s.Run("service not ready", func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

c := &GrpcClient{}
_, err := c.DescribeUsers(ctx)
s.Error(err)
s.ErrorIs(err, ErrClientNotReady)
})
}

func (s *RBACSuite) TestGrant() {
ctx := context.Background()

Expand Down
6 changes: 6 additions & 0 deletions entity/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ type User struct {
Name string
}

// UserDescription is the model for RBAC user description object.
type UserDescription struct {
Name string
Roles []string
}

// Role is the model for RBAC role object.
type Role struct {
Name string
Expand Down

0 comments on commit 931d6e0

Please sign in to comment.