Passed
Push — master ( cb35fc...e00b12 )
by
unknown
03:21 queued 26s
created

servers.*PermissionServer.BulkCheck   D

Complexity

Conditions 12

Size

Total Lines 128
Code Lines 77

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 77
nop 2
dl 0
loc 128
rs 4.1127
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like servers.*PermissionServer.BulkCheck often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package servers
2
3
import (
4
	"context"
5
	"errors"
6
	"log/slog"
7
	"sync"
8
9
	otelCodes "go.opentelemetry.io/otel/codes"
10
	"google.golang.org/grpc/status"
11
12
	"github.com/Permify/permify/internal"
13
	"github.com/Permify/permify/internal/invoke"
14
	v1 "github.com/Permify/permify/pkg/pb/base/v1"
15
)
16
17
// PermissionServer - Structure for Permission Server
18
type PermissionServer struct {
19
	v1.UnimplementedPermissionServer
20
21
	invoker invoke.Invoker
22
}
23
24
// NewPermissionServer - Creates new Permission Server
25
func NewPermissionServer(i invoke.Invoker) *PermissionServer {
26
	return &PermissionServer{
27
		invoker: i,
28
	}
29
}
30
31
// Check - Performs Authorization Check
32
func (r *PermissionServer) Check(ctx context.Context, request *v1.PermissionCheckRequest) (*v1.PermissionCheckResponse, error) {
33
	ctx, span := internal.Tracer.Start(ctx, "permissions.check")
34
	defer span.End()
35
36
	v := request.Validate()
37
	if v != nil {
38
		return nil, status.Error(GetStatus(v), v.Error()) // Return validation error
39
	}
40
41
	response, err := r.invoker.Check(ctx, request)
42
	if err != nil {
43
		span.RecordError(err)
44
		span.SetStatus(otelCodes.Error, err.Error())
45
		slog.ErrorContext(ctx, err.Error())
46
		return nil, status.Error(GetStatus(err), err.Error())
47
	}
48
49
	return response, nil
50
}
51
52
// BulkCheck - Performs multiple authorization checks in a single request
53
func (r *PermissionServer) BulkCheck(ctx context.Context, request *v1.PermissionBulkCheckRequest) (*v1.PermissionBulkCheckResponse, error) {
54
	// emptyResp is a default, empty response that we will return in case of an error or when the context is cancelled.
55
	emptyResp := &v1.PermissionBulkCheckResponse{
56
		Results: make([]*v1.PermissionCheckResponse, 0),
57
	}
58
59
	ctx, span := internal.Tracer.Start(ctx, "permissions.bulk-check")
60
	defer span.End()
61
62
	// Validate tenant_id
63
	if request.GetTenantId() == "" {
64
		err := status.Error(GetStatus(nil), "tenant_id is required")
65
		span.RecordError(err)
66
		span.SetStatus(otelCodes.Error, err.Error())
67
		return nil, err
68
	}
69
70
	checkItems := request.GetItems()
71
72
	// Validate number of requests
73
	if len(checkItems) == 0 {
74
		err := status.Error(GetStatus(nil), "at least one item is required")
75
		span.RecordError(err)
76
		span.SetStatus(otelCodes.Error, err.Error())
77
		return nil, err
78
	}
79
80
	if len(checkItems) > 100 {
81
		err := status.Error(GetStatus(nil), "maximum 100 items allowed")
82
		span.RecordError(err)
83
		span.SetStatus(otelCodes.Error, err.Error())
84
		return nil, err
85
	}
86
87
	// The buffer size is equal to the number of references in the entity.
88
	type resultItem struct {
89
		index    int
90
		response *v1.PermissionCheckResponse
91
	}
92
	resultChannel := make(chan resultItem, len(checkItems))
93
94
	// The WaitGroup and Mutex are used for synchronization.
95
	var wg sync.WaitGroup
96
	var mutex sync.Mutex
97
98
	// Process each check request
99
	for i, checkRequestItem := range checkItems {
100
		wg.Add(1)
101
102
		go func(index int, checkRequestItem *v1.PermissionBulkCheckRequestItem) {
103
			defer wg.Done()
104
105
			// Validate individual request
106
			v := checkRequestItem.Validate()
107
			if v != nil {
108
				resultChannel <- resultItem{
109
					index: index,
110
					response: &v1.PermissionCheckResponse{
111
						Can: v1.CheckResult_CHECK_RESULT_DENIED,
112
						Metadata: &v1.PermissionCheckResponseMetadata{
113
							CheckCount: 0,
114
						},
115
					},
116
				}
117
				return
118
			}
119
120
			// Perform the check using existing Check function
121
			checkRequest := &v1.PermissionCheckRequest{
122
				TenantId:   request.GetTenantId(),
123
				Subject:    checkRequestItem.GetSubject(),
124
				Entity:     checkRequestItem.GetEntity(),
125
				Permission: checkRequestItem.GetPermission(),
126
				Metadata:   request.GetMetadata(),
127
				Context:    request.GetContext(),
128
				Arguments:  request.GetArguments(),
129
			}
130
			response, err := r.invoker.Check(ctx, checkRequest)
131
			if err != nil {
132
				// Log error but don't fail the entire bulk operation
133
				slog.ErrorContext(ctx, "check failed in bulk operation", "error", err.Error(), "index", index)
134
				resultChannel <- resultItem{
135
					index: index,
136
					response: &v1.PermissionCheckResponse{
137
						Can: v1.CheckResult_CHECK_RESULT_DENIED,
138
						Metadata: &v1.PermissionCheckResponseMetadata{
139
							CheckCount: 1,
140
						},
141
					},
142
				}
143
				return
144
			}
145
146
			resultChannel <- resultItem{index: index, response: &v1.PermissionCheckResponse{
147
				Can:      response.GetCan(),
148
				Metadata: response.GetMetadata(),
149
			}}
150
		}(i, checkRequestItem)
151
	}
152
153
	// Once the function returns, we wait for all goroutines to finish, then close the resultChannel.
154
	defer func() {
155
		wg.Wait()
156
		close(resultChannel)
157
	}()
158
159
	// We read the responses from the resultChannel.
160
	// We expect as many responses as there are references in the entity.
161
	results := make([]*v1.PermissionCheckResponse, len(request.GetItems()))
162
	for range checkItems {
163
		select {
164
		// If we receive a response from the resultChannel, we check for errors.
165
		case response := <-resultChannel:
166
			// If there's no error, we add the result to our response's Results map.
167
			// We use a mutex to safely update the map since multiple goroutines may be writing to it concurrently.
168
			mutex.Lock()
169
			results[response.index] = response.response
170
			mutex.Unlock()
171
172
		// If the context is done (i.e., canceled or deadline exceeded), we return an empty response and an error.
173
		case <-ctx.Done():
174
			return emptyResp, errors.New(v1.ErrorCode_ERROR_CODE_CANCELLED.String())
175
		}
176
	}
177
178
	return &v1.PermissionBulkCheckResponse{
179
		Results: results,
180
	}, nil
181
}
182
183
// Expand - Get schema actions in a tree structure
184
func (r *PermissionServer) Expand(ctx context.Context, request *v1.PermissionExpandRequest) (*v1.PermissionExpandResponse, error) {
185
	ctx, span := internal.Tracer.Start(ctx, "permissions.expand")
186
	defer span.End()
187
188
	v := request.Validate()
189
	if v != nil {
190
		return nil, status.Error(GetStatus(v), v.Error()) // Return validation error
191
	}
192
193
	response, err := r.invoker.Expand(ctx, request)
194
	if err != nil {
195
		span.RecordError(err)
196
		span.SetStatus(otelCodes.Error, err.Error())
197
		slog.ErrorContext(ctx, err.Error())
198
		return nil, status.Error(GetStatus(err), err.Error())
199
	}
200
201
	return response, nil
202
}
203
204
// LookupEntity -
205
func (r *PermissionServer) LookupEntity(ctx context.Context, request *v1.PermissionLookupEntityRequest) (*v1.PermissionLookupEntityResponse, error) {
206
	ctx, span := internal.Tracer.Start(ctx, "permissions.lookup-entity")
207
	defer span.End()
208
209
	v := request.Validate()
210
	if v != nil {
211
		return nil, status.Error(GetStatus(v), v.Error()) // Return validation error
212
	}
213
214
	response, err := r.invoker.LookupEntity(ctx, request)
215
	if err != nil {
216
		span.RecordError(err)
217
		span.SetStatus(otelCodes.Error, err.Error())
218
		slog.ErrorContext(ctx, err.Error())
219
		return nil, status.Error(GetStatus(err), err.Error())
220
	}
221
222
	return response, nil
223
}
224
225
// LookupEntityStream -
226
func (r *PermissionServer) LookupEntityStream(request *v1.PermissionLookupEntityRequest, server v1.Permission_LookupEntityStreamServer) error {
227
	ctx, span := internal.Tracer.Start(server.Context(), "permissions.lookup-entity-stream")
228
	defer span.End()
229
230
	v := request.Validate()
231
	if v != nil {
232
		return v
233
	}
234
235
	err := r.invoker.LookupEntityStream(ctx, request, server)
236
	if err != nil {
237
		span.RecordError(err)
238
		span.SetStatus(otelCodes.Error, err.Error())
239
		slog.ErrorContext(ctx, err.Error())
240
		return status.Error(GetStatus(err), err.Error())
241
	}
242
243
	return nil
244
}
245
246
// LookupSubject -
247
func (r *PermissionServer) LookupSubject(ctx context.Context, request *v1.PermissionLookupSubjectRequest) (*v1.PermissionLookupSubjectResponse, error) {
248
	ctx, span := internal.Tracer.Start(ctx, "permissions.lookup-subject")
249
	defer span.End()
250
251
	v := request.Validate()
252
	if v != nil {
253
		return nil, status.Error(GetStatus(v), v.Error()) // Return validation error
254
	}
255
256
	response, err := r.invoker.LookupSubject(ctx, request)
257
	if err != nil {
258
		span.RecordError(err)
259
		span.SetStatus(otelCodes.Error, err.Error())
260
		slog.ErrorContext(ctx, err.Error())
261
		return nil, status.Error(GetStatus(err), err.Error())
262
	}
263
264
	return response, nil
265
}
266
267
// SubjectPermission -
268
func (r *PermissionServer) SubjectPermission(ctx context.Context, request *v1.PermissionSubjectPermissionRequest) (*v1.PermissionSubjectPermissionResponse, error) {
269
	ctx, span := internal.Tracer.Start(ctx, "permissions.subject-permission")
270
	defer span.End()
271
272
	v := request.Validate()
273
	if v != nil {
274
		return nil, status.Error(GetStatus(v), v.Error()) // Return validation error
275
	}
276
277
	response, err := r.invoker.SubjectPermission(ctx, request)
278
	if err != nil {
279
		span.RecordError(err)
280
		span.SetStatus(otelCodes.Error, err.Error())
281
		slog.ErrorContext(ctx, err.Error())
282
		return nil, status.Error(GetStatus(err), err.Error())
283
	}
284
285
	return response, nil
286
}
287