Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/grpc/index.html

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

18 changes: 8 additions & 10 deletions docs/openapi/policy/actions/actions.openapi.yaml

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

332 changes: 162 additions & 170 deletions protocol/go/policy/actions/actions.pb.go

Large diffs are not rendered by default.

166 changes: 131 additions & 35 deletions service/integration/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,20 @@ func (s *ActionsSuite) TearDownSuite() {
}

func (s *ActionsSuite) Test_ListActions_NoPagination_Succeeds() {
fixtureCustomAction1 := s.f.GetCustomActionKey("custom_action_1")
fixtureCustomAction2 := s.f.GetCustomActionKey("other_special_action")
name1 := fmt.Sprintf("scoped-list-nopage-1-%d", time.Now().UnixNano())
name2 := fmt.Sprintf("scoped-list-nopage-2-%d", time.Now().UnixNano())

created1, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: name1,
NamespaceId: s.defaultNamespaceID(),
})
s.Require().NoError(err)

created2, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: name2,
NamespaceId: s.defaultNamespaceID(),
})
s.Require().NoError(err)

list, err := s.db.PolicyClient.ListActions(s.ctx, &actions.ListActionsRequest{NamespaceId: s.defaultNamespaceID()})
s.NotNil(list)
Expand All @@ -56,10 +68,10 @@ func (s *ActionsSuite) Test_ListActions_NoPagination_Succeeds() {
foundDelete := false

for _, action := range list.GetActionsCustom() {
switch action.GetName() {
case fixtureCustomAction1.Name:
switch action.GetId() {
case created1.GetId():
foundCustomAction1 = true
case fixtureCustomAction2.Name:
case created2.GetId():
foundCustomAction2 = true
}
}
Expand Down Expand Up @@ -187,28 +199,88 @@ func (s *ActionsSuite) Test_ListActions_FiltersCustomActionsByNamespace_Succeeds
s.False(foundSecond)
}

func (s *ActionsSuite) Test_ListActions_LegacyCustomAction_NamespaceProjection_Succeeds() {
func (s *ActionsSuite) Test_ListActions_WithoutNamespace_ReturnsAcrossNamespaces_Succeeds() {
name := fmt.Sprintf("global-list-action-%d", time.Now().UnixNano())

inDefault, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: name,
NamespaceId: s.defaultNamespaceID(),
})
s.Require().NoError(err)

inOther, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: name,
NamespaceId: s.otherNamespaceID(),
})
s.Require().NoError(err)

list, err := s.db.PolicyClient.ListActions(s.ctx, &actions.ListActionsRequest{})
s.Require().NoError(err)

foundDefault := false
foundOther := false
for _, action := range list.GetActionsCustom() {
if action.GetId() == inDefault.GetId() {
foundDefault = true
s.Equal(s.defaultNamespaceID(), action.GetNamespace().GetId())
}
if action.GetId() == inOther.GetId() {
foundOther = true
s.Equal(s.otherNamespaceID(), action.GetNamespace().GetId())
}
}

s.True(foundDefault)
s.True(foundOther)
}
Comment thread
elizabethhealy marked this conversation as resolved.

func (s *ActionsSuite) Test_ListActions_LegacyCustomAction_ScopedExcluded_UnscopedIncluded_Succeeds() {
legacy := s.f.GetCustomActionKey("custom_action_1")
assertLegacyProjected := func(list *actions.ListActionsResponse) {
scopedName := fmt.Sprintf("scoped-list-action-%d", time.Now().UnixNano())
scoped, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: scopedName,
NamespaceId: s.defaultNamespaceID(),
})
s.Require().NoError(err)
s.NotNil(scoped)

assertLegacyAbsent := func(list *actions.ListActionsResponse) {
s.T().Helper()
found := false
for _, action := range list.GetActionsCustom() {
if action.GetId() == legacy.ID {
found = true
s.Equal(s.defaultNamespaceID(), action.GetNamespace().GetId())
s.Equal(s.defaultNamespaceFQN(), action.GetNamespace().GetFqn())
}
}
s.True(found)
s.False(found)
}

listByID, err := s.db.PolicyClient.ListActions(s.ctx, &actions.ListActionsRequest{NamespaceId: s.defaultNamespaceID()})
Comment thread
elizabethhealy marked this conversation as resolved.
s.Require().NoError(err)
assertLegacyProjected(listByID)
assertLegacyAbsent(listByID)

