settings: Add spec and stubs for ListRoleAssignmentsFiltered request

This commit is contained in:
Ralf Haferkamp
2024-05-22 16:35:24 +02:00
committed by Ralf Haferkamp
parent b870c0df12
commit 1b47707e2f
12 changed files with 1344 additions and 693 deletions

View File

File diff suppressed because it is too large Load Diff

View File

@@ -118,6 +118,42 @@ func (m *UserRoleAssignment) UnmarshalJSON(b []byte) error {
var _ json.Unmarshaler = (*UserRoleAssignment)(nil)
// UserRoleAssignmentFilterJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of UserRoleAssignmentFilter. This struct is safe to replace or modify but
// should not be done so concurrently.
var UserRoleAssignmentFilterJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *UserRoleAssignmentFilter) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := UserRoleAssignmentFilterJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*UserRoleAssignmentFilter)(nil)
// UserRoleAssignmentFilterJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of UserRoleAssignmentFilter. This struct is safe to replace or modify but
// should not be done so concurrently.
var UserRoleAssignmentFilterJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *UserRoleAssignmentFilter) UnmarshalJSON(b []byte) error {
return UserRoleAssignmentFilterJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*UserRoleAssignmentFilter)(nil)
// ResourceJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of Resource. This struct is safe to replace or modify but
// should not be done so concurrently.

View File

File diff suppressed because it is too large Load Diff

View File

@@ -394,6 +394,12 @@ func NewRoleServiceEndpoints() []*api.Endpoint {
Method: []string{"POST"},
Handler: "rpc",
},
{
Name: "RoleService.ListRoleAssignmentsFiltered",
Path: []string{"/api/v0/settings/assignments-list-filtered"},
Method: []string{"POST"},
Handler: "rpc",
},
{
Name: "RoleService.AssignRoleToUser",
Path: []string{"/api/v0/settings/assignments-add"},
@@ -414,6 +420,7 @@ func NewRoleServiceEndpoints() []*api.Endpoint {
type RoleService interface {
ListRoles(ctx context.Context, in *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error)
ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error)
ListRoleAssignmentsFiltered(ctx context.Context, in *ListRoleAssignmentsFilteredRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error)
AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error)
RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, opts ...client.CallOption) (*emptypb.Empty, error)
}
@@ -450,6 +457,16 @@ func (c *roleService) ListRoleAssignments(ctx context.Context, in *ListRoleAssig
return out, nil
}
func (c *roleService) ListRoleAssignmentsFiltered(ctx context.Context, in *ListRoleAssignmentsFilteredRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error) {
req := c.c.NewRequest(c.name, "RoleService.ListRoleAssignmentsFiltered", in)
out := new(ListRoleAssignmentsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roleService) AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error) {
req := c.c.NewRequest(c.name, "RoleService.AssignRoleToUser", in)
out := new(AssignRoleToUserResponse)
@@ -475,6 +492,7 @@ func (c *roleService) RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFrom
type RoleServiceHandler interface {
ListRoles(context.Context, *ListBundlesRequest, *ListBundlesResponse) error
ListRoleAssignments(context.Context, *ListRoleAssignmentsRequest, *ListRoleAssignmentsResponse) error
ListRoleAssignmentsFiltered(context.Context, *ListRoleAssignmentsFilteredRequest, *ListRoleAssignmentsResponse) error
AssignRoleToUser(context.Context, *AssignRoleToUserRequest, *AssignRoleToUserResponse) error
RemoveRoleFromUser(context.Context, *RemoveRoleFromUserRequest, *emptypb.Empty) error
}
@@ -483,6 +501,7 @@ func RegisterRoleServiceHandler(s server.Server, hdlr RoleServiceHandler, opts .
type roleService interface {
ListRoles(ctx context.Context, in *ListBundlesRequest, out *ListBundlesResponse) error
ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, out *ListRoleAssignmentsResponse) error
ListRoleAssignmentsFiltered(ctx context.Context, in *ListRoleAssignmentsFilteredRequest, out *ListRoleAssignmentsResponse) error
AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, out *AssignRoleToUserResponse) error
RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, out *emptypb.Empty) error
}
@@ -502,6 +521,12 @@ func RegisterRoleServiceHandler(s server.Server, hdlr RoleServiceHandler, opts .
Method: []string{"POST"},
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.ListRoleAssignmentsFiltered",
Path: []string{"/api/v0/settings/assignments-list-filtered"},
Method: []string{"POST"},
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.AssignRoleToUser",
Path: []string{"/api/v0/settings/assignments-add"},
@@ -529,6 +554,10 @@ func (h *roleServiceHandler) ListRoleAssignments(ctx context.Context, in *ListRo
return h.RoleServiceHandler.ListRoleAssignments(ctx, in, out)
}
func (h *roleServiceHandler) ListRoleAssignmentsFiltered(ctx context.Context, in *ListRoleAssignmentsFilteredRequest, out *ListRoleAssignmentsResponse) error {
return h.RoleServiceHandler.ListRoleAssignmentsFiltered(ctx, in, out)
}
func (h *roleServiceHandler) AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, out *AssignRoleToUserResponse) error {
return h.RoleServiceHandler.AssignRoleToUser(ctx, in, out)
}

