blob: 2eaf47961625041757da3bb4ee2567c54f44bde6 [file] [log] [blame]
package grpcsp
import (
"context"
"testing"
"go.skia.org/infra/go/roles"
"go.skia.org/infra/kube/go/authproxy"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
MethodOneName = "Foo"
MethodTwoName = "Bar"
ServiceName = "foo.service.Example"
)
type mockServer struct {
handleCalled bool
}
func (ts *mockServer) handle(ctx context.Context, req interface{}) (interface{}, error) {
ts.handleCalled = true
return nil, nil
}
func testSetupEmpty(t *testing.T) *ServicePolicy {
desc := grpc.ServiceDesc{}
serverPolicy := Server()
servicePolicy, err := serverPolicy.Service(desc)
require.NoError(t, err)
require.NotNil(t, servicePolicy)
return servicePolicy
}
func testSetupOneMethod(t *testing.T) (grpc.ServiceDesc, *ServerPolicy, *ServicePolicy) {
desc := grpc.ServiceDesc{
ServiceName: ServiceName,
Methods: []grpc.MethodDesc{
{
MethodName: MethodOneName,
},
},
}
serverPolicy := Server()
servicePolicy, err := serverPolicy.Service(desc)
require.NoError(t, err)
require.NotNil(t, servicePolicy)
return desc, serverPolicy, servicePolicy
}
func testSetupTwoMethods(t *testing.T) (grpc.ServiceDesc, *ServerPolicy, *ServicePolicy) {
desc := grpc.ServiceDesc{
ServiceName: ServiceName,
Methods: []grpc.MethodDesc{
{
MethodName: MethodOneName,
},
{
MethodName: MethodTwoName,
},
},
}
serverPolicy := Server()
servicePolicy, err := serverPolicy.Service(desc)
require.NoError(t, err)
require.NotNil(t, servicePolicy)
return desc, serverPolicy, servicePolicy
}
func testSetupUnaryInterceptorWithCallerRoles(t *testing.T, desc grpc.ServiceDesc, policy *ServerPolicy, callerRoles roles.Roles) (context.Context, grpc.UnaryServerInterceptor) {
ctx := context.Background()
md := metadata.New(map[string]string{
authproxy.WebAuthHeaderName: "user@domain.com",
authproxy.WebAuthRoleHeaderName: callerRoles.ToHeader(),
})
ctx = metadata.NewIncomingContext(ctx, md)
interceptor := policy.UnaryInterceptor()
require.NotNil(t, interceptor)
return ctx, interceptor
}
func testMockServerCall(t *testing.T, ctx context.Context, serviceName, methodName string, interceptor grpc.UnaryServerInterceptor) (bool, error) {
info := &grpc.UnaryServerInfo{FullMethod: "/" + serviceName + "/" + methodName}
srv := &mockServer{}
_, err := interceptor(ctx, nil, info, srv.handle)
return srv.handleCalled, err
}
func TestAuthorizeRoles(t *testing.T) {
policy := testSetupEmpty(t)
assert.NoError(t, policy.AuthorizeRoles(roles.Roles{roles.Viewer}))
assert.Error(t, policy.AuthorizeRoles(roles.Roles{roles.Viewer}), "calling AuthorizeRoles more than once returns an error")
}
func TestAuthorizeMethodForRoles_UnknownMethod_ReturnsError(t *testing.T) {
policy := testSetupEmpty(t)
assert.Error(t, policy.AuthorizeMethodForRoles(MethodOneName, roles.Roles{roles.Viewer}), "cannot authorize an unrecognized method")
}
func TestAuthorizeMethodForRoles_DuplicateMethod_ReturnsError(t *testing.T) {
_, _, servicePolicy := testSetupOneMethod(t)
assert.NoError(t, servicePolicy.AuthorizeMethodForRoles(MethodOneName, roles.Roles{roles.Viewer}))
assert.Error(t, servicePolicy.AuthorizeMethodForRoles(MethodOneName, roles.Roles{roles.Editor}),
"calling AuthorizeMethodForRoles more than once for the same method returns an error")
}
func TestUnaryInterceptor_UserHasSufficientServiceWideRoles_Succeed(t *testing.T) {
desc, serverPolicy, servicePolicy := testSetupTwoMethods(t)
require.NoError(t, servicePolicy.AuthorizeRoles(roles.Roles{roles.Admin}))
ctx, interceptor := testSetupUnaryInterceptorWithCallerRoles(t, desc, serverPolicy, roles.Roles{roles.Admin})
handleCalled, err := testMockServerCall(t, ctx, desc.ServiceName, MethodOneName, interceptor)
assert.NoError(t, err, "users with service-wide authorized roles may call any method")
assert.True(t, handleCalled, "control passed on to server handler")
handleCalled, err = testMockServerCall(t, ctx, desc.ServiceName, MethodTwoName, interceptor)
require.NoError(t, err, "users with service-wide authorized roles may call any method")
assert.True(t, handleCalled, "control passed on to server handler")
}
func TestUnaryInterceptor_ServiceHasServiceWideRolesUserHasInsufficientRoles_Fail(t *testing.T) {
desc, serverPolicy, servicePolicy := testSetupTwoMethods(t)
require.NoError(t, servicePolicy.AuthorizeRoles(roles.Roles{roles.Admin}))
ctx, interceptor := testSetupUnaryInterceptorWithCallerRoles(t, desc, serverPolicy, roles.Roles{roles.Viewer})
handleCalled, err := testMockServerCall(t, ctx, desc.ServiceName, MethodOneName, interceptor)
require.Error(t, err, "users with insufficient service-wide roles may not call methods when the policy has no method-specific roles")
assert.False(t, handleCalled, "control is not passed on to server handler")
handleCalled, err = testMockServerCall(t, ctx, desc.ServiceName, MethodTwoName, interceptor)
require.Error(t, err, "users with insufficient service-wide roles may not call methods when the policy has no method-specific roles")
assert.False(t, handleCalled, "control is not passed on to server handler")
}
func TestUnaryInterceptor_UserHasSufficientMethodRoles_Succeed(t *testing.T) {
desc, serverPolicy, servicePolicy := testSetupTwoMethods(t)
require.NoError(t, servicePolicy.AuthorizeMethodForRoles(MethodOneName, roles.Roles{roles.Viewer}))
require.NoError(t, servicePolicy.AuthorizeMethodForRoles(MethodTwoName, roles.Roles{roles.Editor}))
ctx, interceptor := testSetupUnaryInterceptorWithCallerRoles(t, desc, serverPolicy, roles.Roles{roles.Viewer})
handleCalled, err := testMockServerCall(t, ctx, desc.ServiceName, MethodOneName, interceptor)
assert.NoError(t, err, "viewer is authorized to call /"+MethodOneName)
assert.True(t, handleCalled, "control passed on to server handler")
}
func TestUnaryInterceptor_UserHasInsufficientMethodRoles_Fail(t *testing.T) {
desc, serverPolicy, servicePolicy := testSetupTwoMethods(t)
require.NoError(t, servicePolicy.AuthorizeMethodForRoles(MethodOneName, roles.Roles{roles.Viewer}))
require.NoError(t, servicePolicy.AuthorizeMethodForRoles(MethodTwoName, roles.Roles{roles.Editor}))
ctx, interceptor := testSetupUnaryInterceptorWithCallerRoles(t, desc, serverPolicy, roles.Roles{roles.Viewer})
handleCalled, err := testMockServerCall(t, ctx, desc.ServiceName, MethodTwoName, interceptor)
assert.Error(t, err, "viewer isn't authorized to /"+MethodTwoName)
assert.False(t, handleCalled, "control is not passed on to server handler")
}