summaryrefslogtreecommitdiff
path: root/pkg/authz/check_service.go
blob: 3e14c00818ca934cc2a1388144a9cbc08381bd8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package authz

import (
	"context"
	"net/http"
	"path/filepath"

	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
	authzed "github.com/authzed/authzed-go/v1"
	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
	auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
	types "github.com/envoyproxy/go-control-plane/envoy/type/v3"
	"github.com/xlgmokha/x/pkg/log"
	"github.com/xlgmokha/x/pkg/mapper"
	"github.com/xlgmokha/x/pkg/x"
	"gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/pkg/pls"
	status "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc/codes"
)

var StaticAssets []string = []string{".png", ".css", ".html", ".js", ".ico"}

type CheckService struct {
	client *authzed.Client
	auth.UnimplementedAuthorizationServer
}

func NewCheckService(client *authzed.Client) auth.AuthorizationServer {
	return &CheckService{
		client: client,
	}
}

func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) {
	if svc.isAuthorized(ctx, request) {
		return svc.OK(ctx), nil
	}
	return svc.Denied(ctx), nil
}

func (svc *CheckService) isAuthorized(ctx context.Context, r *auth.CheckRequest) bool {
	if !svc.validRequest(ctx, r) {
		return false
	}
	log.WithFields(ctx, mapper.MapFrom[*auth.CheckRequest, log.Fields](r))

	if svc.isStaticAsset(ctx, r) {
		return true
	}

	if x.IsZero(svc.client) {
		return false
	}

	request := mapper.MapFrom[*auth.CheckRequest, *v1.CheckPermissionRequest](r)
	response, err := svc.client.CheckPermission(ctx, request)
	log.WithFields(ctx, log.Fields{
		"spice_request":  request,
		"spice_response": response,
	})
	if err != nil {
		pls.LogError(ctx, err)
		return false
	}
	return response.Permissionship == v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION
}

func (svc *CheckService) isStaticAsset(ctx context.Context, r *auth.CheckRequest) bool {
	if r.Attributes.Request.Http.Method != http.MethodGet {
		return false
	}

	extension := filepath.Ext(r.Attributes.Request.Http.Path)
	return x.Contains(StaticAssets, func(item string) bool {
		return item == extension
	})
}

func (svc *CheckService) validRequest(ctx context.Context, r *auth.CheckRequest) bool {
	return x.IsPresent(r) &&
		x.IsPresent(r.Attributes) &&
		x.IsPresent(r.Attributes.Request) &&
		x.IsPresent(r.Attributes.Request.Http)
}

func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse {
	log.WithFields(ctx, log.Fields{"authorized": true})
	return &auth.CheckResponse{
		Status: &status.Status{
			Code: int32(codes.OK),
		},
		HttpResponse: &auth.CheckResponse_OkResponse{
			OkResponse: &auth.OkHttpResponse{
				Headers:              []*core.HeaderValueOption{},
				HeadersToRemove:      []string{},
				ResponseHeadersToAdd: []*core.HeaderValueOption{},
			},
		},
	}
}

func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse {
	log.WithFields(ctx, log.Fields{"authorized": false})
	return &auth.CheckResponse{
		Status: &status.Status{
			Code: int32(codes.PermissionDenied),
		},
		HttpResponse: &auth.CheckResponse_DeniedResponse{
			DeniedResponse: &auth.DeniedHttpResponse{
				Status: &types.HttpStatus{
					Code: types.StatusCode_Unauthorized,
				},
				Headers: []*core.HeaderValueOption{},
			},
		},
	}
}