View File

@@ -354,6 +354,32 @@ func (h *webRoleServiceHandler) ListRoleAssignments(w http.ResponseWriter, r *ht
render.JSON(w, r, resp)
}
func (h *webRoleServiceHandler) ListRoleAssignmentsFiltered(w http.ResponseWriter, r *http.Request) {
req := &ListRoleAssignmentsFilteredRequest{}
resp := &ListRoleAssignmentsResponse{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusPreconditionFailed)
return
}
if err := h.h.ListRoleAssignmentsFiltered(
r.Context(),
req,
resp,
); err != nil {
if merr, ok := merrors.As(err); ok && merr.Code == http.StatusNotFound {
http.Error(w, err.Error(), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusBadRequest)
}
return
}
render.Status(r, http.StatusCreated)
render.JSON(w, r, resp)
}
func (h *webRoleServiceHandler) AssignRoleToUser(w http.ResponseWriter, r *http.Request) {
req := &AssignRoleToUserRequest{}
resp := &AssignRoleToUserResponse{}
@@ -414,6 +440,7 @@ func RegisterRoleServiceWeb(r chi.Router, i RoleServiceHandler, middlewares ...f
r.MethodFunc("POST", "/api/v0/settings/roles-list", handler.ListRoles)
r.MethodFunc("POST", "/api/v0/settings/assignments-list", handler.ListRoleAssignments)
r.MethodFunc("POST", "/api/v0/settings/assignments-list-filtered", handler.ListRoleAssignmentsFiltered)
r.MethodFunc("POST", "/api/v0/settings/assignments-add", handler.AssignRoleToUser)
r.MethodFunc("POST", "/api/v0/settings/assignments-remove", handler.RemoveRoleFromUser)
}
@@ -1128,6 +1155,42 @@ func (m *ListRoleAssignmentsRequest) UnmarshalJSON(b []byte) error {
var _ json.Unmarshaler = (*ListRoleAssignmentsRequest)(nil)
// ListRoleAssignmentsFilteredRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of ListRoleAssignmentsFilteredRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var ListRoleAssignmentsFilteredRequestJSONMarshaler = new(jsonpb.Marshaler)
// MarshalJSON satisfies the encoding/json Marshaler interface. This method
// uses the more correct jsonpb package to correctly marshal the message.
func (m *ListRoleAssignmentsFilteredRequest) MarshalJSON() ([]byte, error) {
if m == nil {
return json.Marshal(nil)
}
buf := &bytes.Buffer{}
if err := ListRoleAssignmentsFilteredRequestJSONMarshaler.Marshal(buf, m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
var _ json.Marshaler = (*ListRoleAssignmentsFilteredRequest)(nil)
// ListRoleAssignmentsFilteredRequestJSONUnmarshaler describes the default jsonpb.Unmarshaler used by all
// instances of ListRoleAssignmentsFilteredRequest. This struct is safe to replace or modify but
// should not be done so concurrently.
var ListRoleAssignmentsFilteredRequestJSONUnmarshaler = new(jsonpb.Unmarshaler)
// UnmarshalJSON satisfies the encoding/json Unmarshaler interface. This method
// uses the more correct jsonpb package to correctly unmarshal the message.
func (m *ListRoleAssignmentsFilteredRequest) UnmarshalJSON(b []byte) error {
return ListRoleAssignmentsFilteredRequestJSONUnmarshaler.Unmarshal(bytes.NewReader(b), m)
}
var _ json.Unmarshaler = (*ListRoleAssignmentsFilteredRequest)(nil)
// ListRoleAssignmentsResponseJSONMarshaler describes the default jsonpb.Marshaler used by all
// instances of ListRoleAssignmentsResponse. This struct is safe to replace or modify but
// should not be done so concurrently.

View File

@@ -102,6 +102,38 @@
]
}
},
"/api/v0/settings/assignments-list-filtered": {
"post": {
"operationId": "RoleService_ListRoleAssignmentsFiltered",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v0ListRoleAssignmentsResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v0ListRoleAssignmentsFilteredRequest"
}
}
],
"tags": [
"RoleService"
]
}
},
"/api/v0/settings/assignments-remove": {
"post": {
"operationId": "RoleService_RemoveRoleFromUser",
@@ -935,6 +967,17 @@
}
}
},
"v0ListRoleAssignmentsFilteredRequest": {
"type": "object",
"properties": {
"filters": {
"type": "array",
"items": {
"$ref": "#/definitions/v0UserRoleAssignmentFilter"
}
}
}
},
"v0ListRoleAssignmentsRequest": {
"type": "object",
"properties": {
@@ -1196,6 +1239,29 @@
}
}
},
"v0UserRoleAssignmentFilter": {
"type": "object",
"properties": {
"type": {
"$ref": "#/definitions/v0UserRoleAssignmentFilterType"
},
"accountUuid": {
"type": "string"
},
"roleId": {
"type": "string"
}
}
},
"v0UserRoleAssignmentFilterType": {
"type": "string",
"enum": [
"TYPE_UNKNOWN",
"TYPE_ACCOUNT",
"TYPE_ROLE"
],
"default": "TYPE_UNKNOWN"
},
"v0ValueWithIdentifier": {
"type": "object",
"properties": {

View File

@@ -31,6 +31,19 @@ message UserRoleAssignment {
string role_id = 3;
}
message UserRoleAssignmentFilter {
enum Type {
TYPE_UNKNOWN = 0;
TYPE_ACCOUNT = 1;
TYPE_ROLE = 2;
}
Type type = 1;
oneof term {
string account_uuid = 2;
string role_id = 3;
}
}
// ---
// resource payloads
// ---

View File

@@ -106,6 +106,12 @@ service RoleService {
body: "*"
};
}
rpc ListRoleAssignmentsFiltered(ListRoleAssignmentsFilteredRequest) returns (ListRoleAssignmentsResponse) {
option (google.api.http) = {
post: "/api/v0/settings/assignments-list-filtered",
body: "*"
};
}
rpc AssignRoleToUser(AssignRoleToUserRequest) returns (AssignRoleToUserResponse) {
option (google.api.http) = {
post: "/api/v0/settings/assignments-add",
@@ -224,6 +230,10 @@ message ListRoleAssignmentsRequest {
string account_uuid = 1;
}
message ListRoleAssignmentsFilteredRequest {
repeated ocis.messages.settings.v0.UserRoleAssignmentFilter filters = 1;
}
message ListRoleAssignmentsResponse {
repeated ocis.messages.settings.v0.UserRoleAssignment assignments = 1;
}

View File

@@ -52,6 +52,7 @@ type GetGatewayServiceClientFunc func() (gateway.GatewayAPIClient, error)
type RoleService interface {
ListRoles(ctx context.Context, in *settingssvc.ListBundlesRequest, opts ...client.CallOption) (*settingssvc.ListBundlesResponse, error)
ListRoleAssignments(ctx context.Context, in *settingssvc.ListRoleAssignmentsRequest, opts ...client.CallOption) (*settingssvc.ListRoleAssignmentsResponse, error)
ListRoleAssignmentsFiltered(ctx context.Context, in *settingssvc.ListRoleAssignmentsFilteredRequest, opts ...client.CallOption) (*settingssvc.ListRoleAssignmentsResponse, error)
AssignRoleToUser(ctx context.Context, in *settingssvc.AssignRoleToUserRequest, opts ...client.CallOption) (*settingssvc.AssignRoleToUserResponse, error)
RemoveRoleFromUser(ctx context.Context, in *settingssvc.RemoveRoleFromUserRequest, opts ...client.CallOption) (*emptypb.Empty, error)
}

View File

@@ -342,6 +342,28 @@ func (g Service) ListRoleAssignments(ctx context.Context, req *settingssvc.ListR
return nil
}
func (g Service) ListRoleAssignmentsFiltered(ctx context.Context, req *settingssvc.ListRoleAssignmentsFilteredRequest, res *settingssvc.ListRoleAssignmentsResponse) error {
if validationError := validateListRoleAssignmentsFiltered(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
filters := req.GetFilters()
var r []*settingsmsg.UserRoleAssignment
var err error
switch filters[0].GetType() {
case settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT:
accountUUID := getValidatedAccountUUID(ctx, filters[0].GetAccountUuid())
r, err = g.manager.ListRoleAssignments(accountUUID)
case settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE:
err = fmt.Errorf("filtering by role not implemented")
}
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
res.Assignments = r
return nil
}
// AssignRoleToUser implements the RoleServiceHandler interface
func (g Service) AssignRoleToUser(ctx context.Context, req *settingssvc.AssignRoleToUserRequest, res *settingssvc.AssignRoleToUserResponse) error {
req.AccountUuid = getValidatedAccountUUID(ctx, req.AccountUuid)

View File

@@ -234,3 +234,105 @@ func TestListPermissionsOfOtherUser(t *testing.T) {
assert.Equal(t, int32(http.StatusNotFound), merr.Code)
assert.Contains(t, err.Error(), req.AccountUuid)
}
func TestListRoleAssignmentsFiltered(t *testing.T) {
manager := &mocks.Manager{}
svc := Service{
manager: manager,
}
tests := map[string]struct {
req *v0.ListRoleAssignmentsFilteredRequest
statusCode int32
}{
"no filters": {
req: &v0.ListRoleAssignmentsFilteredRequest{},
statusCode: http.StatusBadRequest,
},
"multiple filters": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT,
Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{
AccountUuid: "uid",
},
},
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{
RoleId: "rid",
},
},
},
},
statusCode: http.StatusBadRequest,
},
"bad filtertype": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_UNKNOWN,
},
},
},
statusCode: http.StatusBadRequest,
},
"account filter without term": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT,
},
},
},
statusCode: http.StatusBadRequest,
},
"account filter with invalid term": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT,
Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{
AccountUuid: "invalid-&*&^%$#",
},
},
},
},
statusCode: http.StatusBadRequest,
},
"role filter without term": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
},
},
},
statusCode: http.StatusBadRequest,
},
"role filter with invalid uuid": {
req: &v0.ListRoleAssignmentsFilteredRequest{
Filters: []*settingsmsg.UserRoleAssignmentFilter{
{
Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{
RoleId: "this is no uuid",
},
},
},
},
statusCode: http.StatusBadRequest,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
res := v0.ListRoleAssignmentsResponse{}
err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, test.req, &res)
merr, ok := merrors.As(err)
assert.True(t, ok)
assert.Equal(t, int32(test.statusCode), merr.Code)
})
}
}

