Skip to content

Commit

Permalink
feat: schema generator (#31)
Browse files Browse the repository at this point in the history
* feat: add build_schema function

* feat: add function to build policy definiton

* chore: add namespace to roles

* chore: add subtype to roles

* chore: add actions table

* chore: add namespaces table

* chore: add support for multiple role types

* feat: add build_schema function

* feat: add function to build policy definiton

* chore: add namespace to roles

* chore: add subtype to roles

* chore: add actions table

* chore: add namespaces table

* chore: add support for multiple role types

* fix: googleapis/go-genproto#700

* refactor: move schema models to model package

* chore: add policies table

* refactor: change schema generator Policy to model.Policy

* refactor: change struct keys to lowercase

* chore: add namespace_id to roles and actions
remove slug from actions and namepaces

* refactor: remove slug from namespace and actions
add namespaceId to roles and actions

* chore: add check for action namespace in schema generation

* chore: namespace_id to actions and roles struct

* fix: create roles api db error

* chore: add list policies api

* chore: register policy service

* chore: add create policy api

* chore: add get policy api

* chore: add update policy api

* chore: add schema generator to create and update policy

* chore: add spicedb config and setup in docker-compose

* chore: add authz package to connect to spicedb

* chore: add authz AddPolicy Method

* refactor: fix lint issues

* chore: add hook to push schema to spicedb on Create and Update Policy

* chore: add namespace field to roles response

* feat: add namespace apis

* feat: add actions crud apis

* feat: add polices crud apis

* fix: go import lint issues

* chore: update proto path for shield apis

* chore: add shield apis proto generated files

* fix: lint issues

* fix: policy tests issue

* fix: schema generator tests issue

* fix: sort definitions to fix Schema generator tests issue

* fix: sort roles and actions to fix tests

* chore: update shield proto path

* chore: cleanup unused code and logs

* chore: pass context to AddPolicy

* chore: fix typo and update polices query to take argument

* chore(policy): Add Update method for action and namespace
  • Loading branch information
rsbh committed Dec 10, 2021
1 parent 9537cec commit 0f57be2
Show file tree
Hide file tree
Showing 37 changed files with 7,390 additions and 294 deletions.
1 change: 0 additions & 1 deletion api/handler/handler.go
Expand Up @@ -20,6 +20,5 @@ func Register(ctx context.Context, s *server.MuxServer, gw *server.GRPCGateway,

// grpc gateway api will have version endpoints
s.SetGateway("/", gw)

v1.RegisterV1(ctx, s, gw, deps.V1)
}
132 changes: 132 additions & 0 deletions api/handler/v1/action.go
@@ -0,0 +1,132 @@
package v1

import (
"context"
"errors"

grpczap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/odpf/shield/internal/schema"
"github.com/odpf/shield/model"
shieldv1 "github.com/odpf/shield/proto/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)

type ActionService interface {
GetAction(ctx context.Context, id string) (model.Action, error)
ListActions(ctx context.Context) ([]model.Action, error)
CreateAction(ctx context.Context, action model.Action) (model.Action, error)
UpdateAction(ctx context.Context, id string, action model.Action) (model.Action, error)
}

var grpcActionNotFoundErr = status.Errorf(codes.NotFound, "action doesn't exist")

func (v Dep) ListActions(ctx context.Context, request *shieldv1.ListActionsRequest) (*shieldv1.ListActionsResponse, error) {
logger := grpczap.Extract(ctx)
var actions []*shieldv1.Action

actionsList, err := v.ActionService.ListActions(ctx)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

for _, act := range actionsList {
actPB, err := transformActionToPB(act)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

actions = append(actions, &actPB)
}

return &shieldv1.ListActionsResponse{Actions: actions}, nil
}

func (v Dep) CreateAction(ctx context.Context, request *shieldv1.CreateActionRequest) (*shieldv1.CreateActionResponse, error) {
logger := grpczap.Extract(ctx)

newAction, err := v.ActionService.CreateAction(ctx, model.Action{
Id: request.GetBody().Id,
Name: request.GetBody().Name,
NamespaceId: request.GetBody().NamespaceId,
})

if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

actionPB, err := transformActionToPB(newAction)

if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

return &shieldv1.CreateActionResponse{Action: &actionPB}, nil
}

func (v Dep) GetAction(ctx context.Context, request *shieldv1.GetActionRequest) (*shieldv1.GetActionResponse, error) {
logger := grpczap.Extract(ctx)

fetchedAction, err := v.ActionService.GetAction(ctx, request.GetId())
if err != nil {
logger.Error(err.Error())
switch {
case errors.Is(err, schema.ActionDoesntExist):
return nil, grpcActionNotFoundErr
case errors.Is(err, schema.InvalidUUID):
return nil, grpcBadBodyError
default:
return nil, grpcInternalServerError
}
}

actionPB, err := transformActionToPB(fetchedAction)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

return &shieldv1.GetActionResponse{Action: &actionPB}, nil
}

func (v Dep) UpdateAction(ctx context.Context, request *shieldv1.UpdateActionRequest) (*shieldv1.UpdateActionResponse, error) {
logger := grpczap.Extract(ctx)

updatedAction, err := v.ActionService.UpdateAction(ctx, request.GetId(), model.Action{
Id: request.GetId(),
Name: request.GetBody().Name,
NamespaceId: request.GetBody().NamespaceId,
})

if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

actionPB, err := transformActionToPB(updatedAction)
if err != nil {
logger.Error(err.Error())
return nil, grpcInternalServerError
}

return &shieldv1.UpdateActionResponse{Action: &actionPB}, nil
}

func transformActionToPB(act model.Action) (shieldv1.Action, error) {
namespace, err := transformNamespaceToPB(act.Namespace)
if err != nil {
return shieldv1.Action{}, err
}
return shieldv1.Action{
Id: act.Id,
Name: act.Name,
Namespace: &namespace,
CreatedAt: timestamppb.New(act.CreatedAt),
UpdatedAt: timestamppb.New(act.UpdatedAt),
}, nil
}
233 changes: 233 additions & 0 deletions api/handler/v1/action_test.go
@@ -0,0 +1,233 @@
package v1

import (
"context"
"errors"
"sort"
"strings"
"testing"
"time"

"github.com/odpf/shield/model"
shieldv1 "github.com/odpf/shield/proto/v1"

"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)

var testActionMap = map[string]model.Action{
"read": {
Id: "read",
Name: "Read",
Namespace: model.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
"write": {
Id: "write",
Name: "Write",
Namespace: model.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
"manage": {
Id: "manage",
Name: "Manage",
Namespace: model.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
},
}

func TestListActions(t *testing.T) {
t.Parallel()
table := []struct {
title string
mockActionSrc mockActionSrv
req *shieldv1.ListActionsRequest
want *shieldv1.ListActionsResponse
err error
}{
{
title: "error in Action Service",
mockActionSrc: mockActionSrv{ListActionsFunc: func(ctx context.Context) (actions []model.Action, err error) {
return []model.Action{}, errors.New("some error")
}},
want: nil,
err: status.Errorf(codes.Internal, internalServerError.Error()),
},
{
title: "success",
mockActionSrc: mockActionSrv{ListActionsFunc: func(ctx context.Context) (actions []model.Action, err error) {
var testActionList []model.Action
for _, act := range testActionMap {
testActionList = append(testActionList, act)
}

sort.Slice(testActionList[:], func(i, j int) bool {
return strings.Compare(testActionList[i].Id, testActionList[j].Id) < 1
})
return testActionList, nil
}},
want: &shieldv1.ListActionsResponse{Actions: []*shieldv1.Action{
{
Id: "manage",
Name: "Manage",
Namespace: &shieldv1.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
{
Id: "read",
Name: "Read",
Namespace: &shieldv1.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
{
Id: "write",
Name: "Write",
Namespace: &shieldv1.Namespace{
Id: "resource-1",
Name: "Resource 1",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
}},
err: nil,
},
}

for _, tt := range table {
t.Run(tt.title, func(t *testing.T) {
t.Parallel()

mockDep := Dep{ActionService: tt.mockActionSrc}
resp, err := mockDep.ListActions(context.Background(), tt.req)

assert.EqualValues(t, tt.want, resp)
assert.EqualValues(t, tt.err, err)
})
}
}

func TestCreateAction(t *testing.T) {
t.Parallel()

table := []struct {
title string
mockActionSrv mockActionSrv
req *shieldv1.CreateActionRequest
want *shieldv1.CreateActionResponse
err error
}{
{
title: "error in creating action",
mockActionSrv: mockActionSrv{CreateActionFunc: func(ctx context.Context, act model.Action) (model.Action, error) {
return model.Action{}, errors.New("some error")
}},
req: &shieldv1.CreateActionRequest{Body: &shieldv1.ActionRequestBody{
Id: "read",
Name: "Read",
NamespaceId: "team",
}},
want: nil,
err: grpcInternalServerError,
},
{
title: "success",
mockActionSrv: mockActionSrv{CreateActionFunc: func(ctx context.Context, act model.Action) (model.Action, error) {
return model.Action{
Id: "read",
Name: "Read",
Namespace: model.Namespace{
Id: "team",
Name: "Team",
},
}, nil
}},
req: &shieldv1.CreateActionRequest{Body: &shieldv1.ActionRequestBody{
Id: "read",
Name: "Read",
NamespaceId: "team",
}},
want: &shieldv1.CreateActionResponse{Action: &shieldv1.Action{
Id: "read",
Name: "Read",
Namespace: &shieldv1.Namespace{
Id: "team",
Name: "Team",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
},
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
}},
err: nil,
},
}

for _, tt := range table {
t.Run(tt.title, func(t *testing.T) {
t.Parallel()

mockDep := Dep{ActionService: tt.mockActionSrv}
resp, err := mockDep.CreateAction(context.Background(), tt.req)
assert.EqualValues(t, tt.want, resp)
assert.EqualValues(t, tt.err, err)
})
}
}

type mockActionSrv struct {
GetActionFunc func(ctx context.Context, id string) (model.Action, error)
CreateActionFunc func(ctx context.Context, act model.Action) (model.Action, error)
ListActionsFunc func(ctx context.Context) ([]model.Action, error)
UpdateActionFunc func(ctx context.Context, id string, act model.Action) (model.Action, error)
}

func (m mockActionSrv) GetAction(ctx context.Context, id string) (model.Action, error) {
return m.GetActionFunc(ctx, id)
}

func (m mockActionSrv) ListActions(ctx context.Context) ([]model.Action, error) {
return m.ListActionsFunc(ctx)
}

func (m mockActionSrv) CreateAction(ctx context.Context, act model.Action) (model.Action, error) {
return m.CreateActionFunc(ctx, act)
}

func (m mockActionSrv) UpdateAction(ctx context.Context, id string, act model.Action) (model.Action, error) {
return m.UpdateActionFunc(ctx, id, act)
}

0 comments on commit 0f57be2

Please sign in to comment.