// Copyright (c) 2026 Probo Inc . // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package iam_test import ( "bytes" "context" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.gearno.de/kit/log" "go.gearno.de/kit/pg" "go.probo.inc/probo/internal/test" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam/oauth2scope" "go.probo.inc/probo/pkg/iam/policy" ) func TestAuthorizer_DecisionLogging(t *testing.T) { t.Parallel() t.Run("allow writes audit and decision log", func(t *testing.T) { t.Parallel() client := test.PGClient(t) fixture := seedBatchAuthorizeFixture(t, context.Background(), client) action := newBatchTestAction() var logOutput bytes.Buffer authorizer := newTestAuthorizerWithLogger(client, action, nil, &logOutput) _, err := authorizer.AuthorizeBatch( context.Background(), iam.AuthorizeBatchParams{ Principal: fixture.identityID, Action: action, Resources: []gid.GID{fixture.frameworkID1}, }, ) require.NoError(t, err) output := logOutput.String() assert.Contains(t, output, "authz decision") assert.Contains(t, output, "allow") assert.Contains(t, output, action) assert.Contains(t, output, fixture.identityID.String()) assert.Contains(t, output, fixture.frameworkID1.String()) assert.Contains(t, output, "allow-test-action") assert.Equal(t, 1, countAuditLogsForAction(t, context.Background(), client, action)) }) t.Run("deny writes decision log without audit row", func(t *testing.T) { t.Parallel() client := test.PGClient(t) fixture := seedBatchAuthorizeFixture(t, context.Background(), client) action := newBatchTestAction() var logOutput bytes.Buffer authorizer := newTestAuthorizerWithLogger( client, action, nil, &logOutput, policy.Deny(action).WithSID("deny-test-action"), ) _, err := authorizer.AuthorizeBatch( context.Background(), iam.AuthorizeBatchParams{ Principal: fixture.identityID, Action: action, Resources: []gid.GID{fixture.frameworkID1}, }, ) require.Error(t, err) output := logOutput.String() assert.Contains(t, output, "authz decision") assert.Contains(t, output, "deny") assert.Contains(t, output, "deny-test-action") assert.Contains(t, output, "explicit deny by statement deny-test-action") assert.Equal(t, 0, countAuditLogsForAction(t, context.Background(), client, action)) }) t.Run("implicit deny logs no_match without policy id", func(t *testing.T) { t.Parallel() client := test.PGClient(t) fixture := seedBatchAuthorizeFixture(t, context.Background(), client) action := newBatchTestAction() var logOutput bytes.Buffer authorizer := newTestAuthorizerWithLogger( client, "core:other:action", nil, &logOutput, ) _, err := authorizer.AuthorizeBatch( context.Background(), iam.AuthorizeBatchParams{ Principal: fixture.identityID, Action: action, Resources: []gid.GID{fixture.frameworkID1}, }, ) require.Error(t, err) output := logOutput.String() assert.Contains(t, output, "authz decision") assert.Contains(t, output, "no_match") assert.NotContains(t, output, "policy_id") assert.True(t, strings.Contains(output, "implicit deny")) }) } func newTestAuthorizerWithLogger( client *pg.Client, action string, allowResourceID *gid.GID, logOutput *bytes.Buffer, extraStatements ...policy.Statement, ) *iam.Authorizer { statements := []policy.Statement{ policy.Allow(action).WithSID("allow-test-action"), } if allowResourceID != nil { statements[0] = statements[0].When(policy.Equals("resource.id", allowResourceID.String())) } statements = append(statements, extraStatements...) authorizer := iam.NewAuthorizer(client, log.NewLogger(log.WithOutput(logOutput)), oauth2scope.NewRegistry()) authorizer.RegisterPolicySet( iam.NewPolicySet().AddRolePolicy( string(coredata.MembershipRoleOwner), policy.NewPolicy("batch-authorize-test", "Batch Authorize Test", statements...), ), ) return authorizer }