View File

@@ -1,6 +1,7 @@
package svc
import (
"errors"
"regexp"
validation "github.com/go-ozzo/ozzo-validation/v4"
@@ -116,6 +117,57 @@ func validateListRoleAssignments(req *settingssvc.ListRoleAssignmentsRequest) er
return validation.Validate(req.AccountUuid, requireAccountID...)
}
func validateListRoleAssignmentsFiltered(req *settingssvc.ListRoleAssignmentsFilteredRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.Filters,
validation.Required,
validation.Length(1, 1),
validation.Each(validation.By(validateUserRoleAssignmentFilter)),
),
)
}
func validateUserRoleAssignmentFilter(values interface{}) error {
filter, ok := values.(settingsmsg.UserRoleAssignmentFilter)
if !ok {
return errors.New("expected UserRoleAssignmentFilter")
}
return validation.ValidateStruct(
&filter,
validation.Field(&filter.Type,
validation.Required,
validation.In(settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT, settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE),
),
validation.Field(&filter.Term,
validation.When(
filter.Type == settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT,
validation.By(validateFilterAccountUUID),
),
validation.When(
filter.Type == settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE,
validation.By(validateFilterRoleID),
),
),
)
}
func validateFilterRoleID(value interface{}) error {
roleTerm, ok := value.(*settingsmsg.UserRoleAssignmentFilter_RoleId)
if !ok {
return errors.New("expected UserRoleAssignmentFilter_RoleId")
}
return validation.Validate(&roleTerm.RoleId, is.UUID)
}
func validateFilterAccountUUID(value interface{}) error {
accountTerm, ok := value.(*settingsmsg.UserRoleAssignmentFilter_AccountUuid)
if !ok {
return errors.New("expected UserRoleAssignmentFilter_AccountUuid")
}
return validation.Validate(&accountTerm.AccountUuid, requireAccountID...)
}
func validateAssignRoleToUser(req *settingssvc.AssignRoleToUserRequest) error {
return validation.ValidateStruct(
req,