listByFQN, err := s.db.PolicyClient.ListActions(s.ctx, &actions.ListActionsRequest{NamespaceFqn: s.defaultNamespaceFQN()})
s.Require().NoError(err)
assertLegacyProjected(listByFQN)
assertLegacyAbsent(listByFQN)

listUnscoped, err := s.db.PolicyClient.ListActions(s.ctx, &actions.ListActionsRequest{})
s.Require().NoError(err)

foundUnscoped := false
foundScoped := false
for _, action := range listUnscoped.GetActionsCustom() {
if action.GetId() == legacy.ID {
foundUnscoped = true
s.Nil(action.GetNamespace())
}
if action.GetId() == scoped.GetId() {
foundScoped = true
s.Require().NotNil(action.GetNamespace())
s.Equal(s.defaultNamespaceID(), action.GetNamespace().GetId())
}
}
s.True(foundUnscoped)
Comment thread
elizabethhealy marked this conversation as resolved.
s.True(foundScoped)
}

func (s *ActionsSuite) Test_GetAction_Id_Succeeds() {
Expand Down Expand Up @@ -240,30 +312,26 @@ func (s *ActionsSuite) Test_GetAction_Id_Succeeds() {
}

func (s *ActionsSuite) Test_GetAction_Name_Succeeds() {
customAction := s.f.GetCustomActionKey("other_special_action")
actionCreate := s.f.GetStandardAction(policydb.ActionCreate.String())
action, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{
Name: customAction.Name,
},
name := fmt.Sprintf("get-by-name-action-%d", time.Now().UnixNano())
customAction, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: name,
NamespaceId: s.defaultNamespaceID(),
})
s.NotNil(action)
s.Require().NoError(err)
s.Equal(customAction.ID, action.GetId())
s.Equal(customAction.Name, action.GetName())
s.NotNil(action.GetMetadata())
s.NotNil(customAction)

action, err = s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
action, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{
Name: actionCreate.GetName(),
Name: customAction.GetName(),
},
NamespaceId: s.defaultNamespaceID(),
})
s.NotNil(action)
s.Require().NoError(err)
s.Equal(actionCreate.GetName(), action.GetName())
s.Equal(actionCreate.GetId(), action.GetId())
s.Equal(customAction.GetId(), action.GetId())
s.Equal(customAction.GetName(), action.GetName())
s.Require().NotNil(action.GetNamespace())
s.Equal(s.defaultNamespaceID(), action.GetNamespace().GetId())
s.NotNil(action.GetMetadata())
}

Expand Down Expand Up @@ -299,28 +367,47 @@ func (s *ActionsSuite) Test_GetAction_Name_ResolvesByNamespace_Succeeds() {
s.Equal(s.otherNamespaceID(), gotOther.GetNamespace().GetId())
}

func (s *ActionsSuite) Test_GetAction_Name_LegacyCustomAction_Succeeds() {
func (s *ActionsSuite) Test_GetAction_Name_LegacyCustomAction_UnscopedSucceeds_ScopedFails() {
legacy := s.f.GetCustomActionKey("other_special_action")
assertLegacyGet := func(action *policy.Action) {

byUnscoped, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{Name: legacy.Name},
})
s.Require().NoError(err)
s.NotNil(byUnscoped)
s.Equal(legacy.ID, byUnscoped.GetId())
s.Nil(byUnscoped.GetNamespace())

assertLegacyGet := func(action *policy.Action, err error) {
s.T().Helper()
s.Equal(legacy.ID, action.GetId())
s.Equal(s.defaultNamespaceID(), action.GetNamespace().GetId())
s.Equal(s.defaultNamespaceFQN(), action.GetNamespace().GetFqn())
s.Nil(action)
s.Require().Error(err)
s.Require().ErrorIs(err, db.ErrNotFound)
}

byID, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{Name: legacy.Name},
NamespaceId: s.defaultNamespaceID(),
})
s.Require().NoError(err)
assertLegacyGet(byID)
assertLegacyGet(byID, err)

byFQN, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{Name: legacy.Name},
NamespaceFqn: s.defaultNamespaceFQN(),
})
s.Require().NoError(err)
assertLegacyGet(byFQN)
assertLegacyGet(byFQN, err)

byOtherID, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{Name: legacy.Name},
NamespaceId: s.otherNamespaceID(),
})
assertLegacyGet(byOtherID, err)

