go.temporal.io/server@v1.23.0/common/authorization/interceptor_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package authorization 26 27 import ( 28 "context" 29 "testing" 30 31 "github.com/golang/mock/gomock" 32 "github.com/stretchr/testify/require" 33 "github.com/stretchr/testify/suite" 34 "go.temporal.io/api/workflowservice/v1" 35 "go.temporal.io/api/workflowservicemock/v1" 36 "google.golang.org/grpc" 37 "google.golang.org/grpc/metadata" 38 39 "go.temporal.io/server/common/log" 40 "go.temporal.io/server/common/metrics" 41 ) 42 43 const ( 44 testNamespace string = "test-namespace" 45 ) 46 47 var ( 48 ctx = context.Background() 49 describeNamespaceRequest = &workflowservice.DescribeNamespaceRequest{Namespace: testNamespace} 50 describeNamespaceTarget = &CallTarget{Namespace: testNamespace, Request: describeNamespaceRequest, APIName: "/temporal.api.workflowservice.v1.WorkflowService/DescribeNamespace"} 51 describeNamespaceInfo = &grpc.UnaryServerInfo{FullMethod: "/temporal.api.workflowservice.v1.WorkflowService/DescribeNamespace"} 52 startWorkflowExecutionRequest = &workflowservice.StartWorkflowExecutionRequest{Namespace: testNamespace} 53 startWorkflowExecutionTarget = &CallTarget{Namespace: testNamespace, Request: startWorkflowExecutionRequest, APIName: "/temporal.api.workflowservice.v1.WorkflowService/StartWorkflowExecution"} 54 startWorkflowExecutionInfo = &grpc.UnaryServerInfo{FullMethod: "/temporal.api.workflowservice.v1.WorkflowService/StartWorkflowExecution"} 55 ) 56 57 type ( 58 authorizerInterceptorSuite struct { 59 suite.Suite 60 *require.Assertions 61 62 controller *gomock.Controller 63 mockFrontendHandler *workflowservicemock.MockWorkflowServiceServer 64 mockAuthorizer *MockAuthorizer 65 mockMetricsHandler *metrics.MockHandler 66 interceptor grpc.UnaryServerInterceptor 67 handler grpc.UnaryHandler 68 mockClaimMapper *MockClaimMapper 69 } 70 ) 71 72 func TestAuthorizerInterceptorSuite(t *testing.T) { 73 s := new(authorizerInterceptorSuite) 74 suite.Run(t, s) 75 } 76 77 func (s *authorizerInterceptorSuite) SetupTest() { 78 s.Assertions = require.New(s.T()) 79 s.controller = gomock.NewController(s.T()) 80 81 s.mockFrontendHandler = workflowservicemock.NewMockWorkflowServiceServer(s.controller) 82 s.mockAuthorizer = NewMockAuthorizer(s.controller) 83 s.mockMetricsHandler = metrics.NewMockHandler(s.controller) 84 s.mockMetricsHandler.EXPECT().WithTags(metrics.OperationTag(metrics.AuthorizationScope)).Return(s.mockMetricsHandler).AnyTimes() 85 s.mockMetricsHandler.EXPECT().Timer(metrics.ServiceAuthorizationLatency.Name()).Return(metrics.NoopTimerMetricFunc).AnyTimes() 86 s.mockClaimMapper = NewMockClaimMapper(s.controller) 87 s.interceptor = NewAuthorizationInterceptor( 88 s.mockClaimMapper, 89 s.mockAuthorizer, 90 s.mockMetricsHandler, 91 log.NewNoopLogger(), 92 nil, 93 "", 94 "", 95 ) 96 s.handler = func(ctx context.Context, req interface{}) (interface{}, error) { return true, nil } 97 } 98 99 func (s *authorizerInterceptorSuite) TearDownTest() { 100 s.controller.Finish() 101 } 102 103 func (s *authorizerInterceptorSuite) TestIsAuthorized() { 104 s.mockAuthorizer.EXPECT().Authorize(ctx, nil, describeNamespaceTarget). 105 Return(Result{Decision: DecisionAllow}, nil) 106 107 res, err := s.interceptor(ctx, describeNamespaceRequest, describeNamespaceInfo, s.handler) 108 s.True(res.(bool)) 109 s.NoError(err) 110 } 111 112 func (s *authorizerInterceptorSuite) TestIsAuthorizedWithNamespace() { 113 s.mockAuthorizer.EXPECT().Authorize(ctx, nil, startWorkflowExecutionTarget). 114 Return(Result{Decision: DecisionAllow}, nil) 115 116 res, err := s.interceptor(ctx, startWorkflowExecutionRequest, startWorkflowExecutionInfo, s.handler) 117 s.True(res.(bool)) 118 s.NoError(err) 119 } 120 121 func (s *authorizerInterceptorSuite) TestIsUnauthorized() { 122 s.mockAuthorizer.EXPECT().Authorize(ctx, nil, describeNamespaceTarget). 123 Return(Result{Decision: DecisionDeny}, nil) 124 s.mockMetricsHandler.EXPECT().Counter(metrics.ServiceErrUnauthorizedCounter.Name()).Return(metrics.NoopCounterMetricFunc) 125 126 res, err := s.interceptor(ctx, describeNamespaceRequest, describeNamespaceInfo, s.handler) 127 s.Nil(res) 128 s.Error(err) 129 } 130 131 func (s *authorizerInterceptorSuite) TestAuthorizationFailed() { 132 s.mockAuthorizer.EXPECT().Authorize(ctx, nil, describeNamespaceTarget). 133 Return(Result{Decision: DecisionDeny}, errUnauthorized) 134 s.mockMetricsHandler.EXPECT().Counter(metrics.ServiceErrAuthorizeFailedCounter.Name()).Return(metrics.NoopCounterMetricFunc) 135 136 res, err := s.interceptor(ctx, describeNamespaceRequest, describeNamespaceInfo, s.handler) 137 s.Nil(res) 138 s.Error(err) 139 } 140 141 func (s *authorizerInterceptorSuite) TestNoopClaimMapperWithoutTLS() { 142 admin := &Claims{System: RoleAdmin} 143 s.mockAuthorizer.EXPECT().Authorize(gomock.Any(), admin, describeNamespaceTarget). 144 DoAndReturn(func(ctx context.Context, caller *Claims, target *CallTarget) (Result, error) { 145 // check that claims are present in ctx also 146 s.Equal(admin, ctx.Value(MappedClaims)) 147 148 return Result{Decision: DecisionAllow}, nil 149 }) 150 151 interceptor := NewAuthorizationInterceptor( 152 NewNoopClaimMapper(), 153 s.mockAuthorizer, 154 s.mockMetricsHandler, 155 log.NewNoopLogger(), 156 nil, 157 "", 158 "", 159 ) 160 _, err := interceptor(ctx, describeNamespaceRequest, describeNamespaceInfo, s.handler) 161 s.NoError(err) 162 } 163 164 func (s *authorizerInterceptorSuite) TestAlternateHeaders() { 165 interceptor := NewAuthorizationInterceptor( 166 s.mockClaimMapper, 167 NewNoopAuthorizer(), 168 s.mockMetricsHandler, 169 log.NewNoopLogger(), 170 nil, 171 "custom-header", 172 "custom-extra-header", 173 ) 174 175 cases := []struct { 176 md metadata.MD 177 authInfo *AuthInfo 178 }{ 179 { 180 metadata.Pairs( 181 "custom-header", "the-token", 182 "custom-extra-header", "more stuff", 183 ), 184 &AuthInfo{ 185 AuthToken: "the-token", 186 ExtraData: "more stuff", 187 }, 188 }, 189 { 190 metadata.Pairs( 191 "custom-header", "the-token", 192 "authorization-extras", "this gets ignored", 193 ), 194 &AuthInfo{ 195 AuthToken: "the-token", 196 }, 197 }, 198 { 199 metadata.Pairs( 200 "custom-extra-header", "missing main header, this gets ignored", 201 ), 202 nil, 203 }, 204 } 205 for _, testCase := range cases { 206 if testCase.authInfo != nil { 207 s.mockClaimMapper.EXPECT().GetClaims(testCase.authInfo).Return(&Claims{System: RoleAdmin}, nil) 208 } 209 inCtx := metadata.NewIncomingContext(ctx, testCase.md) 210 _, err := interceptor(inCtx, describeNamespaceRequest, describeNamespaceInfo, s.handler) 211 s.NoError(err) 212 } 213 }