Skip to content

Commit

Permalink
scopes proto
Browse files Browse the repository at this point in the history
  • Loading branch information
rrrkren committed Jun 30, 2023
0 parents commit 95e2eca
Show file tree
Hide file tree
Showing 18 changed files with 820 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.PHONY: generate-proto
generate-proto:
@echo "Generating proto files..."
@protoc --proto_path=proto --go_out=scopesproto --go_opt=paths=source_relative proto/scopes.proto

.PHONY: generate-test-proto
generate-test-proto:
@echo "Generating test proto files..."
@cd test && buf mod update && buf generate

.PHONY: test
test: generate-test-proto
@echo "Running tests..."
@go test ./...
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# proto-gen-go-grpc-scopes
23 changes: 23 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module github.com/dapperlabs/proto-gen-go-grpc-scopes

go 1.20

require (
github.com/golang/protobuf v1.5.3
github.com/stretchr/testify v1.8.4
google.golang.org/grpc v1.56.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/protobuf v1.30.0
)
30 changes: 30 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
59 changes: 59 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"flag"
"fmt"

"github.com/dapperlabs/proto-gen-go-grpc-scopes/scopesproto"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/types/descriptorpb"
"google.golang.org/protobuf/types/pluginpb"
)

func main() {
var (
flags flag.FlagSet
)

protogen.Options{
ParamFunc: flags.Set,
}.Run(func(gen *protogen.Plugin) error {
for _, f := range gen.Files {
if !f.Generate {
continue
}
msgScopesInfo := map[string][]string{}
for _, msg := range f.Messages {
msgName := msg.GoIdent.GoName
msgOptions := msg.Desc.Options()
if msgOptions == nil {
continue
}
msgOptionsPB := msgOptions.(*descriptorpb.MessageOptions)
ext, err := proto.GetExtension(msgOptionsPB, scopesproto.E_RequiredReqScopes)
if err != nil {
// not a scope extension
continue
}
scopesExt := ext.(*scopesproto.RequiredScopesOption)
msgScopesInfo[msgName] = scopesExt.Scopes
}

if len(msgScopesInfo) > 0 {
// generate scopes file
filename := f.GeneratedFilenamePrefix + "_scopes.pb.go"
g := gen.NewGeneratedFile(filename, f.GoImportPath)
g.P("// Code generated by protoc-gen-go-scopes. DO NOT EDIT.")
g.P("package ", f.GoPackageName)
for msg, requiredScopes := range msgScopesInfo {
g.P(fmt.Sprintf("func (*%s) RequiredScopes() []string {", msg))
g.P(fmt.Sprintf("return %#v", requiredScopes))
g.P("}")
}
}
}
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
return nil
})
}
8 changes: 8 additions & 0 deletions proto/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v1
name: buf.build/rrrkren/proto-gen-go-grpc-scopes
breaking:
use:
- FILE
lint:
use:
- DEFAULT
16 changes: 16 additions & 0 deletions proto/scopes.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";

package scopes;

option go_package = "github.com/dapperlabs/proto-gen-go-grpc-scopes/scopesproto";

import "google/protobuf/descriptor.proto";

// Define your custom option type
message RequiredScopesOption {
repeated string scopes = 1;
}

extend google.protobuf.MessageOptions {
RequiredScopesOption required_req_scopes = 50000; // 50000 is an example, replace it with your desired field number
}
21 changes: 21 additions & 0 deletions scopes/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package scopes

import (
"context"

"google.golang.org/grpc"
)

type ScopeValidator func(ctx context.Context, scopes []string) error

// ScopeValidationInterceptor validates that the request has the required scopes
func ScopeValidationInterceptor(v ScopeValidator) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if scopeFetcher, ok := req.(HasScopeRequirements); ok {
if err := v(ctx, scopeFetcher.RequiredScopes()); err != nil {
return nil, err
}
}
return handler(ctx, req)
}
}
75 changes: 75 additions & 0 deletions scopes/grpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package scopes_test

import (
"context"
"net"
"testing"

"github.com/dapperlabs/proto-gen-go-grpc-scopes/scopes"
"github.com/dapperlabs/proto-gen-go-grpc-scopes/test/testgen"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

func TestScopeValidationInterceptor(t *testing.T) {
server := grpc.NewServer(
grpc.UnaryInterceptor(scopes.ScopeValidationInterceptor(func(ctx context.Context, scopes []string) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Error(codes.Unauthenticated, "missing metadata")
}

authScope := md.Get("authorization_scope")
if len(authScope) != 1 {
return status.Error(codes.Unauthenticated, "missing authorization_scope")
}
providedScope := authScope[0]
for _, allowedScope := range scopes {
if providedScope == allowedScope {
return nil
}
}
return status.Errorf(codes.PermissionDenied, "missing scope: %s", scopes)
})),
)
testgen.RegisterPingPongServer(server, &ScopeValidatorServer{})

lis, err := net.Listen("tcp", ":8080")
require.NoError(t, err)

go server.Serve(lis)

conn, err := grpc.Dial(":8080", grpc.WithInsecure())
require.NoError(t, err)
defer conn.Close()

client := testgen.NewPingPongClient(conn)

header := metadata.New(map[string]string{"authorization_scope": "scope1"})
ctx := metadata.NewOutgoingContext(context.Background(), header)

res, err := client.Ping(ctx, &testgen.PingRequest{})
assert.NoError(t, err)
assert.Equal(t, "pong", res.GetPong())

res, err = client.Ping(context.Background(), &testgen.PingRequest{})
assert.Equal(t, codes.Unauthenticated, status.Code(err))

header = metadata.New(map[string]string{"authorization_scope": "scope3"})
ctx = metadata.NewOutgoingContext(context.Background(), header)
res, err = client.Ping(ctx, &testgen.PingRequest{})
assert.Equal(t, codes.PermissionDenied, status.Code(err))
}

type ScopeValidatorServer struct {
}

func (s ScopeValidatorServer) Ping(ctx context.Context, request *testgen.PingRequest) (*testgen.PingResponse, error) {
return &testgen.PingResponse{Pong: "pong"}, nil
}

var _ testgen.PingPongServer = (*ScopeValidatorServer)(nil)
5 changes: 5 additions & 0 deletions scopes/hasscope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package scopes

type HasScopeRequirements interface {
RequiredScopes() []string
}
Loading

0 comments on commit 95e2eca

Please sign in to comment.