byOtherFQN, err := s.db.PolicyClient.GetAction(s.ctx, &actions.GetActionRequest{
Identifier: &actions.GetActionRequest_Name{Name: legacy.Name},
NamespaceFqn: s.otherNamespaceFQN(),
})
assertLegacyGet(byOtherFQN, err)
}

func (s *ActionsSuite) Test_CreateListGetAction_WithNamespaceFQN_Succeeds() {
Expand Down Expand Up @@ -417,6 +504,15 @@ func (s *ActionsSuite) Test_CreateAction_Conflict_Fails() {
s.Require().ErrorIs(err, db.ErrUniqueConstraintViolation)
}

func (s *ActionsSuite) Test_CreateAction_MissingNamespace_SucceedsInLegacyMode() {
action, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Name: fmt.Sprintf("missing-namespace-%d", time.Now().UnixNano()),
})
s.Require().NoError(err)
s.NotNil(action)
s.Nil(action.GetNamespace())
}

func (s *ActionsSuite) Test_CreateAction_NormalizesToLowerCase() {
newName := "New_Custom_Action_CreateAction_UPPER"
action, err := s.db.PolicyClient.CreateAction(s.ctx, &actions.CreateActionRequest{
Expand Down
39 changes: 0 additions & 39 deletions service/integration/registered_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1939,45 +1939,6 @@ func (s *RegisteredResourcesSuite) Test_SameNamespaceEnforcement_SameNamespace_S
s.Require().Len(resVal.GetActionAttributeValues(), 1)
}

func (s *RegisteredResourcesSuite) Test_CreateRegisteredResourceValue_WithNamespacedCustomActionName_Succeeds() {
nsID := s.getNamespaceID("example.com")

res, err := s.db.PolicyClient.CreateRegisteredResource(s.ctx, &registeredresources.CreateRegisteredResourceRequest{
NamespaceId: nsID,
Name: fmt.Sprintf("test_rr_custom_action_name_%d", time.Now().UnixNano()),
})
s.Require().NoError(err)
s.NotNil(res)

customActionName := fmt.Sprintf("rr_custom_action_%d", time.Now().UnixNano())
customAction, err := s.db.PolicyClient.CreateAction(s.ctx, &pbActions.CreateActionRequest{
Name: customActionName,
NamespaceId: nsID,
})
s.Require().NoError(err)
s.NotNil(customAction)

resVal, err := s.db.PolicyClient.CreateRegisteredResourceValue(s.ctx, &registeredresources.CreateRegisteredResourceValueRequest{
ResourceId: res.GetId(),
Value: fmt.Sprintf("test_rr_custom_action_name_value_%d", time.Now().UnixNano()),
ActionAttributeValues: []*registeredresources.ActionAttributeValue{
{
ActionIdentifier: &registeredresources.ActionAttributeValue_ActionName{
ActionName: customActionName,
},
AttributeValueIdentifier: &registeredresources.ActionAttributeValue_AttributeValueFqn{
AttributeValueFqn: "https://example.com/attr/attr1/value/value1",
},
},
},
})
s.Require().NoError(err)
s.NotNil(resVal)
s.Require().Len(resVal.GetActionAttributeValues(), 1)
s.Equal(customAction.GetId(), resVal.GetActionAttributeValues()[0].GetAction().GetId())
s.Equal(customActionName, resVal.GetActionAttributeValues()[0].GetAction().GetName())
}

func (s *RegisteredResourcesSuite) getNamespaceID(key string) string {
ns := s.f.GetNamespaceKey(key)
return ns.ID
Expand Down
5 changes: 5 additions & 0 deletions service/policy/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package actions

import (
"context"
"errors"
"fmt"
"log/slog"

Expand Down Expand Up @@ -112,6 +113,10 @@ func (a *ActionService) ListActions(ctx context.Context, req *connect.Request[ac

func (a *ActionService) CreateAction(ctx context.Context, req *connect.Request[actions.CreateActionRequest]) (*connect.Response[actions.CreateActionResponse], error) {
a.logger.DebugContext(ctx, "creating action", slog.String("name", req.Msg.GetName()))
if a.config.NamespacedPolicy && req.Msg.GetNamespaceId() == "" && req.Msg.GetNamespaceFqn() == "" {
return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("either namespace_id or namespace_fqn must be provided"))
Comment thread
elizabethhealy marked this conversation as resolved.
}

auditParams := audit.PolicyEventParams{
ActionType: audit.ActionTypeCreate,
ObjectType: audit.ObjectTypeAction,
Expand Down
Loading
Loading